diff --git a/.gitignore b/.gitignore index e3ca5c97..90666cec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # Prerequisites -*.d +# *.d # Object files *.o @@ -37,13 +37,23 @@ *.hex # Debug files -*.dSYM/ +*.dSYM *.su *.idb *.pdb +# PID And Log +*.log +*.PID +*.pid + # Code Edit .vscode +*.la +*.lai +src/e*.h +src/l*.h +build cfadmin # Kernel Module Compile Results diff --git a/3rd/Makefile b/3rd/Makefile new file mode 100644 index 00000000..bd7fc3fa --- /dev/null +++ b/3rd/Makefile @@ -0,0 +1,20 @@ +.PHONY : build rebuild clean + +default : + @echo "=======================================" + @echo "Please use 'make build' command to build it.." + @echo "Please use 'make rebuild' command to build it.." + @echo "Please use 'make clean' command to clean all." + @echo "=======================================" + +# 当您使用Make build、rebuild、clean时, 这里会被调用. +# 所以, 您应该定义一套合理的脚本用于3rd内的库进行维护. + +build: + @echo "Done." + +rebuild: + @echo "Done." + +clean: + @echo "Done." diff --git a/3rd/README.md b/3rd/README.md new file mode 100644 index 00000000..07dab181 --- /dev/null +++ b/3rd/README.md @@ -0,0 +1,55 @@ +## 用户自定义库目录 + + 此目录是用户自定义目录, cf开发者不会对3rd内部文件进行任何意义上的修改, 3rd内组织方式需要用户自行确定. + + 在使用者编译cf的make build, make rebuild, make clean等命令的时候将会同时传入到3rd的Makefile内. + + 3rd也为用户自定义库提供整合方式或联合编译(如有需要), 使用者在开发阶段可能会需要自己编写一套业务维护库. + + 3rd的库维护者应该(至少)维护上述三个编译命令并使其正常工作. 同时, 维护者也需要至少保证以下两点: + + 1. 3rd库的维护者(至少)需要保证引用名唯一性; + + 2. 3rd库的维护者(至少)需要保证无(除cf)的底层特殊依赖性; + + 注: 在无需编译的情况下, 使用者可以讲makefile看做一套自定义库代码整理集合. 有助于用户自行组织库目录. + +## 如何在3rd维护自己的lua库? + + 1. 将文件copy到3rd目录下(这里假设直接copy到3rd根目录, 当然也可以自行构建目录结构) + + 2. 在main.lu文件内使用```local lib = require "3rd.you_lib_name"``` + + 3. 开始使用. + +## 如何在3rd维护自己的lua C库? + + 1. 按照lua C API开发模式开发完毕(可参考luaclib内的文件). + + 2. 将源码copy到3rd目录下, 并在修改```3rd/Makefile```进行进行联合编译. + + 3. 编译完成之后, 执行自己定义的脚本整理文件与路径. + + 4. 在main.lu文件内使用```local lib = require "3rd.you_lib_name"``` + + 5. 开始使用. + +## 最简单3rd用户库编写实例 + + 首先, 在3rd目录下新建一个名为```printer.lua```的文件, 其内容如下: + + ```lua + return function (...) + return print(...) + end + ``` + + 然后, 清空```script/main.lua```内的所有内容, 然后输入以下内容: + + ```lua + local printer = require "3rd.printer" + + printer("这是我在3rd内编写的库") + ``` + + 最后, 使用```./cfadmin``` 命令运行并查看效果. diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..8b7545d5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,121 @@ +# 作者信息 +message("======================================") +message("Project Name : cfadmin") +message("Author Name : CandyMi") +message("Author Email : 869646063@qq.com") +message("Author Github : github.com/CandyMi") +message("======================================") + +# 最低版本号 +cmake_minimum_required(VERSION 2.8...3.13) + +# 项目名称 +project("cfadmin") + +if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set(CMAKE_MACOSX_RPATH 1) +else() + set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) +endif() + +# 增加 CCache 支持 +find_program(CCACHE_FOUND ccache) +if(CCACHE_FOUND) + set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache) + set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) +endif() + +# 依赖openssl +find_package(OpenSSL 1.0.1 REQUIRED) + +# 头文件存放位置 +include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/src /usr/local/include ) +# 库文件存放路径 +link_directories( ${CMAKE_CURRENT_SOURCE_DIR} /usr/local/lib ) + +set(cf_rpath + "-Wl,-rpath,./" + "-Wl,-rpath,../" + "-Wl,-rpath,/usr/local/lib" +) + +set(cf_core + src/core.c + src/core_ev.c + src/core_sys.c + src/core_memory.c +) + +set(cf_admin + src/core_start.c +) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 -Wno-implicit-fallthrough -minline-all-stringops") + +# 编译版本 +if (CMAKE_BUILD_TYPE STREQUAL Release) + add_compile_options(-O2 -g -W -Wall -Wno-unused-parameter -Wno-unused-function -Wno-sign-compare) +else() + add_compile_options(-O0 -g -W -Wall -Wno-unused-parameter -Wno-unused-function -Wno-sign-compare) + add_definitions(-DDEBUG=1) +endif() + +add_definitions(-D_GNU_SOURCE=1) + +# 构建libcore +add_library(core SHARED ${cf_core}) +set_target_properties( + core PROPERTIES + INSTALL_RPATH ${cf_rpath} + LINK_FLAGS ${cf_rpath} + core PROPERTIES + PREFIX "lib" + core PROPERTIES + C_EXTENSIONS ON +) + +# 内存分配器 +if(USE_MIMALLOC OR MIMALLOC) + target_link_libraries(core PRIVATE ev PRIVATE eio PRIVATE lua PRIVATE dl PRIVATE m PRIVATE pthread PRIVATE mimalloc) + add_definitions(-DUSE_ALLOCATOR=1 -DUSE_MIMALLOC=1) +elseif(USE_JEMALLOC OR JEMALLOC) + target_link_libraries(core PRIVATE ev PRIVATE eio PRIVATE lua PRIVATE dl PRIVATE m PRIVATE pthread PRIVATE jemalloc) + add_definitions(-DUSE_ALLOCATOR=1 -DUSE_JEMALLOC=1) +elseif(USE_TCMALLOC OR TCMALLOC) + target_link_libraries(core PRIVATE ev PRIVATE eio PRIVATE lua PRIVATE dl PRIVATE m PRIVATE pthread PRIVATE tcmalloc) + add_definitions(-DUSE_ALLOCATOR=1 -DUSE_TCMALLOC=1) +else() + target_link_libraries(core PRIVATE ev PRIVATE eio PRIVATE lua PRIVATE dl PRIVATE m PRIVATE pthread) +endif() + +# cfadmin可执行文件 +add_executable(cfadmin ${cf_admin}) +target_link_libraries(cfadmin PRIVATE core) +set_target_properties( + cfadmin PROPERTIES + LINK_FLAGS ${cf_rpath} + INSTALL_RPATH "${cf_rpath}" +) + +# 构建`luaclib` +add_subdirectory( luaclib ) + +# 调试环境 +execute_process(COMMAND ln -s -f ${CMAKE_SOURCE_DIR}/lualib ${CMAKE_SOURCE_DIR}/build) +execute_process(COMMAND ln -s -f ${CMAKE_SOURCE_DIR}/script ${CMAKE_SOURCE_DIR}/build) + +# 安装`lualib`/`script` +install( + DIRECTORY + ${PROJECT_SOURCE_DIR}/script + ${PROJECT_SOURCE_DIR}/lualib + DESTINATION + ${CMAKE_INSTALL_PREFIX} +) + +# 安装头文件、动态库、可执行文件 +install( + TARGETS cfadmin core + RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX} # cfadmin.exe + LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX} # libcore +) \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..09310e02 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM rockylinux/rockylinux:9 as builder +# 设置时区与语言环境变量 +#ENV TIME_ZONE=Asia/Shanghai +#RUN echo "${TIME_ZONE}" > /etc/timezone && ln -sf /usr/share/zoneinfo/${TIME_ZONE} /etc/localtime +WORKDIR /app + +ADD . /app +RUN yum install gcc file autoconf automake make libtool git openssl-devel zlib-devel -y && rm -rf /var/cache/yum +RUN sh build.sh && make build + +FROM rockylinux/rockylinux:9 +# 设置时区与语言环境变量 +#ENV TIME_ZONE=Asia/Shanghai +#RUN echo "${TIME_ZONE}" > /etc/timezone && ln -sf /usr/share/zoneinfo/${TIME_ZONE} /etc/localtime +COPY --from=builder /app/ /app +WORKDIR /app +ENTRYPOINT ["./cfadmin"] diff --git a/Excel.csv b/Excel.csv new file mode 100644 index 00000000..e7b258c7 --- /dev/null +++ b/Excel.csv @@ -0,0 +1,4 @@ +ID,Name,Des,Model, +1,广告,www.baidu.com,csv.png,1.1 +2,公告,www.notice.com,notice.png,2.1 +3,测试,www.test.com,test.png,3.1 diff --git a/Makefile b/Makefile index 2f0a5c65..100d440c 100644 --- a/Makefile +++ b/Makefile @@ -1,26 +1,30 @@ -.PHONY : build clean +.PHONY : build rebuild clean + +RM = rm -rf default : @echo "=======================================" @echo "Please use 'make build' command to build it.." + @echo "Please use 'make rebuild' command to rebuild it.." @echo "Please use 'make clean' command to clean all." @echo "=======================================" # 如果需要修改内存分配器,请修改: # 1. src/Makefile -# 2. lualib/Makefile -# 3. lualib/src/cjson/Makefile +# 2. luaclib/Makefile build : - cd src && rm -rf *.so *.o && make build - cd luaclib && rm -rf *.so *.o && make build + @$(MAKE) -s -C src build + @echo "********** Built-in core modules **********" + @cd luaclib && $(MAKE) -s internal 3part -j4 + @$(MAKE) -s -C 3rd build rebuild : - rm -rf main cfadmin *.so - cd src && rm -rf *.so *.o && make rebuild - cd luaclib && rm -rf *.so *.o && make rebuild + @$(MAKE) -s clean + @$(MAKE) -s build clean : - rm -rf main cfadmin *.so - cd src && make clean - cd luaclib && make clean \ No newline at end of file + @echo "********** Clean All Files **********" + @echo "rm -rf cfadmin libcore luaclib/*.so 3rd/*.so" + @$(RM) cfadmin cfadmin.exe libcore.so libcore.dll luaclib/*.so + @$(MAKE) -s -C 3rd clean \ No newline at end of file diff --git a/Person.pb b/Person.pb new file mode 100644 index 00000000..e183002e --- /dev/null +++ b/Person.pb @@ -0,0 +1,14 @@ + + + Person.proto" +Person +name ( Rname +age ( Rage +hand ( 2 .Person.HandRhand +foot ( 2 .Person.FootRfoot0 +Hand +left ( Rleft +right ( Rright0 +Foot +left ( Rleft +right ( Rrightbproto3 \ No newline at end of file diff --git a/Person.proto b/Person.proto new file mode 100644 index 00000000..a0741372 --- /dev/null +++ b/Person.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; + +message Person +{ + message Hand + { + string left = 1; + string right = 2; + } + message Foot + { + string left = 1; + string right = 2; + } + string name = 1; + uint32 age = 2; + Hand hand = 3; + Foot foot = 4; +} diff --git a/README.md b/README.md index 3171bd4d..a91f997c 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,98 @@ -## core_framework 一个基于libev的轻量级lua网络开发框架 -

- - - - - - -

+

+
-> 大道至简, 返璞归真. + English | [简体中文](README_zh-cn.md) ---- +
-## 介绍一下CF +
+

+ +

+ Efficient asynchronous comes from design, excellent performance practice truth. +

+

+ + + + + + + + + +

+
-> 文档在这里: +# Advantage -* [CF是什么?](https://github.com/CandyMi/core_framework/wiki/home) + - [x] Asynchronous I/O - The `Network I/O` and `File I/O` have been transformed, and the internal operations are now fully asynchronous. -* [CF使用到的技术栈?](https://github.com/CandyMi/core_framework/wiki/MAP) + - [x] Rich built-in libraries - Many complete built-in libraries are implemented to complete the development and coverage of all aspects of basic applications. -## 第一次安装CF + - [x] Automated scheduling - The bottom layer will automatically schedule `coroutines`, `timers` and `I/O`, and you can also choose to control it manually. -> [CF如何安装?](https://github.com/CandyMi/core_framework/wiki/install) + - [x] Security and Encryption - Provides nearly 60 kinds of hash/digest/hash/signature algorithms, and internally supports SSL Server/Client. -> [CF如何运行?](https://github.com/CandyMi/core_framework/wiki/RUN) + - [x] Data Exchange Format - Do you need `JSON` / `PROTOBUF` / `XML` / `MSGPACK` / `BSON` ? Great, they are all provided! -> [如何在容器内运行?](https://github.com/CandyMi/core_framework/wiki/Docker) + - [x] Multi-database driver - `MySQL` / `PGSQL` / `MSSQL` / `MongoDB` are all available, you can also use it as you like. -## 第一次运行CF + - [x] Enforcement and standardization -The framework forces you to write code, and you can also pass it on to your close friends. -> [CF如何运行?](https://github.com/CandyMi/core_framework/wiki/RUN) +# Website -## 第一次使用CF的问题 + * [Home](https://cfadmin.cn/)(Only Chinese.) -> [我有一些cf使用上的问题?](https://github.com/CandyMi/core_framework/wiki/QA) + * [Wiki](https://doc.cfadmin.cn/)(Only Chinese.) -## 联系作者 +# Build And Run -> [issues](https://github.com/CandyMi/core_framework/issues) +

-> 作者邮箱 +

-## 支持 +# Preview -> 简单, 不简单! +

-> 使用cf的目标: [拒绝996](https://github.com/996icu/996.ICU), [没有ICU](https://github.com/996icu/996.ICU) +

-## 授权协议 +
More picture -[BSD LICENSE](https://github.com/CandyMi/core_framework/blob/master/LICENSE) +

+ +

+ +

+ +

+ +

+ +

+ +
+ +# Feedback + + * [ISSUES](https://github.com/CandyMi/cfadmin/issues) + + * [QQ Group](https://shang.qq.com/wpa/qunwpa?idkey=5cc977ebaf4eb17391b2c6b03eb0ee36e3d3c1871bc95ba3c96ffc426a9dc907) + +# List of contributors + + * [suzaichuan](https://github.com/suzaichuan) - `Test` and `Problem Feedback`. + + * [Nuctori](https://github.com/Nuctori) - Feature Contribution. + +# Users + + |Project Name|Project Logo| + |:-:|:-:| + |商票易|![](https://raw.githubusercontent.com/wiki/CandyMi/cfadmin/images/company-2.png) + |爆款捕手|![](https://raw.githubusercontent.com/wiki/CandyMi/cfadmin/images/company-1.png)| + +# License + + [BSD LICENSE](https://github.com/CandyMi/cfadmin/blob/master/LICENSE) diff --git a/README_zh-cn.md b/README_zh-cn.md new file mode 100644 index 00000000..e87be12d --- /dev/null +++ b/README_zh-cn.md @@ -0,0 +1,94 @@ +

+
+ + [English](README.md) | 简体中文 + +
+ +
+

+ +

+ 高效的异步源于设计, 卓越的性能实践真理. +

+

+ + + + + + + + + +

+
+ +# 特性优势 + + - [x] 完善的异步I/O - 框架已经对网络、文件两种IO进行改造, 使内部的I/O全异步化成为可能. + + - [x] 丰富的内置库 - 框架内部的提供了完整的内置库, 实现了基础应用全方面开发覆盖. + + - [x] 自动化调度 - 框架会在内部自动调度协程、定时器、I/O, 您也可以选择手动控制它. + + - [x] 安全与加密 - 提供近60多种加密/散列/签名等等算法, 并且内部已经支持SSL Server / Client. + + - [x] 数据交换格式 - 您需要 JSON / PROTOBUF / XML / MSGPACK / BSON 吗? 那么非常幸运, 它们都提供好了! + + - [x] 带宽与性能优化 - 传输数据压缩与数据库连接池的利用都不用手动维护. + + - [x] 多数据库驱动支持 - MySQL / PGSQL / MSSQL / MongoDB 这些都有, 你也可以随心所欲的使用. + + - [x] 强制化与规范化 - 框架强制要求您编写代码, 同样您也可以将其传授给挚友. + +# 学习网站 + + * [社区首页](https://cfadmin.cn) + + * [学习文档](https://cfadmin.cn) + +# 使用预览 + +

+ +

+ +
更多图片 + +

+ +

+ +

+ +

+ +

+ +

+ +
+ +# 反馈方式 + + * [我要提问](https://github.com/CandyMi/cfadmin/issues) + + * [我要加群](https://shang.qq.com/wpa/qunwpa?idkey=5cc977ebaf4eb17391b2c6b03eb0ee36e3d3c1871bc95ba3c96ffc426a9dc907) + +# 贡献名单 + + * [suzaichuan](https://github.com/suzaichuan) - `积极测试`与`问题反馈`. + + * [Nuctori](https://github.com/Nuctori) - `功能性代码`的贡献. + +# 用户列表 + + |项目名称|项目图片| + |:-:|:-:| + |商票易|![](https://raw.githubusercontent.com/wiki/CandyMi/cfadmin/images/company-2.png)| + |爆款捕手|![](https://raw.githubusercontent.com/wiki/CandyMi/cfadmin/images/company-1.png)| + +# 授权协议 + + [BSD LICENSE](https://github.com/CandyMi/cfadmin/blob/master/LICENSE) diff --git a/build.sh b/build.sh new file mode 100755 index 00000000..652bfc27 --- /dev/null +++ b/build.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash + +# Run this file to install libev and lua; if you already have lua and libev in your environment, you can ignore this file and try to compile directly using makefile. +# 运行这个文件可以安装libev与lua; 如果您的环境中已经有了lua/libeio/libev后可以忽略此文件并且直接使用makefile尝试编译. + +# This file must be executed in the current folder directory, otherwise the installation will be wrong. Beginners need to keep in mind. +# 必须在当前文件夹目录执行此文件, 否则安装将会出错. 初学者需要谨记. + +# Running this script in some embedded environments may lack the scripting tools that should be present (eg ls/printf/grep, etc.), you should find a way to install this command. +# 在一些嵌入式环境下运行此脚本可能会缺少本应存在的脚本工具(如: ls/printf/grep等等), 您应该想办法安装此命令. + +# Before executing this build file, you need to make sure that these software environments are installed: gcc/clang autoconf automake make libtool git readline-devel openssl-devel. +# 执行这个编译文件之前需要确保安装了这些软件环境: gcc/clang autoconf automake make libtool git readline-devel openssl-devel. 如果未安装或者缺少安装, 请仔细检查并且自行尝试安装依赖环境. + +current=`pwd` + +rm -rf build && mkdir build && cd build + + +osID=`cat /etc/*-release | grep -w ID | cut -f2 -d '=' | tr -d '\"'` + + +timeArea=`date +"%Z %z"` +if [[ $timeArea == "CST +0800" ]] +then + echo -n "是否自动安装cfadmin所需依赖? y/n: " +else + echo -n "Do you want to install the required dependencies? (y/N)? y/n: " +fi + +#自动安装依赖 +read isAutoDependence +if [[ $isAutoDependence == "y" ]] || [[ $isAutoDependence == "yes" ]] +then + if [[ $osID == "debian" ]] || [[ $osID == "ubuntu" ]] || [[ $osID == "kali" ]] || [[ $osID == "linuxmint" ]] || [[ $osID == "pop" ]] || [[ $osID == "mx" ]] || [[ $osID == "deepin" ]] + then + if [ `id -u` -ne 0 ]; then + sudo apt install gcc file autoconf automake make libtool git libssl-dev zlib1g-dev --no-upgrade -y + else + apt install gcc file autoconf automake make libtool git libssl-dev zlib1g-dev --no-upgrade -y + fi + elif [[ $osID == "centos" ]] || [[ $osID == "rhel" ]] || [[ $osID == "suse" ]] || [[ $osID == "rocky" ]] || [[ $osID == "ol" ]] || [[ $osID == "scientific" ]] || [[ $osID == "almalinux" ]] + then + if [ `id -u` -ne 0 ]; then + sudo yum install gcc file autoconf automake make libtool git zlib-devel openssl-devel -y + else + yum install gcc file autoconf automake make libtool git zlib-devel openssl-devel -y + fi + elif [[ $osID == "arch" ]] || [[ $osID == "msys2" ]] || [[ $osID == "manjaro" ]] || [[ $osID == "endeavouros" ]] || [[ $osID == "parabola" ]] || [[ $osID == "archbang" ]] + then + pacman -S --noconfirm gcc file autoconf automake make libtool git zlib openssl + else + echo $osID "not support anto install dependence, please request issue. https://github.com/cfadmin-cn/cfadmin" + fi +else + echo "not auto install dependence" +fi + +# 通过时区,自动选择镜像源 +if [[ $timeArea == "CST +0800" ]] +then + git clone https://gitee.com/CandyMi/lua -b v5.4.6 + git clone https://gitee.com/CandyMi/libev -b v4.33 + git clone https://gitee.com/CandyMi/libeio + +else + git clone https://github.com/CandyMi/lua -b v5.4.6 + git clone https://github.com/CandyMi/libev -b v4.33 + git clone https://github.com/CandyMi/libeio +fi + +echo "========== build libev ==========" && + cd ${current}/build/libev && sh autogen.sh && ./configure --prefix=/usr/local --enable-shared=no --with-pic && + + ## 1. 将头文件与库文件放到cf框架目录下(Put the header files and library files in the cf framework directory) + make && cp e*.h ${current}/src && cd .libs && cp $(printf "%s" "`ls | grep libev | grep -v la`") ${current}/ + + ## 2. 将 libev 安装到 /usr/local 区域, 对其进行全局共享库链接. (Install `libev` into the `/usr/local` zone and link it with global shared libraries.) + # make && make install + +echo "========== build libeio ==========" && + cd ${current}/build/libeio && sh autogen.sh && ./configure --prefix=/usr/local --enable-shared=no --with-pic && + + ## 1. 将头文件与库文件放到cf框架目录下(Put the header files and library files in the cf framework directory) + make && cp e*.h ${current}/src && cd .libs && cp $(printf "%s" "`ls | grep libeio | grep -v la`") ${current}/ + + ## 2. 将 libeio 安装到 /usr/local 区域, 对其进行全局共享库链接. (Install `libeio` into the `/usr/local` zone and link it with global shared libraries.) + # make && make install + +echo "========== build lua ==========" && + cd ${current}/build/lua && make posix MYCFLAGS="-fPIC -DLUA_USE_DLOPEN" MYLIBS="-ldl" && + + ## 1. 将头文件与库文件放到cf框架目录下(Put the header files and library files in the cf framework directory) + cp lua.h luaconf.h lualib.h lauxlib.h ${current}/src && cp liblua.* ${current}/ + + ## 2. 将 lua 安装到 /usr/local 区域, 对其进行全局共享库链接. (Install `lua` into the `/usr/local` zone and link it with global shared libraries.) + # cp -rf lua.h luaconf.h lualib.h lauxlib.h /usr/local/include && cp liblua.* /usr/local/lib + +echo "Done." + +echo "========== clean build ==========" && cd ${current} && rm -rf build diff --git a/clean.sh b/clean.sh new file mode 100755 index 00000000..67947fdd --- /dev/null +++ b/clean.sh @@ -0,0 +1,5 @@ +# This script is used to clean up build.sh to install the header files and library files brought by libev and lua. You can ignore this file when you already have lua and libev in your environment. +# 此脚本为清理build.sh安装libev与lua带来的头文件与库文件, 当您的环境中已经有了lua与libev后可以忽略此文件. + +rm -rf libev* libeio* liblua* +rm -rf src/l* src/e*.h diff --git a/docker/Dockerfile b/docker/Dockerfile index 90dfe8be..d9131fbb 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,23 +1,12 @@ FROM centos:7 MAINTAINER CandyMi "869646063@qq.com" -# 加入include与lib搜索路径 -ENV LD_LIBRARY_PATH /usr/local/lib:$LD_LIBRARY_PATH -ENV C_INCLUDE_PATH /usr/local/include:$C_INCLUDE_PATH - -WORKDIR /root/download - -COPY ./lua-5.3.5.tar.gz /root/download/lua.tar.gz -COPY ./libev-4.25.tar.gz /root/download/libev.tar.gz - -RUN yum groupinstall "development tools" -y && yum install autoconf git readline readline-devel openssl openssl-devel -y \ - && tar zxvf lua.tar.gz && cd lua* && make linux MYCFLAGS=-fPIC && make install && cd .. \ - && tar zxvf libev.tar.gz && cd libev* && ./configure --prefix=/usr/local && make && make install \ - && rm -rf /roo/download /var/cache/yum \ - && git clone https://github.com/CandyMi/core_framework /app \ - && cd /app && make rebuild +RUN yum install gcc file autoconf automake make libtool git openssl-devel -y \ + && git clone https://github.com/CandyMi/cfadmin.git /app \ + && cd /app && sh build.sh && make build \ + && rm -rf /var/cache/yum # 使用者可在启动容器时使用-v命令将您的代码目录直接挂载到/app/script目录进行调试操作 WORKDIR /app -CMD ["./cfadmin"] \ No newline at end of file +ENTRYPOINT ["./cfadmin"] diff --git a/docker/nginx.conf b/docker/conf.d/nginx.conf similarity index 89% rename from docker/nginx.conf rename to docker/conf.d/nginx.conf index 220eb955..469b57cb 100644 --- a/docker/nginx.conf +++ b/docker/conf.d/nginx.conf @@ -34,19 +34,26 @@ http { gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; gzip_disable "MSIE [1-6]\."; + + upstream myweb { + server webapp1:8080; + server webapp2:8080; + server webapp3:8080; + } + server { listen 80; #access_log /var/log/nginx/8080.log main; location /ws { - proxy_pass http://webapp:8080/ws; + proxy_pass http://myweb/ws; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } location / { - proxy_pass http://webapp:8080; + proxy_pass http://myweb; proxy_http_version 1.1; proxy_ignore_client_abort on; proxy_set_header Host $http_host; diff --git a/docker/db/database.sql b/docker/db/database.sql new file mode 100644 index 00000000..90a70f98 --- /dev/null +++ b/docker/db/database.sql @@ -0,0 +1,110 @@ +# ************************************************************ +# Sequel Pro SQL dump +# Version 4541 +# +# http://www.sequelpro.com/ +# https://github.com/sequelpro/sequelpro +# +# Host: 127.0.0.1 (MySQL 5.7.25) +# Database: cfadmin +# Generation Time: 2019-05-21 02:32:16 +0000 +# ************************************************************ + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +# ---------------------------- +# Table structure for cfadmin_headers +# ---------------------------- +CREATE TABLE IF NOT EXISTS `cfadmin_headers` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID', + `name` varchar(255) NOT NULL COMMENT '头部名称', + `url` varchar(255) NOT NULL COMMENT '头部URL', + `create_at` int(11) unsigned NOT NULL COMMENT '创建时间', + `update_at` int(11) unsigned NOT NULL COMMENT '修改时间', + `active` tinyint(4) unsigned NOT NULL COMMENT '删除标志', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='顶部菜单表'; + +# ---------------------------- +# Table structure for cfadmin_menus +# ---------------------------- +CREATE TABLE IF NOT EXISTS `cfadmin_menus` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID', + `parent` int(11) unsigned NOT NULL COMMENT '父菜单ID', + `name` varchar(255) NOT NULL COMMENT '菜单名称', + `url` varchar(255) DEFAULT NULL COMMENT '菜单链接', + `icon` char(255) DEFAULT NULL COMMENT '菜单图标', + `create_at` int(11) unsigned NOT NULL COMMENT '创建时间', + `update_at` int(11) unsigned NOT NULL COMMENT '更新时间', + `active` tinyint(4) unsigned NOT NULL COMMENT '删除标志', + PRIMARY KEY (`id`), + KEY `parant_index` (`parent`) USING BTREE COMMENT '父ID索引' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='侧边菜单表'; + +# ---------------------------- +# Table structure for cfadmin_permissions +# ---------------------------- +CREATE TABLE IF NOT EXISTS `cfadmin_permissions` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID', + `role_id` int(11) unsigned NOT NULL COMMENT '所属角色', + `menu_id` int(11) unsigned NOT NULL COMMENT '所属菜单', + `create_at` int(11) unsigned NOT NULL COMMENT '创建时间', + `update_at` int(11) unsigned NOT NULL COMMENT '修改时间', + `active` tinyint(4) unsigned NOT NULL COMMENT '是否启用', + PRIMARY KEY (`id`), + KEY `com_index` (`active`,`role_id`,`menu_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色权限表'; + +# ---------------------------- +# Table structure for cfadmin_roles +# ---------------------------- +CREATE TABLE IF NOT EXISTS `cfadmin_roles` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID', + `name` varchar(255) NOT NULL COMMENT '角色名称', + `is_admin` tinyint(4) unsigned NOT NULL COMMENT '管理员标志', + `create_at` int(11) unsigned NOT NULL COMMENT '创建时间', + `update_at` int(1) unsigned NOT NULL COMMENT '修改时间', + `active` tinyint(4) unsigned NOT NULL COMMENT '删除标志', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色表'; + +# ---------------------------- +# Table structure for cfadmin_tokens +# ---------------------------- +CREATE TABLE IF NOT EXISTS `cfadmin_tokens` ( + `uid` int(11) unsigned NOT NULL COMMENT '用户ID', + `name` varchar(255) NOT NULL COMMENT '用户名称', + `token` varchar(255) NOT NULL COMMENT '用户TOKEN', + `create_at` int(11) unsigned NOT NULL COMMENT '登录时间', + PRIMARY KEY (`uid`) +) ENGINE=MEMORY DEFAULT CHARSET=utf8mb4 COMMENT='用户Token表'; + +# ---------------------------- +# Table structure for cfadmin_users +# ---------------------------- +CREATE TABLE IF NOT EXISTS `cfadmin_users` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID', + `name` varchar(255) NOT NULL COMMENT '用户名', + `username` varchar(255) NOT NULL COMMENT '用户账户', + `password` varchar(255) NOT NULL COMMENT '用户密码', + `email` varchar(255) NOT NULL COMMENT '用户邮箱', + `phone` bigint(11) unsigned NOT NULL COMMENT '用户手机', + `role` int(11) unsigned NOT NULL COMMENT '用户角色', + `create_at` int(11) unsigned NOT NULL COMMENT '创建时间', + `update_at` int(11) unsigned NOT NULL COMMENT '修改时间', + `active` tinyint(4) unsigned NOT NULL COMMENT '删除标志', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; + +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/docker/docker-compose-with-cfadmin.yaml b/docker/docker-compose-with-cfadmin.yaml new file mode 100644 index 00000000..f4187982 --- /dev/null +++ b/docker/docker-compose-with-cfadmin.yaml @@ -0,0 +1,39 @@ +# docker-compose.yaml +version: "2" +services: + WebApp: + image: candymi/cfweb + restart: always + volumes: + - ./script/:/app/script/ + # 如需设置时区, 建议请将localtime提取到home目录下再挂在使用 + # - /etc/localtime:/etc/localtime + ports: + - 8080:8080 + depends_on: + - WebDB + links: + - WebDB:WebDB + networks: + - Web_Net + + WebDB: + image: mysql:5.6 #支持8.X以上版本, 但是必须开启下面的COMMAND参数. + restart: always + environment: + - MYSQL_ROOT_PASSWORD=123456789 + - MYSQL_DATABASE=cfadmin + # 8.x以上版本必须设置为mysql_native_password, 否则无法连接到MySQL + # command: --default-authentication-plugin=mysql_native_password + volumes: + - ./db/:/docker-entrypoint-initdb.d/ + # 如需设置时区, 建议请将localtime提取到home目录下再挂在使用 + # - /etc/localtime:/etc/localtime + ports: + - 3306:3306 + networks: + - Web_Net + +networks: + Web_Net: + driver: bridge diff --git a/docker/docker-compose-with-nginx.yaml b/docker/docker-compose-with-nginx.yaml new file mode 100644 index 00000000..e582956c --- /dev/null +++ b/docker/docker-compose-with-nginx.yaml @@ -0,0 +1,80 @@ +# docker-compose.yaml +version: "2" +services: + WebProxy: + image: nginx:latest + restart: always + ports: + - 80:80 + volumes: + - ./conf.d/nginx.conf:/etc/nginx/nginx.conf + links: + - WebApp1:webapp1 + - WebApp2:webapp2 + - WebApp3:webapp3 + networks: + - local + + WebApp1: + image: candymi/cfweb + restart: always + volumes: + - ./script/:/app/script/ + # 如需设置时区, 建议请将localtime提取到home目录下再挂在使用 + # - /etc/localtime:/etc/localtime + depends_on: + - WebDB + links: + - WebDB:WebDB + networks: + - local + + + WebApp2: + image: candymi/cfweb + restart: always + volumes: + - ./script/:/app/script/ + # 如需设置时区, 建议请将localtime提取到home目录下再挂在使用 + # - /etc/localtime:/etc/localtime + depends_on: + - WebDB + links: + - WebDB:WebDB + networks: + - local + + WebApp3: + image: candymi/cfweb + restart: always + volumes: + - ./script/:/app/script/ + # 如需设置时区, 建议请将localtime提取到home目录下再挂在使用 + # - /etc/localtime:/etc/localtime + depends_on: + - WebDB + links: + - WebDB:WebDB + networks: + - local + + WebDB: + image: mysql:5.6 #支持8.X以上版本, 但是必须开启下面的COMMAND参数. + restart: always + environment: + - MYSQL_ROOT_PASSWORD=123456789 + - MYSQL_DATABASE=cfadmin + # 8.x以上版本必须设置为mysql_native_password, 否则无法连接到MySQL + # command: --default-authentication-plugin=mysql_native_password + volumes: + - ./db/:/docker-entrypoint-initdb.d/ + # 如需设置时区, 建议请将localtime提取到home目录下再挂在使用 + # - /etc/localtime:/etc/localtime + ports: + - 3306:3306 + networks: + - local + +networks: + local: + driver: bridge diff --git a/docker/docker-compose-with-traefik.yaml b/docker/docker-compose-with-traefik.yaml new file mode 100644 index 00000000..473fba36 --- /dev/null +++ b/docker/docker-compose-with-traefik.yaml @@ -0,0 +1,49 @@ +# docker-compose.yaml +version: "2" +services: + WebProxy: + image: traefik + restart: always + command: --api --docker # Enables the web UI and tells Traefik to listen to docker + ports: + - "80:80" # The HTTP port + - "8080:8080" # The Web UI (enabled by --api) + volumes: + - /var/run/docker.sock:/var/run/docker.sock # So that Traefik can listen to the Docker events + networks: + - local + + WebApp: + image: candymi/cfweb:latest + restart: always + labels: + - "traefik.port=8080" + - "traefik.backend=WebApp" + - "traefik.enable=true" + - "traefik.domain=localhost" + - "traefik.frontend.rule=Host:localhost" + volumes: + - ./script:/app/script/ + networks: + - local + + WebDB: + image: mysql:5.6 #支持8.X以上版本, 但是必须开启下面的COMMAND参数. + restart: always + environment: + - MYSQL_ROOT_PASSWORD=123456789 + - MYSQL_DATABASE=cfadmin + # 8.x以上版本必须设置为mysql_native_password, 否则无法连接到MySQL + # command: --default-authentication-plugin=mysql_native_password + volumes: + - ./db/:/docker-entrypoint-initdb.d/ + # 如需设置时区, 建议请将localtime提取到home目录下再挂在使用 + # - /etc/localtime:/etc/localtime + ports: + - 3306:3306 + networks: + - local + +networks: + local: + driver: bridge diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml deleted file mode 100644 index a45d3817..00000000 --- a/docker/docker-compose.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# docker-compose.yaml -version: "2" -services: - WebProxy: - container_name: WebProxy - image: nginx:latest - ports: - - 80:80 - volumes: - - ./nginx.conf:/etc/nginx/nginx.conf - networks: - - local - links: - - WebApp:webapp - WebApp: - container_name: WebApp - image: candymi/cfweb:latest - networks: - - local -networks: - local: - driver: bridge diff --git a/docker/script/main.lua b/docker/script/main.lua new file mode 100644 index 00000000..1e0dacc6 --- /dev/null +++ b/docker/script/main.lua @@ -0,0 +1,105 @@ +local httpd = require "httpd" +local httpc = require "httpc" +local DB = require "DB" + +--[[ +请按照以下步奏初始化后台: + 1. 创建一个数据库(名字任意); + 2. 请手动打开lualib/db/database.sql文件, 复制里面的SQL语句在GUI工具中执行一次; + 3. 执行完成之后, 将您填写的数据库替换database字段, 并且charset需要设置一致. +]] + +local db = DB:new({ + host = 'WebDB', + port = 3306, + username = 'root', + password = '123456789', + charset = 'utf8mb4', + database = 'cfadmin', + max = 100, +}) + +db:connect() + +-- 导入httpd对象 +local app = httpd:new("App") +-- httpd启用Cookie扩展 +app:enable_cookie() +-- httpd设置Cookie加密的密匙 +app:cookie_secure("https://github.com/CandyMi/core_framework") +-- app:cookie_secure("candymi") + +app:ws('/ws', require "ws") + +app:api('/api', function (content) + local code, response = httpc.get("https://www.baifubao.com/callback?cmd=1059&callback=phone&phone=13000000000") + return code == 200 and response or "httc请求失败" +end) + +app:use('/view', function (content) + return "

cfadmin v0.3

" +end) + + +-- 导入cf内置的admin库 +local cfadmin = require "admin" + +-- 注册后台页面路由 +cfadmin.init_page(app, db) + + +-- 这个函数仅在第一次初始化数据的时候适用 +-- 初始化完成之后, 请不要再运行. +cfadmin.init_db() + +-- 这里设置首页的显示的页面 +-- cfadmin.init_home(location or domain + path) +-- cfadmin.init_home('https://www.baidu.com') + +local view = require "admin.view" +-- 参数: +-- 1. ctx是一个http req 对象, 目前内置包括: get_method, get_args, get_path, get_raw_path, get_headers, get_cookie +-- 2. db初始化后的db对象, 方便用户直接使用. + +view.use('/admin/test1', function (ctx, db) + return "hello world" +end) + +view.api('/api/admin/test2', function (ctx, db) + return '{"code":0,"msg":"hello world"}' +end) + +-- 这里是设置语言的地方 +-- 语言表在admin/locales内, 可参照key -> value进行填写. +-- 传入一个数组表: 索引1是key, 索引2为显示内容. + +-- cfadmin.add_locale_item('ZH-CN', { +-- {'login.form.title', '这是登录页Title'}, +-- {'dashboard.header.logo', '仪表盘 Logo'} +-- }) + +-- cfadmin.add_locale_item('EN-US', { +-- {'login.form.title', 'This is Login Page Title'}, +-- {'dashboard.header.logo', 'dashboard Logo'} +-- }) + +-- 开启页面缓存能显著提升页面渲染性能. 生产环境下建议开启. +-- 也因为cf缓存模板页面内容, 所以开发模式下不建议开启. +-- cfadmin.cached() + +-- 这个方法可以用来设置静态文件域名与前缀. +-- 如果静态文件在其它域名或者无法访问, 可以使用这个参数修改.(域名后必须加上'/') +-- cfadmin.static('/') + +-- 设置cfadmin的区域语言, 默认为: ZH-CN +-- cfadmin.set_locale('EN-US') + +-- 设置客户端静态文件ttl值内无需再次请求, 减少服务端消耗 +-- app:static('static', 30) +app:static('static') + +-- httpd监听端口 +app:listen("0.0.0.0", 8080) + +-- 运行 +app:run() diff --git a/docker/script/ws.lua b/docker/script/ws.lua new file mode 100644 index 00000000..cf523ccd --- /dev/null +++ b/docker/script/ws.lua @@ -0,0 +1,41 @@ +local class = require "class" +local cf = require "cf" +local json = require "json" +local websocket = class("websocket") + +function websocket:ctor(opt) + self.ws = opt.ws -- websocket对象 + self.send_masked = false -- 掩码(默认为false, 不建议修改或者使用) + self.max_payload_len = 65535 -- 最大有效载荷长度(默认为65535, 不建议修改或者使用) + self.timeout = 15 -- 默认为一直等待, 非number类型会导致异常. + self.count = 0 +end + +function websocket:on_open() + print('on_open') + self.timer = cf.at(0.01, function ( ... ) -- 定时器 + self.count = self.count + 1 + self.ws:send(tostring(self.count)) + end) +end + +function websocket:on_message(data, typ) + print('on_message', self.ws, data) + self.ws:send('welcome') + -- self.ws:close(data) +end + +function websocket:on_error(error) + print('on_error', self.ws, error) +end + +function websocket:on_close(data) + print('on_close', self.ws, data) + if self.timer then -- 清理定时器 + print("清理定时器") + self.timer:stop() + self.timer = nil + end +end + +return websocket diff --git a/logs/README.md b/logs/README.md new file mode 100644 index 00000000..55dceb52 --- /dev/null +++ b/logs/README.md @@ -0,0 +1,3 @@ +## 日志文件夹 + +> 日志将会自动按照日期切割 diff --git a/luaclib/CMakeLists.txt b/luaclib/CMakeLists.txt new file mode 100644 index 00000000..1ff2e5a8 --- /dev/null +++ b/luaclib/CMakeLists.txt @@ -0,0 +1,125 @@ +# 最低版本号 +cmake_minimum_required(VERSION 2.8...3.13) + +# 项目名称 +project("cfadmin luaclib") + +if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") + set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) +endif() + +set(cf_luaclib + sys + tcp + udp + task + pack + timer + child + cjson + laio + lz + lfs + lpeg + lcrypt + lmsgpack + lprotobuf + lhttpparser +) + +# sys库 +FILE(GLOB sys src/lsys.c) +add_library(sys SHARED ${sys}) +target_link_libraries(sys PRIVATE core) + +# tcp库 +FILE(GLOB tcp src/ltcp.c) +add_library(tcp SHARED ${tcp}) +target_link_libraries(tcp PRIVATE core crypto ssl) + +# udp库 +FILE(GLOB udp src/ludp.c) +add_library(udp SHARED ${udp}) +target_link_libraries(udp PRIVATE core) + +# task库 +FILE(GLOB task src/ltask.c) +add_library(task SHARED ${task}) +target_link_libraries(task PRIVATE core) + +# pack库 +FILE(GLOB pack src/lpack.c) +add_library(pack SHARED ${pack}) +target_link_libraries(pack PRIVATE core) + +# child库 +FILE(GLOB child src/lchild.c) +add_library(child SHARED ${child}) +target_link_libraries(child PRIVATE core) + +# timer库 +FILE(GLOB timer src/ltimer.c) +add_library(timer SHARED ${timer}) +target_link_libraries(timer PRIVATE core) + + +# aio库 +FILE(GLOB laio src/laio.c) +add_library(laio SHARED ${laio}) +target_link_libraries(laio PRIVATE core eio) + +# lz库 +FILE(GLOB lz src/lz/*.c) +add_library(lz SHARED ${lz}) +target_link_libraries(lz PRIVATE core) + +# lfs库 +FILE(GLOB lfs src/lfs/*.c) +add_library(lfs SHARED ${lfs}) +target_link_libraries(lfs PRIVATE core) + +# pbc库 +FILE(GLOB lprotobuf src/lpbc/*.c) +add_library(lprotobuf SHARED ${lprotobuf}) +target_link_libraries(lprotobuf PRIVATE core) + +# peg库 +FILE(GLOB lpeg src/lpeg/*.c) +add_library(lpeg SHARED ${lpeg}) +target_link_libraries(lpeg PRIVATE core) + +# cjson库 +FILE(GLOB cjson src/lcjson/*.c) +add_library(cjson SHARED ${cjson}) +target_link_libraries(cjson PRIVATE core) + +# crypt库 +FILE(GLOB lcrypt src/lcrypt/*.c) +add_library(lcrypt SHARED ${lcrypt}) +target_link_libraries(lcrypt PRIVATE core crypto ssl) + +# httpparser库 +FILE(GLOB lhttpparser src/lhttpparser/*.c) +add_library(lhttpparser SHARED ${lhttpparser}) +target_link_libraries(lhttpparser PRIVATE core) + +# msgpack库 +FILE(GLOB lmsgpack src/lmsgpack/*.c) +add_library(lmsgpack SHARED ${lmsgpack}) +target_link_libraries(lmsgpack PRIVATE core) + +# 配置属性 +set_target_properties( + ${cf_luaclib} PROPERTIES + C_EXTENSIONS ON C_STANDARD 99 + INSTALL_RPATH ${cf_rpath} + LINK_FLAGS ${cf_rpath} + PREFIX "" +) + + # 安装luaclib +install( + TARGETS ${cf_luaclib} + RUNTIME DESTINATION luaclib + LIBRARY DESTINATION luaclib +) \ No newline at end of file diff --git a/luaclib/Makefile b/luaclib/Makefile index 112f2121..f0608d5c 100644 --- a/luaclib/Makefile +++ b/luaclib/Makefile @@ -3,38 +3,48 @@ default : @echo "=======================================" @echo "Please use 'make build' command to build it.." + @echo "Please use 'make rebuild' command to build it.." @echo "Please use 'make clean' command to clean all." @echo "=======================================" -INCLUDES += -I/usr/local/include -LIBS += -L/usr/local/lib -L./ -L../ -# CFLAGS = -Wall -Os -fPIC --shared -I/usr/local/include -L./ -L../ -DJEMALLOC -ljemalloc -# CFLAGS = -Wall -Os -fPIC --shared -I/usr/local/include -L./ -L../ -DTCMALLOC -ltcmalloc -CFLAGS = -Wall -Os -fPIC --shared +INCLUDES += -I../src -I/usr/local/include +LIBS += -L./ -L../ -L/usr/local/lib +# CFLAGS = -Wall -O3 -fPIC --shared -DJEMALLOC -ljemalloc -Wl,-rpath,. -Wl,-rpath,.. -Wl,-rpath,/usr/local/lib +# CFLAGS = -Wall -O3 -fPIC --shared -DTCMALLOC -ltcmalloc -Wl,-rpath,. -Wl,-rpath,.. -Wl,-rpath,/usr/local/lib +CFLAGS = -Wall -O3 -fPIC --shared -Wno-unused-function -Wl,-rpath,. -Wl,-rpath,.. -Wl,-rpath,/usr/local/lib -build : - rm -rf *.o *.so - $(CC) -o sys.so src/lsys.c $(CFLAGS) $(INCLUDES) $(LIBS) -lcore - $(CC) -o tcp.so src/ltcp.c $(CFLAGS) $(INCLUDES) $(LIBS) -lssl -lcrypto -lcore - $(CC) -o udp.so src/ludp.c $(CFLAGS) $(INCLUDES) $(LIBS) -lcore - $(CC) -o timer.so src/ltimer.c $(CFLAGS) $(INCLUDES) $(LIBS) -lcore - $(CC) -o task.so src/ltask.c $(CFLAGS) $(INCLUDES) $(LIBS) -lcore - $(CC) -o crypt.so src/lcrypt.c $(CFLAGS) $(INCLUDES) $(LIBS) -lcore - ### 以下为预留第三方库编译位置 ### - cd src/lhttpparser && rm -rf *.o *.so && make build# 增加PicoHTTPParser库 - cd src/lcjson && rm -rf *.o *.so && make build # 增加cjson库 +internal : + @echo "CC - lsys" + @$(CC) -o sys.so src/lsys.c $(CFLAGS) $(INCLUDES) $(LIBS) -lcore + @echo "CC - lpack" + @$(CC) -o pack.so src/lpack.c $(CFLAGS) $(INCLUDES) $(LIBS) -lcore + @echo "CC - lchild" + @$(CC) -o child.so src/lchild.c $(CFLAGS) $(INCLUDES) $(LIBS) -lcore + @echo "CC - ludp" + @$(CC) -o udp.so src/ludp.c $(CFLAGS) $(INCLUDES) $(LIBS) -lcore + @echo "CC - ltask" + @$(CC) -o task.so src/ltask.c $(CFLAGS) $(INCLUDES) $(LIBS) -lcore + @echo "CC - ltimer" + @$(CC) -o timer.so src/ltimer.c $(CFLAGS) $(INCLUDES) $(LIBS) -lcore + @echo "CC - laio" + @$(CC) -o laio.so src/laio.c $(CFLAGS) $(INCLUDES) $(LIBS) -lcore -leio -lpthread + @echo "CC - ltcp" + @$(CC) -o tcp.so src/ltcp.c $(CFLAGS) $(INCLUDES) $(LIBS) -lcore -lssl -lcrypto -rebuild : - rm -rf *.o *.so - $(CC) -o sys.so src/lsys.c $(CFLAGS) $(INCLUDES) $(LIBS) -lcore - $(CC) -o tcp.so src/ltcp.c $(CFLAGS) $(INCLUDES) $(LIBS) -lssl -lcrypto -lcore - $(CC) -o udp.so src/ludp.c $(CFLAGS) $(INCLUDES) $(LIBS) -lcore - $(CC) -o timer.so src/ltimer.c $(CFLAGS) $(INCLUDES) $(LIBS) -lcore - $(CC) -o task.so src/ltask.c $(CFLAGS) $(INCLUDES) $(LIBS) -lcore - $(CC) -o crypt.so src/lcrypt.c $(CFLAGS) $(INCLUDES) $(LIBS) -lcore - ### 以下为预留第三方库编译位置 ### - cd src/lhttpparser && rm -rf *.o *.so && make rebuild # 增加PicoHTTPParser库 - cd src/lcjson && rm -rf *.o *.so && make rebuild # 增加cjson库 - -clean : - rm -rf *.so +3part : + @echo "CC - lz" + @$(MAKE) -C src/lz build + @echo "CC - lfs" + @$(MAKE) -C src/lfs build + @echo "CC - lpbc" + @$(MAKE) -C src/lpbc build + @echo "CC - lpeg" + @$(MAKE) -C src/lpeg build + @echo "CC - lcrypt" + @$(MAKE) -C src/lcrypt build + @echo "CC - lcjson" + @$(MAKE) -C src/lcjson build + @echo "CC - lmsgpack" + @$(MAKE) -C src/lmsgpack build + @echo "CC - lhttpparser" + @$(MAKE) -C src/lhttpparser build diff --git a/luaclib/src/laio.c b/luaclib/src/laio.c new file mode 100644 index 00000000..c67fdce3 --- /dev/null +++ b/luaclib/src/laio.c @@ -0,0 +1,773 @@ +#define LUA_LIB + +/* 工作线程最大使用堆栈 */ +#define EIO_STACKSIZE (1 << 16) + +/* 最小线程数量(数量多少与性能并无太大相关性, 只是为了配合事件驱动完成异步IO改造) */ +#define AIO_MAX_NTHREADS (8) + +#include +#include + +/* 初始化 */ +static int INITIALIZATION = 0; + +#define req_data_to_coroutine(req) (req->data) + +#define luaL_push_string_string(L, k, v) ({ lua_pushliteral(L, k); lua_pushstring(L, (v)); lua_rawset(L, -3); }) + +/* --- 文件(夹)类型 --- */ + +/* 内核宏 判断类型是否为目录 */ +#ifndef S_ISDIR + #define S_ISDIR(mode) (mode & _S_IFDIR) +#endif + +/* 内核宏 判断类型是否为常规文件 */ +#ifndef S_ISREG + #define S_ISREG(mode) (mode & _S_IFREG) +#endif + +/* 内核宏 判断是否为链接 */ +#ifndef S_ISLNK + #define S_ISLNK(mode) (0) +#endif + +/* 内核宏 判断是否为域套接字 */ +#ifndef S_ISSOCK + #define S_ISSOCK(mode) (0) +#endif + +/* 内核宏 判断是否为命名管道 */ +#ifndef S_ISFIFO + #define S_ISFIFO(mode) (0) +#endif + +/* 内核宏 判断是否为字符设备 */ +#ifndef S_ISCHR + #define S_ISCHR(mode) (mode & _S_IFCHR) +#endif + +/* 内核宏 判断是否为块设备 */ +#ifndef S_ISBLK + #define S_ISBLK(mode) (0) +#endif + +/* --- 文件(夹)类型 --- */ + +/* --- 文件(夹)权限 --- */ + +/* 拥有者是否有读权限 */ +#ifndef S_IRUSR + #define S_IRUSR (1 << 8) +#endif + +/* 拥有者是否有写权限 */ +#ifndef S_IWUSR + #define S_IWUSR (1 << 7) +#endif + +/* 拥有者是否有执行权限 */ +#ifndef S_IXUSR + #define S_IXUSR (1 << 6) +#endif + +/* 用户组是否有读权限 */ +#ifndef S_IRGRP + #define S_IRGRP (1 << 5) +#endif + +/* 用户组是否有写权限 */ +#ifndef S_IWGRP + #define S_IWGRP (1 << 4) +#endif + +/* 用户组是否有执行权限 */ +#ifndef S_IXGRP + #define S_IXGRP (1 << 3) +#endif + +/* 其他人是否有读权限 */ +#ifndef S_IROTH + #define S_IROTH (1 << 2) +#endif + +/* 其他人是否有写权限 */ +#ifndef S_IWOTH + #define S_IWOTH (1 << 1) +#endif + +/* 其他人是否有执行权限 */ +#ifndef S_IXOTH + #define S_IXOTH (1 << 0) +#endif + +/* --- 文件(夹)权限 --- */ + +static inline void luaL_push_string_integer(lua_State* L, const char* k, int v) { + lua_pushstring(L, k); + lua_pushinteger(L, v); + lua_rawset(L, -3); +} + +/* 文件类型转字符串 */ +static inline const char *mode2string (mode_t mode) { + if ( S_ISREG(mode) ) + return "file"; + else if ( S_ISDIR(mode) ) + return "directory"; + else if ( S_ISLNK(mode) ) + return "link"; + else if ( S_ISSOCK(mode) ) + return "socket"; + else if ( S_ISFIFO(mode) ) + return "named pipe"; + else if ( S_ISCHR(mode) ) + return "char device"; + else if ( S_ISBLK(mode) ) + return "block device"; + else + return "other"; +} + +/* 权限转字符串 */ +static inline const char *perm2string (mode_t mode, char* perms) { + int i; + for (i=0;i<9;i++) perms[i]='-'; + if (mode & S_IRUSR) perms[0] = 'r'; + if (mode & S_IWUSR) perms[1] = 'w'; + if (mode & S_IXUSR) perms[2] = 'x'; + if (mode & S_IRGRP) perms[3] = 'r'; + if (mode & S_IWGRP) perms[4] = 'w'; + if (mode & S_IXGRP) perms[5] = 'x'; + if (mode & S_IROTH) perms[6] = 'r'; + if (mode & S_IWOTH) perms[7] = 'w'; + if (mode & S_IXOTH) perms[8] = 'x'; + return perms; +} + +static inline void luaL_push_stat(lua_State *co, eio_req *req) { + char permissions[10] = "---------"; + struct stat *st = (struct stat *)req->ptr2; + luaL_push_string_string(co, "mode", mode2string(st->st_mode)); + luaL_push_string_integer(co, "dev", st->st_dev); + luaL_push_string_integer(co, "ino", st->st_ino); + luaL_push_string_integer(co, "nlink", st->st_nlink); + luaL_push_string_integer(co, "uid", st->st_uid); + luaL_push_string_integer(co, "gid", st->st_gid); + luaL_push_string_integer(co, "rdev", st->st_rdev); + luaL_push_string_integer(co, "access", st->st_atime); + luaL_push_string_integer(co, "change", st->st_ctime); + luaL_push_string_integer(co, "modification", st->st_mtime); + luaL_push_string_integer(co, "size", st->st_size); + luaL_push_string_integer(co, "blocks", st->st_blocks); + luaL_push_string_integer(co, "blksize", st->st_blksize); + luaL_push_string_string(co, "permissions", perm2string(st->st_mode, permissions)); +} + +/* AIO方法只需要简单返回状态时, 可以使用这个回调 */ +static int AIO_RESPONSE(eio_req* req) { + lua_State* co = (lua_State*)req_data_to_coroutine(req); + if (EIO_RESULT (req)){ + lua_pushboolean(co, 0); + lua_pushstring(co, strerror(req->errorno)); + }else{ + lua_pushboolean(co, 1); + } + if (LUA_OK != CO_RESUME(co, NULL, lua_gettop(co) - 1)) { + LOG("ERROR", lua_tostring(co, -1)); + } + return 0; +} + +/* AIO方法需要返回数值和fd时, 可以使用这个回调 */ +static int AIO_RESPONSE_FD(eio_req* req) { + lua_State* co = (lua_State*)req_data_to_coroutine(req); + if (EIO_RESULT (req) == -1){ + lua_pushboolean(co, 0); + lua_pushstring(co, strerror(req->errorno)); + }else{ + lua_pushinteger(co, EIO_RESULT (req)); + } + if (LUA_OK != CO_RESUME(co, NULL, lua_gettop(co) - 1)) { + LOG("ERROR", lua_tostring(co, -1)); + } + return 0; +} + +/* AIO调用读取数据则需要使用此回调 */ +static int AIO_RESPONSE_READ(eio_req* req) { + lua_State* co = (lua_State*)req_data_to_coroutine(req); + if (EIO_RESULT (req) == -1){ + lua_pushboolean(co, 0); + lua_pushstring(co, strerror(req->errorno)); + }else{ + lua_pushlstring(co, EIO_BUF (req), EIO_RESULT (req)); + lua_pushinteger(co, EIO_RESULT (req)); + } + if (LUA_OK != CO_RESUME(co, NULL, lua_gettop(co) - 1)) { + LOG("ERROR", lua_tostring(co, -1)); + } + return 0; +} + +/* AIO调用写入数据则需要使用此回调 */ +static int AIO_RESPONSE_WRITE(eio_req* req) { + lua_State* co = (lua_State*)req_data_to_coroutine(req); + if (EIO_RESULT (req) == -1){ + lua_pushboolean(co, 0); + lua_pushstring(co, strerror(req->errorno)); + }else{ + lua_pushinteger(co, EIO_RESULT (req)); + } + if (LUA_OK != CO_RESUME(co, NULL, lua_gettop(co) - 1)) { + LOG("ERROR", lua_tostring(co, -1)); + } + return 0; +} + +/* AIO调用stat是需要使用此回调 */ +static int AIO_RESPONSE_STAT(eio_req* req) { + lua_State* co = (lua_State*)req_data_to_coroutine(req); + if (EIO_RESULT (req) != -1){ + lua_createtable(co, 0, 16); + luaL_push_stat(co, req); + }else{ + lua_pushboolean(co, 0); + lua_pushstring(co, strerror(req->errorno)); + } + if (LUA_OK != CO_RESUME(co, NULL, lua_gettop(co) - 1)) { + LOG("ERROR", lua_tostring(co, -1)); + } + return 0; +} + +/* AIO调用需要循环检查文件名称必须使用此回调 */ +static int AIO_RESPONSE_DIR(eio_req* req) { + lua_State* co = (lua_State*)req_data_to_coroutine(req); + if (EIO_RESULT (req) >= 0){ + lua_createtable(co, EIO_RESULT (req), 0); + char *buf = (char *)EIO_BUF (req); + int i; + for (i = 0; i < EIO_RESULT (req); i++) { + lua_pushlstring(co, buf, strlen(buf)); + lua_rawseti(co, -2, i + 1); + buf += strlen(buf) + 1; + } + } + if (LUA_OK != CO_RESUME(co, NULL, lua_gettop(co) - 1)) { + LOG("ERROR", lua_tostring(co, -1)); + } + return 0; +} + +/* AIO调用需要循环检查文件名称必须使用此回调 */ +int AIO_RESPONSE_PATH(eio_req* req) { + lua_State* co = (lua_State*)req_data_to_coroutine(req); + if (EIO_RESULT (req) >= 0){ + /* 文档中说明:成功后 req->result 为 req->ptr2 指针长度*/ + lua_pushlstring(co, req->ptr2, EIO_RESULT (req)); + } + if (LUA_OK != CO_RESUME(co, NULL, lua_gettop(co) - 1)) { + LOG("ERROR", lua_tostring(co, -1)); + } + return 0; +} + +typedef struct aio_object{ + lua_State *L; + char *path; +}aio_object; + +static int AIO_RESPONSE_REMOVE(eio_req* req) { + aio_object* obj = (aio_object*)req_data_to_coroutine(req); + if (EIO_RESULT (req) == -1){ + lua_pushboolean(obj->L, 0); + lua_pushstring(obj->L, strerror(req->errorno)); + }else { + lua_pushboolean(obj->L, 1); + } + if (LUA_OK != CO_RESUME(obj->L, NULL, lua_gettop(obj->L) - 1)) { + LOG("ERROR", lua_tostring(obj->L, -1)); + } + return 0; +} + +static void AIO_REMOVE(eio_req *req) { + aio_object* obj = (aio_object*)req_data_to_coroutine(req); + if ((req->result = remove(obj->path)) == -1) { + req->errorno = errno; + } +} + + +typedef struct aio_file{ + lua_State *L; + FILE *f; +}aio_file; + +static int AIO_RESPONSE_FFLUSH(eio_req* req) { + aio_file* afile = (aio_file*)req_data_to_coroutine(req); + if (EIO_RESULT (req) == -1){ + lua_pushboolean(afile->L, 0); + lua_pushstring(afile->L, strerror(req->errorno)); + }else { + lua_pushboolean(afile->L, 1); + } + if (LUA_OK != CO_RESUME(afile->L, NULL, lua_gettop(afile->L) - 1)) { + LOG("ERROR", lua_tostring(afile->L, -1)); + } + return 0; +} + +static void AIO_FFLUSH(eio_req *req) { + aio_file* afile = (aio_file*)req_data_to_coroutine(req); + if ((req->result = fflush(afile->f)) == -1) { + req->errorno = errno; + } +} + +static int sp[2]; + +static void AIO_WANT_POLL(void) { + // printf("AIO_WANT_POLL Called. 工作线程ID为: %d\n", pthread_self()); + char event = '1'; + int wsize = write(sp[1], &event, 1); + (void)wsize; + } + +static void AIO_DONE_POLL(void) { + // printf("AIO_DONE_POLL Called. 主线程ID为: %d\n", pthread_self()); + char event = '2'; + int rsize = read(sp[0], &event, 1); + (void)rsize; +} + +static void AIO_EVENT(CORE_P_ core_io *io, int revents) { + if (revents & EV_ERROR) { + LOG("ERROR", "Recevied a core_io object internal error from libev."); + return ; + } + if (revents & EV_READ){ + /* 根据边缘触发规则, 只要还有请求则会不断检查 */ + while (eio_npending() && !eio_poll ()); + } +} + +static core_io io_watcher; + +static int pip_init() { + + /* 创建管道 */ + if (-1 == socketpair(AF_LOCAL, SOCK_STREAM, 0, sp)) + return -1; + + /* 非阻塞 */ + non_blocking(sp[0]); + + /* 将写socket设置为阻塞操作, 这样能防止问题进一步扩散 */ + non_blocking(sp[1]); + + memset(&io_watcher, 0x0, sizeof(core_io)); + + core_io_init(&io_watcher, AIO_EVENT, sp[0], EV_READ); + + core_io_start(CORE_LOOP_ &io_watcher); + + return 0; + +} + +static int aio_init() { + + /* 初始化eio内部数据 */ + if (eio_init(AIO_WANT_POLL, AIO_DONE_POLL)) + return -1; + + /* 创建并初始化通讯管道 */ + if (pip_init()) + return -1; + + /* 设置工作线程数量 */ + eio_set_min_parallel(AIO_MAX_NTHREADS); + eio_set_max_parallel(AIO_MAX_NTHREADS); + eio_set_max_idle(AIO_MAX_NTHREADS); + return 0; +} + +/* aio.open 打开一个文件(不存在则创建) */ +static int laio_open(lua_State* L) { + lua_State *t = lua_tothread(L, 1); + if (!t) + return luaL_error(L, "Invalid lua coroutine."); + + size_t path_size = 0; + const char *path = luaL_checklstring(L, 2, &path_size); + if (!path || path_size < 1){ + return luaL_error(L, "Invalid aio open [path]."); + } + + eio_open(path, O_CREAT | O_RDWR, 0755, EIO_PRI_DEFAULT, AIO_RESPONSE_FD, (void*)t); + + return 1; +} + +/* aio.create 创建一个文件(存在则返回错误) */ +static int laio_create(lua_State* L) { + lua_State *t = lua_tothread(L, 1); + if (!t) + return luaL_error(L, "Invalid lua coroutine."); + + size_t path_size = 0; + const char *path = luaL_checklstring(L, 2, &path_size); + if (!path || path_size < 1){ + return luaL_error(L, "Invalid aio create [path]."); + } + + eio_open(path, O_RDWR | O_CREAT | O_EXCL, 0755, EIO_PRI_DEFAULT, AIO_RESPONSE_FD, (void*)t); + + return 1; +} + +/* aio.read 从文件内读取数据 */ +static int laio_read(lua_State* L) { + lua_State *t = lua_tothread(L, 1); + if (!t) + return luaL_error(L, "Invalid lua coroutine."); + + /* 适用pread来完成offset控制读取. */ + eio_read(lua_tointeger(L, 2), 0, lua_tointeger(L, 3), lua_tointeger(L, 4), EIO_PRI_DEFAULT, AIO_RESPONSE_READ, (void*)t); + return 1; +} + +/* aio.write 写入数据到文件内 */ +static int laio_write(lua_State* L) { + lua_State *t = lua_tothread(L, 1); + if (!t) + return luaL_error(L, "Invalid lua coroutine."); + + int fd = lua_tointeger(L, 2); + + size_t buffer_size = 0; + const char *buffer = luaL_checklstring(L, 3, &buffer_size); + if (!buffer || buffer_size < 1){ + return luaL_error(L, "Invalid aio write [buffer]."); + } + + /* 适用write来完成追加操作, 同时也不允许单线程覆盖写入. */ + eio_write(fd, (void*)buffer, buffer_size, lua_tointeger(L, 4), EIO_PRI_DEFAULT, AIO_RESPONSE_WRITE, (void*)t); + return 1; +} + +/* aio.flush 将文件内存数据刷新到磁盘 */ +static int laio_flush(lua_State* L) { + lua_State *t = lua_tothread(L, 1); + if (!t) + return luaL_error(L, "Invalid lua coroutine."); + + eio_fsync(lua_tointeger(L, 2), EIO_PRI_DEFAULT, AIO_RESPONSE, (void*)t); + + return 1; +} + +/* aio.fflush 将文件内存数据刷新到磁盘 */ +static int laio_fflush(lua_State* L) { + + aio_file* afile = lua_newuserdata(L, sizeof(aio_file)); + + afile->L = lua_tothread(L, 1); + if (!afile->L) + return luaL_error(L, "Invalid lua coroutine."); + + luaL_Stream *p = luaL_checkudata(L, 2, LUA_FILEHANDLE); + if (!p || !p->closef) + luaL_error(L, "attempt to use a closed file"); + + afile->f = p->f; + + eio_custom(AIO_FFLUSH, EIO_PRI_DEFAULT, AIO_RESPONSE_FFLUSH, (void*)afile); + + return 1; +} + +/* aio.remove 删除一个文件或者文件夹 */ +static int laio_remove(lua_State* L) { + + aio_object* obj = lua_newuserdata(L, sizeof(aio_object)); + + obj->L = lua_tothread(L, 1); + if (!obj->L) + return luaL_error(L, "Invalid lua coroutine."); + + size_t path_size = 0; + obj->path = (char*)luaL_checklstring(L, 2, &path_size); + if (!obj->path || path_size < 1){ + return luaL_error(L, "Invalid aio truncate [path]."); + } + + eio_custom(AIO_REMOVE, EIO_PRI_DEFAULT, AIO_RESPONSE_REMOVE, (void*)obj); + + return 1; +} + +/* aio.close 关闭文件描述符 */ +static int laio_close(lua_State* L) { + lua_State *t = lua_tothread(L, 1); + if (!t) + return luaL_error(L, "Invalid lua coroutine."); + + eio_close(lua_tointeger(L, 2), EIO_PRI_DEFAULT, AIO_RESPONSE, (void*)t); + + return 1; +} + +static int laio_truncate(lua_State* L) { + lua_State *t = lua_tothread(L, 1); + if (!t) + return luaL_error(L, "Invalid lua coroutine."); + + size_t path_size = 0; + const char *path = luaL_checklstring(L, 2, &path_size); + if (!path || path_size < 1){ + return luaL_error(L, "Invalid aio truncate [path]."); + } + + eio_truncate(path, lua_tointeger(L, 3), EIO_PRI_DEFAULT, AIO_RESPONSE, (void*)t); + return 1; +} + +/* aio.realpath 将相对路径转换为绝对路径 */ +static int laio_readpath(lua_State* L) { + + lua_State *t = lua_tothread(L, 1); + if (!t) + return luaL_error(L, "Invalid lua coroutine."); + + size_t path_size = 0; + const char *path = luaL_checklstring(L, 2, &path_size); + if (!path || path_size < 1){ + return luaL_error(L, "Invalid aio readpath [path]."); + } + eio_realpath (path, EIO_PRI_DEFAULT, AIO_RESPONSE_PATH, (void*)t); + return 1; +} + +/* aio.readdir 读取文件夹内容 */ +static int laio_readdir(lua_State* L) { + + lua_State *t = lua_tothread(L, 1); + if (!t) + return luaL_error(L, "Invalid lua coroutine."); + + size_t path_size = 0; + const char *path = luaL_checklstring(L, 2, &path_size); + if (!path || path_size < 1){ + return luaL_error(L, "Invalid aio readdir [path]."); + } + + eio_readdir (path, EIO_READDIR_DIRS_FIRST, EIO_PRI_DEFAULT, AIO_RESPONSE_DIR, (void*)t); + return 1; +} + +/* aio.rename 重命名文件/文件夹 */ +static int laio_rename(lua_State* L) { + + lua_State *t = lua_tothread(L, 1); + if (!t) + return luaL_error(L, "Invalid lua coroutine."); + + size_t old_path_size = 0; + const char *old_path = luaL_checklstring(L, 2, &old_path_size); + if (!old_path || old_path_size < 1){ + return luaL_error(L, "Invalid aio rename [old path]."); + } + + size_t new_path_size = 0; + const char *new_path = luaL_checklstring(L, 3, &new_path_size); + if (!new_path || new_path_size < 1){ + return luaL_error(L, "Invalid aio rename [new path]."); + } + + eio_rename (old_path, new_path, EIO_PRI_DEFAULT, AIO_RESPONSE, (void*)t); + return 1; +} + + +/* aio.stat 获取文件/文件夹状态 */ +static int laio_stat(lua_State* L) { + + lua_State *t = lua_tothread(L, 1); + if (!t) + return luaL_error(L, "Invalid lua coroutine."); + + size_t path_size = 0; + const char *path = luaL_checklstring(L, 2, &path_size); + if (!path || path_size < 1){ + return luaL_error(L, "Invalid aio stat [path]."); + } + + eio_stat (path, EIO_PRI_DEFAULT, AIO_RESPONSE_STAT, (void*)t); + return 1; +} + +/* aio.mkdir 创建文件夹 */ +static int laio_mkdir(lua_State* L) { + + lua_State *t = lua_tothread(L, 1); + if (!t) + return luaL_error(L, "Invalid lua coroutine."); + + size_t path_size = 0; + const char *path = luaL_checklstring(L, 2, &path_size); + if (!path || path_size < 1){ + return luaL_error(L, "Invalid aio mkdir [path]."); + } + + eio_mkdir (path, 0755, EIO_PRI_DEFAULT, AIO_RESPONSE, (void*)t); + return 1; +} + +/* aio.rmdir 删除文件夹 */ +static int laio_rmdir(lua_State* L) { + + lua_State *t = lua_tothread(L, 1); + if (!t) + return luaL_error(L, "Invalid lua coroutine."); + + size_t path_size = 0; + const char *path = luaL_checklstring(L, 2, &path_size); + if (!path || path_size < 1){ + return luaL_error(L, "Invalid aio rmdir [path]."); + } + + eio_rmdir (path, EIO_PRI_DEFAULT, AIO_RESPONSE, (void*)t); + return 1; +} + +static void CHILD_CB (core_loop *loop, core_child *w, int revents){ + lua_State *co = (lua_State *)core_get_watcher_userdata(w); + if (co && (lua_status(co) == LUA_YIELD || lua_status(co) == LUA_OK)){ + lua_pushinteger(co, w->rstatus); + int status = CO_RESUME(co, NULL, 1); + if (status != LUA_YIELD && status != LUA_OK) + LOG("ERROR", lua_tostring(co, -1)); + } + // 停止继续监听 + core_child_stop(loop, w); +} + +// 实现异步`system`方法. +static pid_t laio_system(lua_State *L, const char* command, int pfd) { + pid_t pid = fork(); + if (pid) + return pid; + + // 完整独立进程会话ID + setsid(); + // 子进程需要设置为独立的输入输出管道; + (void)dup2(pfd, STDIN_FILENO); + (void)dup2(pfd, STDOUT_FILENO); + (void)dup2(pfd, STDERR_FILENO); + // 子进程需要进与父子进程的的上下文分离 + if (execl("/bin/sh", "sh", "-c", command, NULL)){ + int wsize = write(STDOUT_FILENO, strerror(errno), strlen(strerror(errno)));(void)wsize; + } + // 正常执行完毕是不会走到这里, 所以只能是执行失败. + exit(EXIT_FAILURE); +} + +// 自定义创建进程 +static int laio_popen(lua_State *L) { + size_t clen = 0; + const char *command = luaL_checklstring(L, 1, &clen); + if (!command || clen == 0) + return luaL_error(L, "Invalid command.\n"); + + // 协程回调 + lua_State *co = lua_tothread(L, 2); + + int std[] = { -1, -1 }; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, std) < 0) + return luaL_error(L, "Cand't create pipe.\n"); + + pid_t pid = laio_system(L, command, std[1]); + if (pid < 1) { + close(std[0]); close(std[1]); + return luaL_error(L, "Cand't create subprocess.\n"); + } + + lua_createtable(L, 0, 2); + // 记录子进程的`PID`. + lua_pushliteral(L, "pid"); + lua_pushinteger(L, pid); + lua_rawset(L, -3); + + // 记录双向通信用到的`管道`; + lua_pushliteral(L, "pipe"); + lua_createtable(L, 2, 0); + // 创建`stdin` + lua_pushinteger(L, std[0]); + lua_rawseti(L, -2, 1); + // 创建`stdout` + lua_pushinteger(L, std[1]); + lua_rawseti(L, -2, 2); + + lua_rawset(L, -3); + + lua_pushliteral(L, "child"); + core_child *w = lua_newuserdata(L, sizeof(core_child)); + lua_rawset(L, -3); + + // 监听`子进程`的退出事件 + core_set_watcher_userdata(w, co); + core_child_init(w, CHILD_CB, pid, 0); + core_child_start(core_default_loop(), w); + // 返回一个包含`pipe`、`core_child`指针的`table`. + return 1; +} + +// 根据子进程的`pid`杀死子进程 +static int laio_kill(lua_State *L){ + lua_Integer pid = luaL_checkinteger(L, 1); + if (getpid() != pid && pid != 1){ + if (kill(pid, luaL_optinteger(L, 2, SIGKILL))) + LOG("ERROR", strerror(errno)); + } + lua_pushboolean(L, 1); + return 1; +} + +LUAMOD_API int luaopen_laio(lua_State* L){ + // printf("主线程ID为: %d\n", pthread_self()); + luaL_checkversion(L); + if (INITIALIZATION) + return luaL_error(L, "aio error: Repeated initialization."); + + if (aio_init()) + return luaL_error(L, "aio init error."); + + INITIALIZATION = 1; + + luaL_Reg aio_libs[] = { + { "mkdir", laio_mkdir }, + { "rmdir", laio_rmdir }, + { "stat", laio_stat }, + { "rename", laio_rename }, + { "readdir", laio_readdir }, + { "readpath", laio_readpath }, + { "truncate", laio_truncate }, + { "open", laio_open }, + { "read", laio_read }, + { "write", laio_write }, + { "flush", laio_flush }, + { "close", laio_close }, + { "create", laio_create }, + { "fflush", laio_fflush }, + { "remove", laio_remove }, + { "popen", laio_popen }, + { "kill", laio_kill }, + {NULL, NULL}, + }; + luaL_newlib(L, aio_libs); + return 1; +} diff --git a/luaclib/src/lchild.c b/luaclib/src/lchild.c new file mode 100644 index 00000000..771d9fba --- /dev/null +++ b/luaclib/src/lchild.c @@ -0,0 +1,42 @@ +#define LUA_LIB + +#include + +static void CHILD_CB (core_loop *loop, ev_child *w, int revents){ + ev_child_stop(loop, w); + lua_State *co = core_get_watcher_userdata(w); + if (co) { + lua_pushinteger(co, w->rpid); + lua_pushinteger(co, w->rstatus); + int status = CO_RESUME(co, NULL, lua_status(co) == LUA_YIELD ? lua_gettop(co) : lua_gettop(co) - 1); + if (status != LUA_YIELD && status != LUA_OK){ + LOG("ERROR", lua_tostring(co, -1)); + } + } +} + +/* 监听子进程状态 */ +static int lwatch(lua_State *L){ + core_child* child = lua_newuserdata(L, sizeof(core_child)); + core_child_init(child, CHILD_CB, luaL_checkinteger(L, 1), 0); + core_child_start(core_default_loop(), child); + core_set_watcher_userdata(child, (void*)lua_tothread(L, 2)); + return 1; +} + +/* 发信号杀死子进程 */ +static int lkill(lua_State *L){ + kill(luaL_checkinteger(L, 1), SIGQUIT); + return 0; +} + +LUAMOD_API int luaopen_child(lua_State *L) { + luaL_checkversion(L); + luaL_Reg child_libs[] = { + {"watch", lwatch}, + {"kill", lkill}, + {NULL, NULL}, + }; + luaL_newlib(L, child_libs); + return 1; +} \ No newline at end of file diff --git a/luaclib/src/lcjson/Makefile b/luaclib/src/lcjson/Makefile index 9e7d67ec..067bbf71 100644 --- a/luaclib/src/lcjson/Makefile +++ b/luaclib/src/lcjson/Makefile @@ -3,30 +3,19 @@ default : @echo "=======================================" @echo "Please use 'make build' command to build it.." + @echo "Please use 'make rebuild' command to build it.." @echo "Please use 'make clean' command to clean all." @echo "=======================================" -# CFLAGS = -O3 -Wall -DNDEBUG -fPIC -DJEMALLOC -ljemalloc -# CFLAGS = -O3 -Wall -DNDEBUG -fPIC -DTCMALLOC -ltcmalloc -CFLAGS = -O3 -Wall -DNDEBUG -fPIC +# CFLAGS = -O3 -Wall -DJEMALLOC -shared -fPIC -ljemalloc +# CFLAGS = -O3 -Wall -DTCMALLOC -shared -fPIC -ltcmalloc +CFLAGS = -O3 -Wall -shared -fPIC CC = cc -INCLUDE = -I/usr/local/include -LIB = -L/usr/local/lib +INCLUDES = -I. -I../../../src -I/usr/local/include +LIBS = -L../ -L../../ -L../../../ -L/usr/local/lib +DLL = -lcore -fpconv.o: fpconv.c fpconv.h -strbuf.o: strbuf.c strbuf.h - -OBJS = fpconv.o strbuf.o - -build: $(OBJS) - $(CC) -o cjson.so lua_cjson.c $(OBJS) $(INCLUDE) $(LIB) $(CFLAGS) -shared -fPIC -lcore - mv cjson.so ../../ - -rebuild: $(OBJS) - $(CC) -o cjson.so lua_cjson.c $(OBJS) $(INCLUDE) $(LIB) $(CFLAGS) -shared -fPIC -lcore - rm -rf *.o - mv cjson.so ../../ - -clean: - rm -rf *.o *.so +build: + @$(CC) -o cjson.so lua_cjson.c fpconv.c strbuf.c $(INCLUDES) $(LIBS) $(CFLAGS) $(DLL) + @mv *.so ../../ diff --git a/luaclib/src/lcjson/fpconv.c b/luaclib/src/lcjson/fpconv.c index 63ea938d..2d2a5d36 100644 --- a/luaclib/src/lcjson/fpconv.c +++ b/luaclib/src/lcjson/fpconv.c @@ -28,7 +28,7 @@ * fpconv_* will around these issues with a translation buffer if required. */ -#include "../../../src/core.h" +#include #include "fpconv.h" @@ -127,7 +127,7 @@ double fpconv_strtod(const char *nptr, char **endptr) /* Duplicate number into buffer */ if (buflen >= FPCONV_G_FMT_BUFSIZE) { /* Handle unusually large numbers */ - buf = malloc(buflen + 1); + buf = xmalloc(buflen + 1); if (!buf) { fprintf(stderr, "Out of memory"); abort(); @@ -147,7 +147,7 @@ double fpconv_strtod(const char *nptr, char **endptr) value = strtod(buf, &endbuf); *endptr = (char *)&nptr[endbuf - buf]; if (buflen >= FPCONV_G_FMT_BUFSIZE) - free(buf); + xfree(buf); return value; } diff --git a/luaclib/src/lcjson/lua_cjson.c b/luaclib/src/lcjson/lua_cjson.c index 993bc3c9..4d5bb50e 100644 --- a/luaclib/src/lcjson/lua_cjson.c +++ b/luaclib/src/lcjson/lua_cjson.c @@ -36,7 +36,7 @@ * difficult to know object/array sizes ahead of time. */ -#include "../../../src/core.h" +#include #include "strbuf.h" #include "fpconv.h" @@ -46,7 +46,7 @@ #endif #ifndef CJSON_VERSION -#define CJSON_VERSION "2.1.0.6" +#define CJSON_VERSION "2.1.0.9" #endif #ifdef _MSC_VER @@ -75,6 +75,7 @@ #define DEFAULT_ENCODE_NUMBER_PRECISION 14 #define DEFAULT_ENCODE_EMPTY_TABLE_AS_OBJECT 1 #define DEFAULT_DECODE_ARRAY_WITH_ARRAY_MT 0 +#define DEFAULT_ENCODE_ESCAPE_FORWARD_SLASH 1 #ifdef DISABLE_INVALID_NUMBERS #undef DEFAULT_DECODE_INVALID_NUMBERS @@ -95,6 +96,10 @@ #define json_lightudata_mask(ludata) (ludata) #endif +#if LUA_VERSION_NUM > 501 +#define lua_objlen(L,i) lua_rawlen(L, (i)) +#endif + static const char * const *json_empty_array; static const char * const *json_array; @@ -149,6 +154,7 @@ typedef struct { int encode_number_precision; int encode_keep_buffer; int encode_empty_table_as_object; + int encode_escape_forward_slash; int decode_invalid_numbers; int decode_max_depth; @@ -400,6 +406,20 @@ static int json_cfg_decode_invalid_numbers(lua_State *l) return 1; } +static int json_cfg_encode_escape_forward_slash(lua_State *l) +{ + int ret; + json_config_t *cfg = json_arg_init(l, 1); + + ret = json_enum_option(l, 1, &cfg->encode_escape_forward_slash, NULL, 1); + if (cfg->encode_escape_forward_slash) { + char2escape['/'] = "\\/"; + } else { + char2escape['/'] = NULL; + } + return ret; +} + static int json_destroy_config(lua_State *l) { json_config_t *cfg; @@ -436,6 +456,7 @@ static void json_create_config(lua_State *l) cfg->encode_number_precision = DEFAULT_ENCODE_NUMBER_PRECISION; cfg->encode_empty_table_as_object = DEFAULT_ENCODE_EMPTY_TABLE_AS_OBJECT; cfg->decode_array_with_array_mt = DEFAULT_DECODE_ARRAY_WITH_ARRAY_MT; + cfg->encode_escape_forward_slash = DEFAULT_ENCODE_ESCAPE_FORWARD_SLASH; #if DEFAULT_ENCODE_KEEP_BUFFER > 0 strbuf_init(&cfg->encode_buf, 0); @@ -743,7 +764,7 @@ static void json_append_data(lua_State *l, json_config_t *cfg, } if (as_array) { - len = lua_rawlen(l, -1); + len = lua_objlen(l, -1); json_append_array(l, cfg, current_depth, json, len); } else { len = lua_array_length(l, cfg, json); @@ -1451,6 +1472,7 @@ static int lua_cjson_new(lua_State *l) { "encode_keep_buffer", json_cfg_encode_keep_buffer }, { "encode_invalid_numbers", json_cfg_encode_invalid_numbers }, { "decode_invalid_numbers", json_cfg_decode_invalid_numbers }, + { "encode_escape_forward_slash", json_cfg_encode_escape_forward_slash }, { "new", lua_cjson_new }, { NULL, NULL } }; @@ -1559,4 +1581,4 @@ int luaopen_cjson_safe(lua_State *l) } /* vi:ai et sw=4 ts=4: - */ + */ \ No newline at end of file diff --git a/luaclib/src/lcjson/strbuf.c b/luaclib/src/lcjson/strbuf.c index 764eeef5..09d5b711 100644 --- a/luaclib/src/lcjson/strbuf.c +++ b/luaclib/src/lcjson/strbuf.c @@ -22,7 +22,7 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include "../../../src/core.h" +#include #include "strbuf.h" @@ -55,7 +55,7 @@ void strbuf_init(strbuf_t *s, int len) s->reallocs = 0; s->debug = 0; - s->buf = malloc(size); + s->buf = xmalloc(size); if (!s->buf) die("Out of memory"); @@ -66,7 +66,7 @@ strbuf_t *strbuf_new(int len) { strbuf_t *s; - s = malloc(sizeof(strbuf_t)); + s = xmalloc(sizeof(strbuf_t)); if (!s) die("Out of memory"); @@ -103,11 +103,11 @@ void strbuf_free(strbuf_t *s) debug_stats(s); if (s->buf) { - free(s->buf); + xfree(s->buf); s->buf = NULL; } if (s->dynamic) - free(s); + xfree(s); } char *strbuf_free_to_string(strbuf_t *s, int *len) @@ -123,7 +123,7 @@ char *strbuf_free_to_string(strbuf_t *s, int *len) *len = s->length; if (s->dynamic) - free(s); + xfree(s); return buf; } @@ -170,7 +170,7 @@ void strbuf_resize(strbuf_t *s, int len) } s->size = newsize; - s->buf = realloc(s->buf, s->size); + s->buf = xrealloc(s->buf, s->size); if (!s->buf) die("Out of memory"); s->reallocs++; diff --git a/luaclib/src/lcjson/strbuf.h b/luaclib/src/lcjson/strbuf.h index 47bb4c2c..31b41bcc 100644 --- a/luaclib/src/lcjson/strbuf.h +++ b/luaclib/src/lcjson/strbuf.h @@ -22,7 +22,7 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include "../../../src/core.h" +#include /* Workaround for MSVC */ #ifdef _MSC_VER diff --git a/luaclib/src/lcrypt.c b/luaclib/src/lcrypt.c deleted file mode 100644 index b3fdcca2..00000000 --- a/luaclib/src/lcrypt.c +++ /dev/null @@ -1,1208 +0,0 @@ -/* --- This file is modified version from https://github.com/cloudwu/skynet --- The license is under the BSD license. --- Modified by Candy (merge sha1 and crypt into an file) -*/ - -#define LUA_LIB - -#include "../../src/core.h" - -#define SMALL_CHUNK 256 - -typedef struct { - uint32_t state[5]; - uint32_t count[2]; - uint8_t buffer[64]; -} SHA1_CTX; - -#define SHA1_DIGEST_SIZE 20 - - - -static void SHA1_Transform(uint32_t state[5], const uint8_t buffer[64]); - -#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) - -/* blk0() and blk() perform the initial expand. */ -/* I got the idea of expanding during the round function from SSLeay */ -/* FIXME: can we do this in an endian-proof way? */ -#ifdef WORDS_BIGENDIAN -#define blk0(i) block.l[i] -#else -#define blk0(i) (block.l[i] = (rol(block.l[i],24)&0xFF00FF00) \ - |(rol(block.l[i],8)&0x00FF00FF)) -#endif -#define blk(i) (block.l[i&15] = rol(block.l[(i+13)&15]^block.l[(i+8)&15] \ - ^block.l[(i+2)&15]^block.l[i&15],1)) - -/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ -#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); -#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); -#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); -#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); -#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); - - - -/* the eight DES S-boxes */ - -static uint32_t SB1[64] = { - 0x01010400, 0x00000000, 0x00010000, 0x01010404, - 0x01010004, 0x00010404, 0x00000004, 0x00010000, - 0x00000400, 0x01010400, 0x01010404, 0x00000400, - 0x01000404, 0x01010004, 0x01000000, 0x00000004, - 0x00000404, 0x01000400, 0x01000400, 0x00010400, - 0x00010400, 0x01010000, 0x01010000, 0x01000404, - 0x00010004, 0x01000004, 0x01000004, 0x00010004, - 0x00000000, 0x00000404, 0x00010404, 0x01000000, - 0x00010000, 0x01010404, 0x00000004, 0x01010000, - 0x01010400, 0x01000000, 0x01000000, 0x00000400, - 0x01010004, 0x00010000, 0x00010400, 0x01000004, - 0x00000400, 0x00000004, 0x01000404, 0x00010404, - 0x01010404, 0x00010004, 0x01010000, 0x01000404, - 0x01000004, 0x00000404, 0x00010404, 0x01010400, - 0x00000404, 0x01000400, 0x01000400, 0x00000000, - 0x00010004, 0x00010400, 0x00000000, 0x01010004 -}; - -static uint32_t SB2[64] = { - 0x80108020, 0x80008000, 0x00008000, 0x00108020, - 0x00100000, 0x00000020, 0x80100020, 0x80008020, - 0x80000020, 0x80108020, 0x80108000, 0x80000000, - 0x80008000, 0x00100000, 0x00000020, 0x80100020, - 0x00108000, 0x00100020, 0x80008020, 0x00000000, - 0x80000000, 0x00008000, 0x00108020, 0x80100000, - 0x00100020, 0x80000020, 0x00000000, 0x00108000, - 0x00008020, 0x80108000, 0x80100000, 0x00008020, - 0x00000000, 0x00108020, 0x80100020, 0x00100000, - 0x80008020, 0x80100000, 0x80108000, 0x00008000, - 0x80100000, 0x80008000, 0x00000020, 0x80108020, - 0x00108020, 0x00000020, 0x00008000, 0x80000000, - 0x00008020, 0x80108000, 0x00100000, 0x80000020, - 0x00100020, 0x80008020, 0x80000020, 0x00100020, - 0x00108000, 0x00000000, 0x80008000, 0x00008020, - 0x80000000, 0x80100020, 0x80108020, 0x00108000 -}; - -static uint32_t SB3[64] = { - 0x00000208, 0x08020200, 0x00000000, 0x08020008, - 0x08000200, 0x00000000, 0x00020208, 0x08000200, - 0x00020008, 0x08000008, 0x08000008, 0x00020000, - 0x08020208, 0x00020008, 0x08020000, 0x00000208, - 0x08000000, 0x00000008, 0x08020200, 0x00000200, - 0x00020200, 0x08020000, 0x08020008, 0x00020208, - 0x08000208, 0x00020200, 0x00020000, 0x08000208, - 0x00000008, 0x08020208, 0x00000200, 0x08000000, - 0x08020200, 0x08000000, 0x00020008, 0x00000208, - 0x00020000, 0x08020200, 0x08000200, 0x00000000, - 0x00000200, 0x00020008, 0x08020208, 0x08000200, - 0x08000008, 0x00000200, 0x00000000, 0x08020008, - 0x08000208, 0x00020000, 0x08000000, 0x08020208, - 0x00000008, 0x00020208, 0x00020200, 0x08000008, - 0x08020000, 0x08000208, 0x00000208, 0x08020000, - 0x00020208, 0x00000008, 0x08020008, 0x00020200 -}; - -static uint32_t SB4[64] = { - 0x00802001, 0x00002081, 0x00002081, 0x00000080, - 0x00802080, 0x00800081, 0x00800001, 0x00002001, - 0x00000000, 0x00802000, 0x00802000, 0x00802081, - 0x00000081, 0x00000000, 0x00800080, 0x00800001, - 0x00000001, 0x00002000, 0x00800000, 0x00802001, - 0x00000080, 0x00800000, 0x00002001, 0x00002080, - 0x00800081, 0x00000001, 0x00002080, 0x00800080, - 0x00002000, 0x00802080, 0x00802081, 0x00000081, - 0x00800080, 0x00800001, 0x00802000, 0x00802081, - 0x00000081, 0x00000000, 0x00000000, 0x00802000, - 0x00002080, 0x00800080, 0x00800081, 0x00000001, - 0x00802001, 0x00002081, 0x00002081, 0x00000080, - 0x00802081, 0x00000081, 0x00000001, 0x00002000, - 0x00800001, 0x00002001, 0x00802080, 0x00800081, - 0x00002001, 0x00002080, 0x00800000, 0x00802001, - 0x00000080, 0x00800000, 0x00002000, 0x00802080 -}; - -static uint32_t SB5[64] = { - 0x00000100, 0x02080100, 0x02080000, 0x42000100, - 0x00080000, 0x00000100, 0x40000000, 0x02080000, - 0x40080100, 0x00080000, 0x02000100, 0x40080100, - 0x42000100, 0x42080000, 0x00080100, 0x40000000, - 0x02000000, 0x40080000, 0x40080000, 0x00000000, - 0x40000100, 0x42080100, 0x42080100, 0x02000100, - 0x42080000, 0x40000100, 0x00000000, 0x42000000, - 0x02080100, 0x02000000, 0x42000000, 0x00080100, - 0x00080000, 0x42000100, 0x00000100, 0x02000000, - 0x40000000, 0x02080000, 0x42000100, 0x40080100, - 0x02000100, 0x40000000, 0x42080000, 0x02080100, - 0x40080100, 0x00000100, 0x02000000, 0x42080000, - 0x42080100, 0x00080100, 0x42000000, 0x42080100, - 0x02080000, 0x00000000, 0x40080000, 0x42000000, - 0x00080100, 0x02000100, 0x40000100, 0x00080000, - 0x00000000, 0x40080000, 0x02080100, 0x40000100 -}; - -static uint32_t SB6[64] = { - 0x20000010, 0x20400000, 0x00004000, 0x20404010, - 0x20400000, 0x00000010, 0x20404010, 0x00400000, - 0x20004000, 0x00404010, 0x00400000, 0x20000010, - 0x00400010, 0x20004000, 0x20000000, 0x00004010, - 0x00000000, 0x00400010, 0x20004010, 0x00004000, - 0x00404000, 0x20004010, 0x00000010, 0x20400010, - 0x20400010, 0x00000000, 0x00404010, 0x20404000, - 0x00004010, 0x00404000, 0x20404000, 0x20000000, - 0x20004000, 0x00000010, 0x20400010, 0x00404000, - 0x20404010, 0x00400000, 0x00004010, 0x20000010, - 0x00400000, 0x20004000, 0x20000000, 0x00004010, - 0x20000010, 0x20404010, 0x00404000, 0x20400000, - 0x00404010, 0x20404000, 0x00000000, 0x20400010, - 0x00000010, 0x00004000, 0x20400000, 0x00404010, - 0x00004000, 0x00400010, 0x20004010, 0x00000000, - 0x20404000, 0x20000000, 0x00400010, 0x20004010 -}; - -static uint32_t SB7[64] = { - 0x00200000, 0x04200002, 0x04000802, 0x00000000, - 0x00000800, 0x04000802, 0x00200802, 0x04200800, - 0x04200802, 0x00200000, 0x00000000, 0x04000002, - 0x00000002, 0x04000000, 0x04200002, 0x00000802, - 0x04000800, 0x00200802, 0x00200002, 0x04000800, - 0x04000002, 0x04200000, 0x04200800, 0x00200002, - 0x04200000, 0x00000800, 0x00000802, 0x04200802, - 0x00200800, 0x00000002, 0x04000000, 0x00200800, - 0x04000000, 0x00200800, 0x00200000, 0x04000802, - 0x04000802, 0x04200002, 0x04200002, 0x00000002, - 0x00200002, 0x04000000, 0x04000800, 0x00200000, - 0x04200800, 0x00000802, 0x00200802, 0x04200800, - 0x00000802, 0x04000002, 0x04200802, 0x04200000, - 0x00200800, 0x00000000, 0x00000002, 0x04200802, - 0x00000000, 0x00200802, 0x04200000, 0x00000800, - 0x04000002, 0x04000800, 0x00000800, 0x00200002 -}; - -static uint32_t SB8[64] = { - 0x10001040, 0x00001000, 0x00040000, 0x10041040, - 0x10000000, 0x10001040, 0x00000040, 0x10000000, - 0x00040040, 0x10040000, 0x10041040, 0x00041000, - 0x10041000, 0x00041040, 0x00001000, 0x00000040, - 0x10040000, 0x10000040, 0x10001000, 0x00001040, - 0x00041000, 0x00040040, 0x10040040, 0x10041000, - 0x00001040, 0x00000000, 0x00000000, 0x10040040, - 0x10000040, 0x10001000, 0x00041040, 0x00040000, - 0x00041040, 0x00040000, 0x10041000, 0x00001000, - 0x00000040, 0x10040040, 0x00001000, 0x00041040, - 0x10001000, 0x00000040, 0x10000040, 0x10040000, - 0x10040040, 0x10000000, 0x00040000, 0x10001040, - 0x00000000, 0x10041040, 0x00040040, 0x10000040, - 0x10040000, 0x10001000, 0x10001040, 0x00000000, - 0x10041040, 0x00041000, 0x00041000, 0x00001040, - 0x00001040, 0x00040040, 0x10000000, 0x10041000 -}; - -/* PC1: left and right halves bit-swap */ - -static uint32_t LHs[16] = { - 0x00000000, 0x00000001, 0x00000100, 0x00000101, - 0x00010000, 0x00010001, 0x00010100, 0x00010101, - 0x01000000, 0x01000001, 0x01000100, 0x01000101, - 0x01010000, 0x01010001, 0x01010100, 0x01010101 -}; - -static uint32_t RHs[16] = { - 0x00000000, 0x01000000, 0x00010000, 0x01010000, - 0x00000100, 0x01000100, 0x00010100, 0x01010100, - 0x00000001, 0x01000001, 0x00010001, 0x01010001, - 0x00000101, 0x01000101, 0x00010101, 0x01010101, -}; - -/* platform-independant 32-bit integer manipulation macros */ - -#define GET_UINT32(n,b,i) \ -{ \ - (n) = ( (uint32_t) (b)[(i) ] << 24 ) \ - | ( (uint32_t) (b)[(i) + 1] << 16 ) \ - | ( (uint32_t) (b)[(i) + 2] << 8 ) \ - | ( (uint32_t) (b)[(i) + 3] ); \ -} - -#define PUT_UINT32(n,b,i) \ -{ \ - (b)[(i) ] = (uint8_t) ( (n) >> 24 ); \ - (b)[(i) + 1] = (uint8_t) ( (n) >> 16 ); \ - (b)[(i) + 2] = (uint8_t) ( (n) >> 8 ); \ - (b)[(i) + 3] = (uint8_t) ( (n) ); \ -} - -/* Initial Permutation macro */ - -#define DES_IP(X,Y) \ -{ \ - T = ((X >> 4) ^ Y) & 0x0F0F0F0F; Y ^= T; X ^= (T << 4); \ - T = ((X >> 16) ^ Y) & 0x0000FFFF; Y ^= T; X ^= (T << 16); \ - T = ((Y >> 2) ^ X) & 0x33333333; X ^= T; Y ^= (T << 2); \ - T = ((Y >> 8) ^ X) & 0x00FF00FF; X ^= T; Y ^= (T << 8); \ - Y = ((Y << 1) | (Y >> 31)) & 0xFFFFFFFF; \ - T = (X ^ Y) & 0xAAAAAAAA; Y ^= T; X ^= T; \ - X = ((X << 1) | (X >> 31)) & 0xFFFFFFFF; \ -} - -/* Final Permutation macro */ - -#define DES_FP(X,Y) \ -{ \ - X = ((X << 31) | (X >> 1)) & 0xFFFFFFFF; \ - T = (X ^ Y) & 0xAAAAAAAA; X ^= T; Y ^= T; \ - Y = ((Y << 31) | (Y >> 1)) & 0xFFFFFFFF; \ - T = ((Y >> 8) ^ X) & 0x00FF00FF; X ^= T; Y ^= (T << 8); \ - T = ((Y >> 2) ^ X) & 0x33333333; X ^= T; Y ^= (T << 2); \ - T = ((X >> 16) ^ Y) & 0x0000FFFF; Y ^= T; X ^= (T << 16); \ - T = ((X >> 4) ^ Y) & 0x0F0F0F0F; Y ^= T; X ^= (T << 4); \ -} - -/* DES round macro */ - -#define DES_ROUND(X,Y) \ -{ \ - T = *SK++ ^ X; \ - Y ^= SB8[ (T ) & 0x3F ] ^ \ - SB6[ (T >> 8) & 0x3F ] ^ \ - SB4[ (T >> 16) & 0x3F ] ^ \ - SB2[ (T >> 24) & 0x3F ]; \ - \ - T = *SK++ ^ ((X << 28) | (X >> 4)); \ - Y ^= SB7[ (T ) & 0x3F ] ^ \ - SB5[ (T >> 8) & 0x3F ] ^ \ - SB3[ (T >> 16) & 0x3F ] ^ \ - SB1[ (T >> 24) & 0x3F ]; \ -} - -/* DES key schedule */ - - - - -/* Hash a single 512-bit block. This is the core of the algorithm. */ -static void SHA1_Transform(uint32_t state[5], const uint8_t buffer[64]) -{ - uint32_t a, b, c, d, e; - typedef union { - uint8_t c[64]; - uint32_t l[16]; - } CHAR64LONG16; - CHAR64LONG16 block; - - memcpy(&block, buffer, 64); - - /* Copy context->state[] to working vars */ - a = state[0]; - b = state[1]; - c = state[2]; - d = state[3]; - e = state[4]; - - /* 4 rounds of 20 operations each. Loop unrolled. */ - R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); - R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); - R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); - R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); - R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); - R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); - R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); - R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); - R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); - R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); - R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); - R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); - R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); - R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); - R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); - R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); - R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); - R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); - R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); - R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); - - /* Add the working vars back into context.state[] */ - state[0] += a; - state[1] += b; - state[2] += c; - state[3] += d; - state[4] += e; - - /* Wipe variables */ - a = b = c = d = e = 0; -} - - -/* SHA1Init - Initialize new context */ -static void sat_SHA1_Init(SHA1_CTX* context) -{ - /* SHA1 initialization constants */ - context->state[0] = 0x67452301; - context->state[1] = 0xEFCDAB89; - context->state[2] = 0x98BADCFE; - context->state[3] = 0x10325476; - context->state[4] = 0xC3D2E1F0; - context->count[0] = context->count[1] = 0; -} - - -/* Run your data through this. */ -static void sat_SHA1_Update(SHA1_CTX* context, const uint8_t* data, const size_t len) -{ - size_t i, j; - -#ifdef VERBOSE - SHAPrintContext(context, "before"); -#endif - - j = (context->count[0] >> 3) & 63; - if ((context->count[0] += len << 3) < (len << 3)) context->count[1]++; - context->count[1] += (len >> 29); - if ((j + len) > 63) { - memcpy(&context->buffer[j], data, (i = 64-j)); - SHA1_Transform(context->state, context->buffer); - for ( ; i + 63 < len; i += 64) { - SHA1_Transform(context->state, data + i); - } - j = 0; - } - else i = 0; - memcpy(&context->buffer[j], &data[i], len - i); - -#ifdef VERBOSE - SHAPrintContext(context, "after "); -#endif -} - - -/* Add padding and return the message digest. */ -static void sat_SHA1_Final(SHA1_CTX* context, uint8_t digest[SHA1_DIGEST_SIZE]) -{ - uint32_t i; - uint8_t finalcount[8]; - - for (i = 0; i < 8; i++) { - finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] - >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ - } - sat_SHA1_Update(context, (uint8_t *)"\200", 1); - while ((context->count[0] & 504) != 448) { - sat_SHA1_Update(context, (uint8_t *)"\0", 1); - } - sat_SHA1_Update(context, finalcount, 8); /* Should cause a SHA1_Transform() */ - for (i = 0; i < SHA1_DIGEST_SIZE; i++) { - digest[i] = (uint8_t) - ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); - } - - /* Wipe variables */ - i = 0; - memset(context->buffer, 0, 64); - memset(context->state, 0, 20); - memset(context->count, 0, 8); - memset(finalcount, 0, 8); /* SWR */ -} - -int -lsha1(lua_State *L) { - size_t sz = 0; - const uint8_t * buffer = (const uint8_t *)luaL_checklstring(L, 1, &sz); - uint8_t digest[SHA1_DIGEST_SIZE]; - SHA1_CTX ctx; - sat_SHA1_Init(&ctx); - sat_SHA1_Update(&ctx, buffer, sz); - sat_SHA1_Final(&ctx, digest); - lua_pushlstring(L, (const char *)digest, SHA1_DIGEST_SIZE); - - return 1; -} - -#define BLOCKSIZE 64 - -static inline void -xor_key(uint8_t key[BLOCKSIZE], uint32_t xor) { - int i; - for (i=0;i BLOCKSIZE) { - SHA1_CTX ctx; - sat_SHA1_Init(&ctx); - sat_SHA1_Update(&ctx, key, key_sz); - sat_SHA1_Final(&ctx, rkey); - key_sz = SHA1_DIGEST_SIZE; - } else { - memcpy(rkey, key, key_sz); - } - - xor_key(rkey, 0x5c5c5c5c); - sat_SHA1_Init(&ctx1); - sat_SHA1_Update(&ctx1, rkey, BLOCKSIZE); - - xor_key(rkey, 0x5c5c5c5c ^ 0x36363636); - sat_SHA1_Init(&ctx2); - sat_SHA1_Update(&ctx2, rkey, BLOCKSIZE); - sat_SHA1_Update(&ctx2, text, text_sz); - sat_SHA1_Final(&ctx2, digest2); - - sat_SHA1_Update(&ctx1, digest2, SHA1_DIGEST_SIZE); - sat_SHA1_Final(&ctx1, digest1); - - lua_pushlstring(L, (const char *)digest1, SHA1_DIGEST_SIZE); - - return 1; -} - -static void -des_main_ks( uint32_t SK[32], const uint8_t key[8] ) { - int i; - uint32_t X, Y, T; - - GET_UINT32( X, key, 0 ); - GET_UINT32( Y, key, 4 ); - - /* Permuted Choice 1 */ - - T = ((Y >> 4) ^ X) & 0x0F0F0F0F; X ^= T; Y ^= (T << 4); - T = ((Y ) ^ X) & 0x10101010; X ^= T; Y ^= (T ); - - X = (LHs[ (X ) & 0xF] << 3) | (LHs[ (X >> 8) & 0xF ] << 2) - | (LHs[ (X >> 16) & 0xF] << 1) | (LHs[ (X >> 24) & 0xF ] ) - | (LHs[ (X >> 5) & 0xF] << 7) | (LHs[ (X >> 13) & 0xF ] << 6) - | (LHs[ (X >> 21) & 0xF] << 5) | (LHs[ (X >> 29) & 0xF ] << 4); - - Y = (RHs[ (Y >> 1) & 0xF] << 3) | (RHs[ (Y >> 9) & 0xF ] << 2) - | (RHs[ (Y >> 17) & 0xF] << 1) | (RHs[ (Y >> 25) & 0xF ] ) - | (RHs[ (Y >> 4) & 0xF] << 7) | (RHs[ (Y >> 12) & 0xF ] << 6) - | (RHs[ (Y >> 20) & 0xF] << 5) | (RHs[ (Y >> 28) & 0xF ] << 4); - - X &= 0x0FFFFFFF; - Y &= 0x0FFFFFFF; - - /* calculate subkeys */ - - for( i = 0; i < 16; i++ ) - { - if( i < 2 || i == 8 || i == 15 ) - { - X = ((X << 1) | (X >> 27)) & 0x0FFFFFFF; - Y = ((Y << 1) | (Y >> 27)) & 0x0FFFFFFF; - } - else - { - X = ((X << 2) | (X >> 26)) & 0x0FFFFFFF; - Y = ((Y << 2) | (Y >> 26)) & 0x0FFFFFFF; - } - - *SK++ = ((X << 4) & 0x24000000) | ((X << 28) & 0x10000000) - | ((X << 14) & 0x08000000) | ((X << 18) & 0x02080000) - | ((X << 6) & 0x01000000) | ((X << 9) & 0x00200000) - | ((X >> 1) & 0x00100000) | ((X << 10) & 0x00040000) - | ((X << 2) & 0x00020000) | ((X >> 10) & 0x00010000) - | ((Y >> 13) & 0x00002000) | ((Y >> 4) & 0x00001000) - | ((Y << 6) & 0x00000800) | ((Y >> 1) & 0x00000400) - | ((Y >> 14) & 0x00000200) | ((Y ) & 0x00000100) - | ((Y >> 5) & 0x00000020) | ((Y >> 10) & 0x00000010) - | ((Y >> 3) & 0x00000008) | ((Y >> 18) & 0x00000004) - | ((Y >> 26) & 0x00000002) | ((Y >> 24) & 0x00000001); - - *SK++ = ((X << 15) & 0x20000000) | ((X << 17) & 0x10000000) - | ((X << 10) & 0x08000000) | ((X << 22) & 0x04000000) - | ((X >> 2) & 0x02000000) | ((X << 1) & 0x01000000) - | ((X << 16) & 0x00200000) | ((X << 11) & 0x00100000) - | ((X << 3) & 0x00080000) | ((X >> 6) & 0x00040000) - | ((X << 15) & 0x00020000) | ((X >> 4) & 0x00010000) - | ((Y >> 2) & 0x00002000) | ((Y << 8) & 0x00001000) - | ((Y >> 14) & 0x00000808) | ((Y >> 9) & 0x00000400) - | ((Y ) & 0x00000200) | ((Y << 7) & 0x00000100) - | ((Y >> 7) & 0x00000020) | ((Y >> 3) & 0x00000011) - | ((Y << 2) & 0x00000004) | ((Y >> 21) & 0x00000002); - } -} - -/* DES 64-bit block encryption/decryption */ - -static void -des_crypt( const uint32_t SK[32], const uint8_t input[8], uint8_t output[8] ) { - uint32_t X, Y, T; - - GET_UINT32( X, input, 0 ); - GET_UINT32( Y, input, 4 ); - - DES_IP( X, Y ); - - DES_ROUND( Y, X ); DES_ROUND( X, Y ); - DES_ROUND( Y, X ); DES_ROUND( X, Y ); - DES_ROUND( Y, X ); DES_ROUND( X, Y ); - DES_ROUND( Y, X ); DES_ROUND( X, Y ); - DES_ROUND( Y, X ); DES_ROUND( X, Y ); - DES_ROUND( Y, X ); DES_ROUND( X, Y ); - DES_ROUND( Y, X ); DES_ROUND( X, Y ); - DES_ROUND( Y, X ); DES_ROUND( X, Y ); - - DES_FP( Y, X ); - - PUT_UINT32( Y, output, 0 ); - PUT_UINT32( X, output, 4 ); -} - -static int -lrandomkey(lua_State *L) { - char tmp[8]; - int i; - char x = 0; - for (i=0;i<8;i++) { - tmp[i] = random() & 0xff; - x ^= tmp[i]; - } - if (x==0) { - tmp[0] |= 1; // avoid 0 - } - lua_pushlstring(L, tmp, 8); - return 1; -} - -static void -des_key(lua_State *L, uint32_t SK[32]) { - size_t keysz = 0; - const void * key = luaL_checklstring(L, 1, &keysz); - if (keysz != 8) { - luaL_error(L, "Invalid key size %d, need 8 bytes", (int)keysz); - } - des_main_ks(SK, key); -} - -static int -ldesencode(lua_State *L) { - uint32_t SK[32]; - des_key(L, SK); - - size_t textsz = 0; - const uint8_t * text = (const uint8_t *)luaL_checklstring(L, 2, &textsz); - size_t chunksz = (textsz + 8) & ~7; - uint8_t tmp[SMALL_CHUNK]; - uint8_t *buffer = tmp; - if (chunksz > SMALL_CHUNK) { - buffer = lua_newuserdata(L, chunksz); - } - int i; - for (i=0;i<(int)textsz-7;i+=8) { - des_crypt(SK, text+i, buffer+i); - } - int bytes = textsz - i; - uint8_t tail[8]; - int j; - for (j=0;j<8;j++) { - if (j < bytes) { - tail[j] = text[i+j]; - } else if (j==bytes) { - tail[j] = 0x80; - } else { - tail[j] = 0; - } - } - des_crypt(SK, tail, buffer+i); - lua_pushlstring(L, (const char *)buffer, chunksz); - - return 1; -} - -static int -ldesdecode(lua_State *L) { - uint32_t ESK[32]; - des_key(L, ESK); - uint32_t SK[32]; - int i; - for( i = 0; i < 32; i += 2 ) { - SK[i] = ESK[30 - i]; - SK[i + 1] = ESK[31 - i]; - } - size_t textsz = 0; - const uint8_t *text = (const uint8_t *)luaL_checklstring(L, 2, &textsz); - if ((textsz & 7) || textsz == 0) { - return luaL_error(L, "Invalid des crypt text length %d", (int)textsz); - } - uint8_t tmp[SMALL_CHUNK]; - uint8_t *buffer = tmp; - if (textsz > SMALL_CHUNK) { - buffer = lua_newuserdata(L, textsz); - } - for (i=0;i=textsz-8;i--) { - if (buffer[i] == 0) { - padding++; - } else if (buffer[i] == 0x80) { - break; - } else { - return luaL_error(L, "Invalid des crypt text"); - } - } - if (padding > 8) { - return luaL_error(L, "Invalid des crypt text"); - } - lua_pushlstring(L, (const char *)buffer, textsz - padding); - return 1; -} - - -static void -Hash(const char * str, int sz, uint8_t key[8]) { - uint32_t djb_hash = 5381L; - uint32_t js_hash = 1315423911L; - - int i; - for (i=0;i> 2)); - } - - key[0] = djb_hash & 0xff; - key[1] = (djb_hash >> 8) & 0xff; - key[2] = (djb_hash >> 16) & 0xff; - key[3] = (djb_hash >> 24) & 0xff; - - key[4] = js_hash & 0xff; - key[5] = (js_hash >> 8) & 0xff; - key[6] = (js_hash >> 16) & 0xff; - key[7] = (js_hash >> 24) & 0xff; -} - -static int -lhashkey(lua_State *L) { - size_t sz = 0; - const char * key = luaL_checklstring(L, 1, &sz); - uint8_t realkey[8]; - Hash(key,(int)sz,realkey); - lua_pushlstring(L, (const char *)realkey, 8); - return 1; -} - -static int -ltohex(lua_State *L) { - static char hex[] = "0123456789abcdef"; - size_t sz = 0; - const uint8_t * text = (const uint8_t *)luaL_checklstring(L, 1, &sz); - char tmp[SMALL_CHUNK]; - char *buffer = tmp; - if (sz > SMALL_CHUNK/2) { - buffer = lua_newuserdata(L, sz * 2); - } - int i; - for (i=0;i> 4]; - buffer[i*2+1] = hex[text[i] & 0xf]; - } - lua_pushlstring(L, buffer, sz * 2); - return 1; -} - -#define HEX(v,c) { char tmp = (char) c; if (tmp >= '0' && tmp <= '9') { v = tmp-'0'; } else { v = tmp - 'a' + 10; } } - -static int -lfromhex(lua_State *L) { - size_t sz = 0; - const char * text = luaL_checklstring(L, 1, &sz); - if (sz & 1) { - return luaL_error(L, "Invalid hex text size %d", (int)sz); - } - char tmp[SMALL_CHUNK]; - char *buffer = tmp; - if (sz > SMALL_CHUNK*2) { - buffer = lua_newuserdata(L, sz / 2); - } - int i; - for (i=0;i 16 || low > 16) { - return luaL_error(L, "Invalid hex text", text); - } - buffer[i/2] = hi<<4 | low; - } - lua_pushlstring(L, buffer, i/2); - return 1; -} - -// Constants are the integer part of the sines of integers (in radians) * 2^32. -static const uint32_t k[64] = { -0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee , -0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501 , -0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be , -0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821 , -0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa , -0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8 , -0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed , -0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a , -0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c , -0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70 , -0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05 , -0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665 , -0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039 , -0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1 , -0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1 , -0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 }; - -// r specifies the per-round shift amounts -static const uint32_t r[] = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, - 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, - 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, - 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}; - -// leftrotate function definition -#define LEFTROTATE(x, c) (((x) << (c)) | ((x) >> (32 - (c)))) - -static void -digest_md5(uint32_t w[16], uint32_t result[4]) { - uint32_t a, b, c, d, f, g, temp; - int i; - - a = 0x67452301u; - b = 0xefcdab89u; - c = 0x98badcfeu; - d = 0x10325476u; - - for(i = 0; i<64; i++) { - if (i < 16) { - f = (b & c) | ((~b) & d); - g = i; - } else if (i < 32) { - f = (d & b) | ((~d) & c); - g = (5*i + 1) % 16; - } else if (i < 48) { - f = b ^ c ^ d; - g = (3*i + 5) % 16; - } else { - f = c ^ (b | (~d)); - g = (7*i) % 16; - } - - temp = d; - d = c; - c = b; - b = b + LEFTROTATE((a + f + k[i] + w[g]), r[i]); - a = temp; - } - - result[0] = a; - result[1] = b; - result[2] = c; - result[3] = d; -} - -// hmac64 use md5 algorithm without padding, and the result is (c^d .. a^b) -static void -hmac(uint32_t x[2], uint32_t y[2], uint32_t result[2]) { - uint32_t w[16]; - uint32_t r[4]; - int i; - for (i=0;i<16;i+=4) { - w[i] = x[1]; - w[i+1] = x[0]; - w[i+2] = y[1]; - w[i+3] = y[0]; - } - - digest_md5(w,r); - - result[0] = r[2]^r[3]; - result[1] = r[0]^r[1]; -} - -static void -hmac_md5(uint32_t x[2], uint32_t y[2], uint32_t result[2]) { - uint32_t w[16]; - uint32_t r[4]; - int i; - for (i=0;i<12;i+=4) { - w[i] = x[0]; - w[i+1] = x[1]; - w[i+2] = y[0]; - w[i+3] = y[1]; - } - - w[12] = 0x80; - w[13] = 0; - w[14] = 384; - w[15] = 0; - - digest_md5(w,r); - - result[0] = (r[0] + 0x67452301u) ^ (r[2] + 0x98badcfeu); - result[1] = (r[1] + 0xefcdab89u) ^ (r[3] + 0x10325476u); -} - -static void -read64(lua_State *L, uint32_t xx[2], uint32_t yy[2]) { - size_t sz = 0; - const uint8_t *x = (const uint8_t *)luaL_checklstring(L, 1, &sz); - if (sz != 8) { - luaL_error(L, "Invalid uint64 x"); - } - const uint8_t *y = (const uint8_t *)luaL_checklstring(L, 2, &sz); - if (sz != 8) { - luaL_error(L, "Invalid uint64 y"); - } - xx[0] = x[0] | x[1]<<8 | x[2]<<16 | x[3]<<24; - xx[1] = x[4] | x[5]<<8 | x[6]<<16 | x[7]<<24; - yy[0] = y[0] | y[1]<<8 | y[2]<<16 | y[3]<<24; - yy[1] = y[4] | y[5]<<8 | y[6]<<16 | y[7]<<24; -} - -static int -pushqword(lua_State *L, uint32_t result[2]) { - uint8_t tmp[8]; - tmp[0] = result[0] & 0xff; - tmp[1] = (result[0] >> 8 )& 0xff; - tmp[2] = (result[0] >> 16 )& 0xff; - tmp[3] = (result[0] >> 24 )& 0xff; - tmp[4] = result[1] & 0xff; - tmp[5] = (result[1] >> 8 )& 0xff; - tmp[6] = (result[1] >> 16 )& 0xff; - tmp[7] = (result[1] >> 24 )& 0xff; - - lua_pushlstring(L, (const char *)tmp, 8); - return 1; -} - -static int -lhmac64(lua_State *L) { - uint32_t x[2], y[2]; - read64(L, x, y); - uint32_t result[2]; - hmac(x,y,result); - return pushqword(L, result); -} - -/* - h1 = crypt.hmac64_md5(a,b) - m = md5.sum((a..b):rep(3)) - h2 = crypt.xor_str(m:sub(1,8), m:sub(9,16)) - assert(h1 == h2) - */ -static int -lhmac64_md5(lua_State *L) { - uint32_t x[2], y[2]; - read64(L, x, y); - uint32_t result[2]; - hmac_md5(x,y,result); - return pushqword(L, result); -} - -/* - 8bytes key - string text - */ -static int -lhmac_hash(lua_State *L) { - uint32_t key[2]; - size_t sz = 0; - const uint8_t *x = (const uint8_t *)luaL_checklstring(L, 1, &sz); - if (sz != 8) { - luaL_error(L, "Invalid uint64 key"); - } - key[0] = x[0] | x[1]<<8 | x[2]<<16 | x[3]<<24; - key[1] = x[4] | x[5]<<8 | x[6]<<16 | x[7]<<24; - const char * text = luaL_checklstring(L, 2, &sz); - uint8_t h[8]; - Hash(text,(int)sz,h); - uint32_t htext[2]; - htext[0] = h[0] | h[1]<<8 | h[2]<<16 | h[3]<<24; - htext[1] = h[4] | h[5]<<8 | h[6]<<16 | h[7]<<24; - uint32_t result[2]; - hmac(htext,key,result); - return pushqword(L, result); -} - -// powmodp64 for DH-key exchange - -// The biggest 64bit prime -#define P 0xffffffffffffffc5ull - -static inline uint64_t -mul_mod_p(uint64_t a, uint64_t b) { - uint64_t m = 0; - while(b) { - if(b&1) { - uint64_t t = P-a; - if ( m >= t) { - m -= t; - } else { - m += a; - } - } - if (a >= P - a) { - a = a * 2 - P; - } else { - a = a * 2; - } - b>>=1; - } - return m; -} - -static inline uint64_t -pow_mod_p(uint64_t a, uint64_t b) { - if (b==1) { - return a; - } - uint64_t t = pow_mod_p(a, b>>1); - t = mul_mod_p(t,t); - if (b % 2) { - t = mul_mod_p(t, a); - } - return t; -} - -// calc a^b % p -static uint64_t -powmodp(uint64_t a, uint64_t b) { - if (a > P) - a%=P; - return pow_mod_p(a,b); -} - -static void -push64(lua_State *L, uint64_t r) { - uint8_t tmp[8]; - tmp[0] = r & 0xff; - tmp[1] = (r >> 8 )& 0xff; - tmp[2] = (r >> 16 )& 0xff; - tmp[3] = (r >> 24 )& 0xff; - tmp[4] = (r >> 32 )& 0xff; - tmp[5] = (r >> 40 )& 0xff; - tmp[6] = (r >> 48 )& 0xff; - tmp[7] = (r >> 56 )& 0xff; - - lua_pushlstring(L, (const char *)tmp, 8); -} - -static int -ldhsecret(lua_State *L) { - uint32_t x[2], y[2]; - read64(L, x, y); - uint64_t xx = (uint64_t)x[0] | (uint64_t)x[1]<<32; - uint64_t yy = (uint64_t)y[0] | (uint64_t)y[1]<<32; - if (xx == 0 || yy == 0) - return luaL_error(L, "Can't be 0"); - uint64_t r = powmodp(xx, yy); - - push64(L, r); - - return 1; -} - -#define G 5 - -static int -ldhexchange(lua_State *L) { - size_t sz = 0; - const uint8_t *x = (const uint8_t *)luaL_checklstring(L, 1, &sz); - if (sz != 8) { - luaL_error(L, "Invalid dh uint64 key"); - } - uint32_t xx[2]; - xx[0] = x[0] | x[1]<<8 | x[2]<<16 | x[3]<<24; - xx[1] = x[4] | x[5]<<8 | x[6]<<16 | x[7]<<24; - - uint64_t x64 = (uint64_t)xx[0] | (uint64_t)xx[1]<<32; - if (x64 == 0) - return luaL_error(L, "Can't be 0"); - - uint64_t r = powmodp(G, x64); - push64(L, r); - return 1; -} - -// base64 - -static int -lb64encode(lua_State *L) { - static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - size_t sz = 0; - const uint8_t * text = (const uint8_t *)luaL_checklstring(L, 1, &sz); - int encode_sz = (sz + 2)/3*4; - char tmp[SMALL_CHUNK]; - char *buffer = tmp; - if (encode_sz > SMALL_CHUNK) { - buffer = lua_newuserdata(L, encode_sz); - } - int i,j; - j=0; - for (i=0;i<(int)sz-2;i+=3) { - uint32_t v = text[i] << 16 | text[i+1] << 8 | text[i+2]; - buffer[j] = encoding[v >> 18]; - buffer[j+1] = encoding[(v >> 12) & 0x3f]; - buffer[j+2] = encoding[(v >> 6) & 0x3f]; - buffer[j+3] = encoding[(v) & 0x3f]; - j+=4; - } - int padding = sz-i; - uint32_t v; - switch(padding) { - case 1 : - v = text[i]; - buffer[j] = encoding[v >> 2]; - buffer[j+1] = encoding[(v & 3) << 4]; - buffer[j+2] = '='; - buffer[j+3] = '='; - break; - case 2 : - v = text[i] << 8 | text[i+1]; - buffer[j] = encoding[v >> 10]; - buffer[j+1] = encoding[(v >> 4) & 0x3f]; - buffer[j+2] = encoding[(v & 0xf) << 2]; - buffer[j+3] = '='; - break; - } - lua_pushlstring(L, buffer, encode_sz); - return 1; -} - -static inline int -b64index(uint8_t c) { - static const int decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51}; - int decoding_size = sizeof(decoding)/sizeof(decoding[0]); - if (c<43) { - return -1; - } - c -= 43; - if (c>=decoding_size) - return -1; - return decoding[c]; -} - -static int -lb64decode(lua_State *L) { - size_t sz = 0; - const uint8_t * text = (const uint8_t *)luaL_checklstring(L, 1, &sz); - int decode_sz = (sz+3)/4*3; - char tmp[SMALL_CHUNK]; - char *buffer = tmp; - if (decode_sz > SMALL_CHUNK) { - buffer = lua_newuserdata(L, decode_sz); - } - int i,j; - int output = 0; - for (i=0;i=sz) { - return luaL_error(L, "Invalid base64 text"); - } - c[j] = b64index(text[i]); - if (c[j] == -1) { - ++i; - continue; - } - if (c[j] == -2) { - ++padding; - } - ++i; - ++j; - } - uint32_t v; - switch (padding) { - case 0: - v = (unsigned)c[0] << 18 | c[1] << 12 | c[2] << 6 | c[3]; - buffer[output] = v >> 16; - buffer[output+1] = (v >> 8) & 0xff; - buffer[output+2] = v & 0xff; - output += 3; - break; - case 1: - if (c[3] != -2 || (c[2] & 3)!=0) { - return luaL_error(L, "Invalid base64 text"); - } - v = (unsigned)c[0] << 10 | c[1] << 4 | c[2] >> 2 ; - buffer[output] = v >> 8; - buffer[output+1] = v & 0xff; - output += 2; - break; - case 2: - if (c[3] != -2 || c[2] != -2 || (c[1] & 0xf) !=0) { - return luaL_error(L, "Invalid base64 text"); - } - v = (unsigned)c[0] << 2 | c[1] >> 4; - buffer[output] = v; - ++ output; - break; - default: - return luaL_error(L, "Invalid base64 text"); - } - } - lua_pushlstring(L, buffer, output); - return 1; -} - -static int -lxor_str(lua_State *L) { - size_t len1,len2; - const char *s1 = luaL_checklstring(L,1,&len1); - const char *s2 = luaL_checklstring(L,2,&len2); - if (len2 == 0) { - return luaL_error(L, "Can't xor empty string"); - } - luaL_Buffer b; - char * buffer = luaL_buffinitsize(L, &b, len1); - int i; - for (i=0;i + +/* CRC32 TAB */ +static uint32_t CRC32[] = { + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, + 0x076dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L, + 0x0edb8832L, 0x79dcb8a4L, 0xe0d5e91eL, 0x97d2d988L, + 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, 0x90bf1d91L, + 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, + 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, + 0x136c9856L, 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, + 0x14015c4fL, 0x63066cd9L, 0xfa0f3d63L, 0x8d080df5L, + 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, 0xa2677172L, + 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, + 0x32d86ce3L, 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, + 0x26d930acL, 0x51de003aL, 0xc8d75180L, 0xbfd06116L, + 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, 0xb8bda50fL, + 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, + 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, + 0x76dc4190L, 0x01db7106L, 0x98d220bcL, 0xefd5102aL, + 0x71b18589L, 0x06b6b51fL, 0x9fbfe4a5L, 0xe8b8d433L, + 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, 0xe10e9818L, + 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, + 0x6c0695edL, 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, + 0x65b0d9c6L, 0x12b7e950L, 0x8bbeb8eaL, 0xfcb9887cL, + 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, 0xfbd44c65L, + 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, + 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, + 0x4369e96aL, 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, + 0x44042d73L, 0x33031de5L, 0xaa0a4c5fL, 0xdd0d7cc9L, + 0x5005713cL, 0x270241aaL, 0xbe0b1010L, 0xc90c2086L, + 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, + 0x59b33d17L, 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, + 0xedb88320L, 0x9abfb3b6L, 0x03b6e20cL, 0x74b1d29aL, + 0xead54739L, 0x9dd277afL, 0x04db2615L, 0x73dc1683L, + 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, + 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, + 0xf00f9344L, 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, + 0xf762575dL, 0x806567cbL, 0x196c3671L, 0x6e6b06e7L, + 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, 0x67dd4accL, + 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, + 0xd1bb67f1L, 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, + 0xd80d2bdaL, 0xaf0a1b4cL, 0x36034af6L, 0x41047a60L, + 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 0x4669be79L, + 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, + 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, + 0xc5ba3bbeL, 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, + 0xc2d7ffa7L, 0xb5d0cf31L, 0x2cd99e8bL, 0x5bdeae1dL, + 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, 0x026d930aL, + 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, + 0x92d28e9bL, 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, + 0x86d3d2d4L, 0xf1d4e242L, 0x68ddb3f8L, 0x1fda836eL, + 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, 0x18b74777L, + 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, + 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, + 0xa00ae278L, 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, + 0xa7672661L, 0xd06016f7L, 0x4969474dL, 0x3e6e77dbL, + 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, 0x37d83bf0L, + 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, + 0xbad03605L, 0xcdd70693L, 0x54de5729L, 0x23d967bfL, + 0xb3667a2eL, 0xc4614ab8L, 0x5d681b02L, 0x2a6f2b94L, + 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, 0x2d02ef8dL +}; + +/* CRC64 TAB */ +static uint64_t CRC64[] = { + 0x0000000000000000, 0x7ad870c830358979, + 0xf5b0e190606b12f2, 0x8f689158505e9b8b, + 0xc038e5739841b68f, 0xbae095bba8743ff6, + 0x358804e3f82aa47d, 0x4f50742bc81f2d04, + 0xab28ecb46814fe75, 0xd1f09c7c5821770c, + 0x5e980d24087fec87, 0x24407dec384a65fe, + 0x6b1009c7f05548fa, 0x11c8790fc060c183, + 0x9ea0e857903e5a08, 0xe478989fa00bd371, + 0x7d08ff3b88be6f81, 0x07d08ff3b88be6f8, + 0x88b81eabe8d57d73, 0xf2606e63d8e0f40a, + 0xbd301a4810ffd90e, 0xc7e86a8020ca5077, + 0x4880fbd87094cbfc, 0x32588b1040a14285, + 0xd620138fe0aa91f4, 0xacf86347d09f188d, + 0x2390f21f80c18306, 0x594882d7b0f40a7f, + 0x1618f6fc78eb277b, 0x6cc0863448deae02, + 0xe3a8176c18803589, 0x997067a428b5bcf0, + 0xfa11fe77117cdf02, 0x80c98ebf2149567b, + 0x0fa11fe77117cdf0, 0x75796f2f41224489, + 0x3a291b04893d698d, 0x40f16bccb908e0f4, + 0xcf99fa94e9567b7f, 0xb5418a5cd963f206, + 0x513912c379682177, 0x2be1620b495da80e, + 0xa489f35319033385, 0xde51839b2936bafc, + 0x9101f7b0e12997f8, 0xebd98778d11c1e81, + 0x64b116208142850a, 0x1e6966e8b1770c73, + 0x8719014c99c2b083, 0xfdc17184a9f739fa, + 0x72a9e0dcf9a9a271, 0x08719014c99c2b08, + 0x4721e43f0183060c, 0x3df994f731b68f75, + 0xb29105af61e814fe, 0xc849756751dd9d87, + 0x2c31edf8f1d64ef6, 0x56e99d30c1e3c78f, + 0xd9810c6891bd5c04, 0xa3597ca0a188d57d, + 0xec09088b6997f879, 0x96d1784359a27100, + 0x19b9e91b09fcea8b, 0x636199d339c963f2, + 0xdf7adabd7a6e2d6f, 0xa5a2aa754a5ba416, + 0x2aca3b2d1a053f9d, 0x50124be52a30b6e4, + 0x1f423fcee22f9be0, 0x659a4f06d21a1299, + 0xeaf2de5e82448912, 0x902aae96b271006b, + 0x74523609127ad31a, 0x0e8a46c1224f5a63, + 0x81e2d7997211c1e8, 0xfb3aa75142244891, + 0xb46ad37a8a3b6595, 0xceb2a3b2ba0eecec, + 0x41da32eaea507767, 0x3b024222da65fe1e, + 0xa2722586f2d042ee, 0xd8aa554ec2e5cb97, + 0x57c2c41692bb501c, 0x2d1ab4dea28ed965, + 0x624ac0f56a91f461, 0x1892b03d5aa47d18, + 0x97fa21650afae693, 0xed2251ad3acf6fea, + 0x095ac9329ac4bc9b, 0x7382b9faaaf135e2, + 0xfcea28a2faafae69, 0x8632586aca9a2710, + 0xc9622c4102850a14, 0xb3ba5c8932b0836d, + 0x3cd2cdd162ee18e6, 0x460abd1952db919f, + 0x256b24ca6b12f26d, 0x5fb354025b277b14, + 0xd0dbc55a0b79e09f, 0xaa03b5923b4c69e6, + 0xe553c1b9f35344e2, 0x9f8bb171c366cd9b, + 0x10e3202993385610, 0x6a3b50e1a30ddf69, + 0x8e43c87e03060c18, 0xf49bb8b633338561, + 0x7bf329ee636d1eea, 0x012b592653589793, + 0x4e7b2d0d9b47ba97, 0x34a35dc5ab7233ee, + 0xbbcbcc9dfb2ca865, 0xc113bc55cb19211c, + 0x5863dbf1e3ac9dec, 0x22bbab39d3991495, + 0xadd33a6183c78f1e, 0xd70b4aa9b3f20667, + 0x985b3e827bed2b63, 0xe2834e4a4bd8a21a, + 0x6debdf121b863991, 0x1733afda2bb3b0e8, + 0xf34b37458bb86399, 0x8993478dbb8deae0, + 0x06fbd6d5ebd3716b, 0x7c23a61ddbe6f812, + 0x3373d23613f9d516, 0x49aba2fe23cc5c6f, + 0xc6c333a67392c7e4, 0xbc1b436e43a74e9d, + 0x95ac9329ac4bc9b5, 0xef74e3e19c7e40cc, + 0x601c72b9cc20db47, 0x1ac40271fc15523e, + 0x5594765a340a7f3a, 0x2f4c0692043ff643, + 0xa02497ca54616dc8, 0xdafce7026454e4b1, + 0x3e847f9dc45f37c0, 0x445c0f55f46abeb9, + 0xcb349e0da4342532, 0xb1eceec59401ac4b, + 0xfebc9aee5c1e814f, 0x8464ea266c2b0836, + 0x0b0c7b7e3c7593bd, 0x71d40bb60c401ac4, + 0xe8a46c1224f5a634, 0x927c1cda14c02f4d, + 0x1d148d82449eb4c6, 0x67ccfd4a74ab3dbf, + 0x289c8961bcb410bb, 0x5244f9a98c8199c2, + 0xdd2c68f1dcdf0249, 0xa7f41839ecea8b30, + 0x438c80a64ce15841, 0x3954f06e7cd4d138, + 0xb63c61362c8a4ab3, 0xcce411fe1cbfc3ca, + 0x83b465d5d4a0eece, 0xf96c151de49567b7, + 0x76048445b4cbfc3c, 0x0cdcf48d84fe7545, + 0x6fbd6d5ebd3716b7, 0x15651d968d029fce, + 0x9a0d8ccedd5c0445, 0xe0d5fc06ed698d3c, + 0xaf85882d2576a038, 0xd55df8e515432941, + 0x5a3569bd451db2ca, 0x20ed197575283bb3, + 0xc49581ead523e8c2, 0xbe4df122e51661bb, + 0x3125607ab548fa30, 0x4bfd10b2857d7349, + 0x04ad64994d625e4d, 0x7e7514517d57d734, + 0xf11d85092d094cbf, 0x8bc5f5c11d3cc5c6, + 0x12b5926535897936, 0x686de2ad05bcf04f, + 0xe70573f555e26bc4, 0x9ddd033d65d7e2bd, + 0xd28d7716adc8cfb9, 0xa85507de9dfd46c0, + 0x273d9686cda3dd4b, 0x5de5e64efd965432, + 0xb99d7ed15d9d8743, 0xc3450e196da80e3a, + 0x4c2d9f413df695b1, 0x36f5ef890dc31cc8, + 0x79a59ba2c5dc31cc, 0x037deb6af5e9b8b5, + 0x8c157a32a5b7233e, 0xf6cd0afa9582aa47, + 0x4ad64994d625e4da, 0x300e395ce6106da3, + 0xbf66a804b64ef628, 0xc5bed8cc867b7f51, + 0x8aeeace74e645255, 0xf036dc2f7e51db2c, + 0x7f5e4d772e0f40a7, 0x05863dbf1e3ac9de, + 0xe1fea520be311aaf, 0x9b26d5e88e0493d6, + 0x144e44b0de5a085d, 0x6e963478ee6f8124, + 0x21c640532670ac20, 0x5b1e309b16452559, + 0xd476a1c3461bbed2, 0xaeaed10b762e37ab, + 0x37deb6af5e9b8b5b, 0x4d06c6676eae0222, + 0xc26e573f3ef099a9, 0xb8b627f70ec510d0, + 0xf7e653dcc6da3dd4, 0x8d3e2314f6efb4ad, + 0x0256b24ca6b12f26, 0x788ec2849684a65f, + 0x9cf65a1b368f752e, 0xe62e2ad306bafc57, + 0x6946bb8b56e467dc, 0x139ecb4366d1eea5, + 0x5ccebf68aecec3a1, 0x2616cfa09efb4ad8, + 0xa97e5ef8cea5d153, 0xd3a62e30fe90582a, + 0xb0c7b7e3c7593bd8, 0xca1fc72bf76cb2a1, + 0x45775673a732292a, 0x3faf26bb9707a053, + 0x70ff52905f188d57, 0x0a2722586f2d042e, + 0x854fb3003f739fa5, 0xff97c3c80f4616dc, + 0x1bef5b57af4dc5ad, 0x61372b9f9f784cd4, + 0xee5fbac7cf26d75f, 0x9487ca0fff135e26, + 0xdbd7be24370c7322, 0xa10fceec0739fa5b, + 0x2e675fb4576761d0, 0x54bf2f7c6752e8a9, + 0xcdcf48d84fe75459, 0xb71738107fd2dd20, + 0x387fa9482f8c46ab, 0x42a7d9801fb9cfd2, + 0x0df7adabd7a6e2d6, 0x772fdd63e7936baf, + 0xf8474c3bb7cdf024, 0x829f3cf387f8795d, + 0x66e7a46c27f3aa2c, 0x1c3fd4a417c62355, + 0x935745fc4798b8de, 0xe98f353477ad31a7, + 0xa6df411fbfb21ca3, 0xdc0731d78f8795da, + 0x536fa08fdfd90e51, 0x29b7d047efec8728, +}; + +int lcrc32(lua_State *L){ + size_t len = 0; + const char *str = luaL_checklstring(L, 1, &len); + if (!str || len < 1) + return luaL_error(L, "invalid string."); + + uint32_t i; + uint32_t crc = 0xFFFFFFFF; + + for (i = 0; i < len; i++) crc = CRC32[ (crc ^ str[i]) & 0xff ] ^ (crc >> 8); + lua_pushinteger(L, crc ^ 0xFFFFFFFF); + return 1; +}; + +int lcrc64(lua_State *L){ + size_t len = 0; + const char *str = luaL_checklstring(L, 1, &len); + if (!str || len < 1) + return luaL_error(L, "invalid string."); + + uint32_t i; + uint64_t crc = 0x0; + for (i = 0; i < len; i++) + crc = CRC64[(uint8_t)crc ^ (uint8_t)str[i]] ^ (crc >> 8); + + char* buf = (char*)lua_newuserdata(L, 20); + memset(buf, 0x0, 20); + sprintf(buf, "%"PRIu64"", crc); + + lua_pushlstring(L, (const char*)buf, 20); + return 1; +}; + +int ladler32(lua_State *L){ + size_t len = 0; + const uint8_t *buf = (const uint8_t *)luaL_checklstring(L, 1, &len); + if (!buf || len < 1) + return luaL_error(L, "invalid string."); + + uint64_t adler = 1; + uint64_t sum = 0; + size_t index; + for (index = 0; index < len; index++) { + adler = (adler + buf[index]) % 65521; + sum = (sum + adler) % 65521; + } + lua_pushinteger(L, (uint32_t)(adler | (sum << 16))); + return 1; +} diff --git a/luaclib/src/lcrypt/des.c b/luaclib/src/lcrypt/des.c new file mode 100644 index 00000000..5a45d174 --- /dev/null +++ b/luaclib/src/lcrypt/des.c @@ -0,0 +1,561 @@ +#include "lcrypt.h" + +/* the eight DES S-boxes */ + +static uint32_t SB1[64] = { + 0x01010400, 0x00000000, 0x00010000, 0x01010404, + 0x01010004, 0x00010404, 0x00000004, 0x00010000, + 0x00000400, 0x01010400, 0x01010404, 0x00000400, + 0x01000404, 0x01010004, 0x01000000, 0x00000004, + 0x00000404, 0x01000400, 0x01000400, 0x00010400, + 0x00010400, 0x01010000, 0x01010000, 0x01000404, + 0x00010004, 0x01000004, 0x01000004, 0x00010004, + 0x00000000, 0x00000404, 0x00010404, 0x01000000, + 0x00010000, 0x01010404, 0x00000004, 0x01010000, + 0x01010400, 0x01000000, 0x01000000, 0x00000400, + 0x01010004, 0x00010000, 0x00010400, 0x01000004, + 0x00000400, 0x00000004, 0x01000404, 0x00010404, + 0x01010404, 0x00010004, 0x01010000, 0x01000404, + 0x01000004, 0x00000404, 0x00010404, 0x01010400, + 0x00000404, 0x01000400, 0x01000400, 0x00000000, + 0x00010004, 0x00010400, 0x00000000, 0x01010004 +}; + +static uint32_t SB2[64] = { + 0x80108020, 0x80008000, 0x00008000, 0x00108020, + 0x00100000, 0x00000020, 0x80100020, 0x80008020, + 0x80000020, 0x80108020, 0x80108000, 0x80000000, + 0x80008000, 0x00100000, 0x00000020, 0x80100020, + 0x00108000, 0x00100020, 0x80008020, 0x00000000, + 0x80000000, 0x00008000, 0x00108020, 0x80100000, + 0x00100020, 0x80000020, 0x00000000, 0x00108000, + 0x00008020, 0x80108000, 0x80100000, 0x00008020, + 0x00000000, 0x00108020, 0x80100020, 0x00100000, + 0x80008020, 0x80100000, 0x80108000, 0x00008000, + 0x80100000, 0x80008000, 0x00000020, 0x80108020, + 0x00108020, 0x00000020, 0x00008000, 0x80000000, + 0x00008020, 0x80108000, 0x00100000, 0x80000020, + 0x00100020, 0x80008020, 0x80000020, 0x00100020, + 0x00108000, 0x00000000, 0x80008000, 0x00008020, + 0x80000000, 0x80100020, 0x80108020, 0x00108000 +}; + +static uint32_t SB3[64] = { + 0x00000208, 0x08020200, 0x00000000, 0x08020008, + 0x08000200, 0x00000000, 0x00020208, 0x08000200, + 0x00020008, 0x08000008, 0x08000008, 0x00020000, + 0x08020208, 0x00020008, 0x08020000, 0x00000208, + 0x08000000, 0x00000008, 0x08020200, 0x00000200, + 0x00020200, 0x08020000, 0x08020008, 0x00020208, + 0x08000208, 0x00020200, 0x00020000, 0x08000208, + 0x00000008, 0x08020208, 0x00000200, 0x08000000, + 0x08020200, 0x08000000, 0x00020008, 0x00000208, + 0x00020000, 0x08020200, 0x08000200, 0x00000000, + 0x00000200, 0x00020008, 0x08020208, 0x08000200, + 0x08000008, 0x00000200, 0x00000000, 0x08020008, + 0x08000208, 0x00020000, 0x08000000, 0x08020208, + 0x00000008, 0x00020208, 0x00020200, 0x08000008, + 0x08020000, 0x08000208, 0x00000208, 0x08020000, + 0x00020208, 0x00000008, 0x08020008, 0x00020200 +}; + +static uint32_t SB4[64] = { + 0x00802001, 0x00002081, 0x00002081, 0x00000080, + 0x00802080, 0x00800081, 0x00800001, 0x00002001, + 0x00000000, 0x00802000, 0x00802000, 0x00802081, + 0x00000081, 0x00000000, 0x00800080, 0x00800001, + 0x00000001, 0x00002000, 0x00800000, 0x00802001, + 0x00000080, 0x00800000, 0x00002001, 0x00002080, + 0x00800081, 0x00000001, 0x00002080, 0x00800080, + 0x00002000, 0x00802080, 0x00802081, 0x00000081, + 0x00800080, 0x00800001, 0x00802000, 0x00802081, + 0x00000081, 0x00000000, 0x00000000, 0x00802000, + 0x00002080, 0x00800080, 0x00800081, 0x00000001, + 0x00802001, 0x00002081, 0x00002081, 0x00000080, + 0x00802081, 0x00000081, 0x00000001, 0x00002000, + 0x00800001, 0x00002001, 0x00802080, 0x00800081, + 0x00002001, 0x00002080, 0x00800000, 0x00802001, + 0x00000080, 0x00800000, 0x00002000, 0x00802080 +}; + +static uint32_t SB5[64] = { + 0x00000100, 0x02080100, 0x02080000, 0x42000100, + 0x00080000, 0x00000100, 0x40000000, 0x02080000, + 0x40080100, 0x00080000, 0x02000100, 0x40080100, + 0x42000100, 0x42080000, 0x00080100, 0x40000000, + 0x02000000, 0x40080000, 0x40080000, 0x00000000, + 0x40000100, 0x42080100, 0x42080100, 0x02000100, + 0x42080000, 0x40000100, 0x00000000, 0x42000000, + 0x02080100, 0x02000000, 0x42000000, 0x00080100, + 0x00080000, 0x42000100, 0x00000100, 0x02000000, + 0x40000000, 0x02080000, 0x42000100, 0x40080100, + 0x02000100, 0x40000000, 0x42080000, 0x02080100, + 0x40080100, 0x00000100, 0x02000000, 0x42080000, + 0x42080100, 0x00080100, 0x42000000, 0x42080100, + 0x02080000, 0x00000000, 0x40080000, 0x42000000, + 0x00080100, 0x02000100, 0x40000100, 0x00080000, + 0x00000000, 0x40080000, 0x02080100, 0x40000100 +}; + +static uint32_t SB6[64] = { + 0x20000010, 0x20400000, 0x00004000, 0x20404010, + 0x20400000, 0x00000010, 0x20404010, 0x00400000, + 0x20004000, 0x00404010, 0x00400000, 0x20000010, + 0x00400010, 0x20004000, 0x20000000, 0x00004010, + 0x00000000, 0x00400010, 0x20004010, 0x00004000, + 0x00404000, 0x20004010, 0x00000010, 0x20400010, + 0x20400010, 0x00000000, 0x00404010, 0x20404000, + 0x00004010, 0x00404000, 0x20404000, 0x20000000, + 0x20004000, 0x00000010, 0x20400010, 0x00404000, + 0x20404010, 0x00400000, 0x00004010, 0x20000010, + 0x00400000, 0x20004000, 0x20000000, 0x00004010, + 0x20000010, 0x20404010, 0x00404000, 0x20400000, + 0x00404010, 0x20404000, 0x00000000, 0x20400010, + 0x00000010, 0x00004000, 0x20400000, 0x00404010, + 0x00004000, 0x00400010, 0x20004010, 0x00000000, + 0x20404000, 0x20000000, 0x00400010, 0x20004010 +}; + +static uint32_t SB7[64] = { + 0x00200000, 0x04200002, 0x04000802, 0x00000000, + 0x00000800, 0x04000802, 0x00200802, 0x04200800, + 0x04200802, 0x00200000, 0x00000000, 0x04000002, + 0x00000002, 0x04000000, 0x04200002, 0x00000802, + 0x04000800, 0x00200802, 0x00200002, 0x04000800, + 0x04000002, 0x04200000, 0x04200800, 0x00200002, + 0x04200000, 0x00000800, 0x00000802, 0x04200802, + 0x00200800, 0x00000002, 0x04000000, 0x00200800, + 0x04000000, 0x00200800, 0x00200000, 0x04000802, + 0x04000802, 0x04200002, 0x04200002, 0x00000002, + 0x00200002, 0x04000000, 0x04000800, 0x00200000, + 0x04200800, 0x00000802, 0x00200802, 0x04200800, + 0x00000802, 0x04000002, 0x04200802, 0x04200000, + 0x00200800, 0x00000000, 0x00000002, 0x04200802, + 0x00000000, 0x00200802, 0x04200000, 0x00000800, + 0x04000002, 0x04000800, 0x00000800, 0x00200002 +}; + +static uint32_t SB8[64] = { + 0x10001040, 0x00001000, 0x00040000, 0x10041040, + 0x10000000, 0x10001040, 0x00000040, 0x10000000, + 0x00040040, 0x10040000, 0x10041040, 0x00041000, + 0x10041000, 0x00041040, 0x00001000, 0x00000040, + 0x10040000, 0x10000040, 0x10001000, 0x00001040, + 0x00041000, 0x00040040, 0x10040040, 0x10041000, + 0x00001040, 0x00000000, 0x00000000, 0x10040040, + 0x10000040, 0x10001000, 0x00041040, 0x00040000, + 0x00041040, 0x00040000, 0x10041000, 0x00001000, + 0x00000040, 0x10040040, 0x00001000, 0x00041040, + 0x10001000, 0x00000040, 0x10000040, 0x10040000, + 0x10040040, 0x10000000, 0x00040000, 0x10001040, + 0x00000000, 0x10041040, 0x00040040, 0x10000040, + 0x10040000, 0x10001000, 0x10001040, 0x00000000, + 0x10041040, 0x00041000, 0x00041000, 0x00001040, + 0x00001040, 0x00040040, 0x10000000, 0x10041000 +}; + +/* PC1: left and right halves bit-swap */ + +static uint32_t LHs[16] = { + 0x00000000, 0x00000001, 0x00000100, 0x00000101, + 0x00010000, 0x00010001, 0x00010100, 0x00010101, + 0x01000000, 0x01000001, 0x01000100, 0x01000101, + 0x01010000, 0x01010001, 0x01010100, 0x01010101 +}; + +static uint32_t RHs[16] = { + 0x00000000, 0x01000000, 0x00010000, 0x01010000, + 0x00000100, 0x01000100, 0x00010100, 0x01010100, + 0x00000001, 0x01000001, 0x00010001, 0x01010001, + 0x00000101, 0x01000101, 0x00010101, 0x01010101, +}; + +/* platform-independant 32-bit integer manipulation macros */ + +#define GET_UINT32(n,b,i) \ +{ \ + (n) = ( (uint32_t) (b)[(i) ] << 24 ) \ + | ( (uint32_t) (b)[(i) + 1] << 16 ) \ + | ( (uint32_t) (b)[(i) + 2] << 8 ) \ + | ( (uint32_t) (b)[(i) + 3] ); \ +} + +#define PUT_UINT32(n,b,i) \ +{ \ + (b)[(i) ] = (uint8_t) ( (n) >> 24 ); \ + (b)[(i) + 1] = (uint8_t) ( (n) >> 16 ); \ + (b)[(i) + 2] = (uint8_t) ( (n) >> 8 ); \ + (b)[(i) + 3] = (uint8_t) ( (n) ); \ +} + +/* Initial Permutation macro */ + +#define DES_IP(X,Y) \ +{ \ + T = ((X >> 4) ^ Y) & 0x0F0F0F0F; Y ^= T; X ^= (T << 4); \ + T = ((X >> 16) ^ Y) & 0x0000FFFF; Y ^= T; X ^= (T << 16); \ + T = ((Y >> 2) ^ X) & 0x33333333; X ^= T; Y ^= (T << 2); \ + T = ((Y >> 8) ^ X) & 0x00FF00FF; X ^= T; Y ^= (T << 8); \ + Y = ((Y << 1) | (Y >> 31)) & 0xFFFFFFFF; \ + T = (X ^ Y) & 0xAAAAAAAA; Y ^= T; X ^= T; \ + X = ((X << 1) | (X >> 31)) & 0xFFFFFFFF; \ +} + +/* Final Permutation macro */ + +#define DES_FP(X,Y) \ +{ \ + X = ((X << 31) | (X >> 1)) & 0xFFFFFFFF; \ + T = (X ^ Y) & 0xAAAAAAAA; X ^= T; Y ^= T; \ + Y = ((Y << 31) | (Y >> 1)) & 0xFFFFFFFF; \ + T = ((Y >> 8) ^ X) & 0x00FF00FF; X ^= T; Y ^= (T << 8); \ + T = ((Y >> 2) ^ X) & 0x33333333; X ^= T; Y ^= (T << 2); \ + T = ((X >> 16) ^ Y) & 0x0000FFFF; Y ^= T; X ^= (T << 16); \ + T = ((X >> 4) ^ Y) & 0x0F0F0F0F; Y ^= T; X ^= (T << 4); \ +} + +/* DES round macro */ + +#define DES_ROUND(X,Y) \ +{ \ + T = *SK++ ^ X; \ + Y ^= SB8[ (T ) & 0x3F ] ^ \ + SB6[ (T >> 8) & 0x3F ] ^ \ + SB4[ (T >> 16) & 0x3F ] ^ \ + SB2[ (T >> 24) & 0x3F ]; \ + \ + T = *SK++ ^ ((X << 28) | (X >> 4)); \ + Y ^= SB7[ (T ) & 0x3F ] ^ \ + SB5[ (T >> 8) & 0x3F ] ^ \ + SB3[ (T >> 16) & 0x3F ] ^ \ + SB1[ (T >> 24) & 0x3F ]; \ +} + +/* DES key schedule */ + +static inline void des_main_ks( uint32_t SK[32], const uint8_t key[8] ) { + int i; + uint32_t X, Y, T; + + GET_UINT32( X, key, 0 ); + GET_UINT32( Y, key, 4 ); + + /* Permuted Choice 1 */ + + T = ((Y >> 4) ^ X) & 0x0F0F0F0F; X ^= T; Y ^= (T << 4); + T = ((Y ) ^ X) & 0x10101010; X ^= T; Y ^= (T ); + + X = (LHs[ (X ) & 0xF] << 3) | (LHs[ (X >> 8) & 0xF ] << 2) + | (LHs[ (X >> 16) & 0xF] << 1) | (LHs[ (X >> 24) & 0xF ] ) + | (LHs[ (X >> 5) & 0xF] << 7) | (LHs[ (X >> 13) & 0xF ] << 6) + | (LHs[ (X >> 21) & 0xF] << 5) | (LHs[ (X >> 29) & 0xF ] << 4); + + Y = (RHs[ (Y >> 1) & 0xF] << 3) | (RHs[ (Y >> 9) & 0xF ] << 2) + | (RHs[ (Y >> 17) & 0xF] << 1) | (RHs[ (Y >> 25) & 0xF ] ) + | (RHs[ (Y >> 4) & 0xF] << 7) | (RHs[ (Y >> 12) & 0xF ] << 6) + | (RHs[ (Y >> 20) & 0xF] << 5) | (RHs[ (Y >> 28) & 0xF ] << 4); + + X &= 0x0FFFFFFF; + Y &= 0x0FFFFFFF; + + /* calculate subkeys */ + + for( i = 0; i < 16; i++ ) + { + if( i < 2 || i == 8 || i == 15 ) + { + X = ((X << 1) | (X >> 27)) & 0x0FFFFFFF; + Y = ((Y << 1) | (Y >> 27)) & 0x0FFFFFFF; + } + else + { + X = ((X << 2) | (X >> 26)) & 0x0FFFFFFF; + Y = ((Y << 2) | (Y >> 26)) & 0x0FFFFFFF; + } + + *SK++ = ((X << 4) & 0x24000000) | ((X << 28) & 0x10000000) + | ((X << 14) & 0x08000000) | ((X << 18) & 0x02080000) + | ((X << 6) & 0x01000000) | ((X << 9) & 0x00200000) + | ((X >> 1) & 0x00100000) | ((X << 10) & 0x00040000) + | ((X << 2) & 0x00020000) | ((X >> 10) & 0x00010000) + | ((Y >> 13) & 0x00002000) | ((Y >> 4) & 0x00001000) + | ((Y << 6) & 0x00000800) | ((Y >> 1) & 0x00000400) + | ((Y >> 14) & 0x00000200) | ((Y ) & 0x00000100) + | ((Y >> 5) & 0x00000020) | ((Y >> 10) & 0x00000010) + | ((Y >> 3) & 0x00000008) | ((Y >> 18) & 0x00000004) + | ((Y >> 26) & 0x00000002) | ((Y >> 24) & 0x00000001); + + *SK++ = ((X << 15) & 0x20000000) | ((X << 17) & 0x10000000) + | ((X << 10) & 0x08000000) | ((X << 22) & 0x04000000) + | ((X >> 2) & 0x02000000) | ((X << 1) & 0x01000000) + | ((X << 16) & 0x00200000) | ((X << 11) & 0x00100000) + | ((X << 3) & 0x00080000) | ((X >> 6) & 0x00040000) + | ((X << 15) & 0x00020000) | ((X >> 4) & 0x00010000) + | ((Y >> 2) & 0x00002000) | ((Y << 8) & 0x00001000) + | ((Y >> 14) & 0x00000808) | ((Y >> 9) & 0x00000400) + | ((Y ) & 0x00000200) | ((Y << 7) & 0x00000100) + | ((Y >> 7) & 0x00000020) | ((Y >> 3) & 0x00000011) + | ((Y << 2) & 0x00000004) | ((Y >> 21) & 0x00000002); + } +} + +/* DES 64-bit block encryption/decryption */ + +static inline void des_crypt( const uint32_t SK[32], const uint8_t input[8], uint8_t output[8] ) { + uint32_t X, Y, T; + + GET_UINT32( X, input, 0 ); + GET_UINT32( Y, input, 4 ); + + DES_IP( X, Y ); + + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + + DES_FP( Y, X ); + + PUT_UINT32( Y, output, 0 ); + PUT_UINT32( X, output, 4 ); +} + +static inline void des_key(lua_State *L, uint32_t SK[32]) { + size_t keysz = 0; + const void * key = luaL_checklstring(L, 1, &keysz); + if (keysz != 8) { + luaL_error(L, "Invalid key size %d, need 8 bytes", (int)keysz); + } + des_main_ks(SK, key); +} + +int ldesencode(lua_State *L) { + uint32_t SK[32]; + des_key(L, SK); + + size_t textsz = 0; + const uint8_t * text = (const uint8_t *)luaL_checklstring(L, 2, &textsz); + size_t chunksz = (textsz + 8) & ~7; + uint8_t tmp[SMALL_CHUNK]; + uint8_t *buffer = tmp; + if (chunksz > SMALL_CHUNK) { + buffer = lua_newuserdata(L, chunksz); + } + int i; + for (i=0;i<(int)textsz-7;i+=8) { + des_crypt(SK, text+i, buffer+i); + } + int bytes = textsz - i; + uint8_t tail[8]; + int j; + for (j=0;j<8;j++) { + if (j < bytes) { + tail[j] = text[i+j]; + } else if (j==bytes) { + tail[j] = 0x80; + } else { + tail[j] = 0; + } + } + des_crypt(SK, tail, buffer+i); + lua_pushlstring(L, (const char *)buffer, chunksz); + + return 1; +} + +int ldesdecode(lua_State *L) { + uint32_t ESK[32]; + des_key(L, ESK); + uint32_t SK[32]; + int i; + for( i = 0; i < 32; i += 2 ) { + SK[i] = ESK[30 - i]; + SK[i + 1] = ESK[31 - i]; + } + size_t textsz = 0; + const uint8_t *text = (const uint8_t *)luaL_checklstring(L, 2, &textsz); + if ((textsz & 7) || textsz == 0) { + return luaL_error(L, "Invalid des crypt text length %d", (int)textsz); + } + uint8_t tmp[SMALL_CHUNK]; + uint8_t *buffer = tmp; + if (textsz > SMALL_CHUNK) { + buffer = lua_newuserdata(L, textsz); + } + for (i=0;i=textsz-8;i--) { + if (buffer[i] == 0) { + padding++; + } else if (buffer[i] == 0x80) { + break; + } else { + return luaL_error(L, "Invalid des crypt text"); + } + } + if (padding > 8) { + return luaL_error(L, "Invalid des crypt text"); + } + lua_pushlstring(L, (const char *)buffer, textsz - padding); + return 1; +} + +static inline const EVP_CIPHER * des_get_cipher(size_t mode) { + switch(mode){ + case 0: + return EVP_desx_cbc(); + case 1: + return EVP_des_cbc(); + case 2: + return EVP_des_ecb(); + case 3: + return EVP_des_cfb(); + case 4: + return EVP_des_ofb(); + case 5: + return EVP_des_ede(); + case 6: + return EVP_des_ede3(); + case 7: + return EVP_des_ede_ecb(); + case 8: + return EVP_des_ede3_ecb(); + } + return NULL; +} + +// 加密函数 +static inline int do_des_encrypt(lua_State *L, size_t des_mode, const uint8_t *key, const uint8_t *iv, const uint8_t *text, size_t tsize) { + + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + if (!ctx) + return luaL_error(L, "allocate EVP failed."); + + EVP_CIPHER_CTX_set_padding(ctx, 0); + + if (1 != EVP_EncryptInit_ex(ctx, des_get_cipher(des_mode), NULL, key, iv)){ + EVP_CIPHER_CTX_cleanup(ctx); + EVP_CIPHER_CTX_free(ctx); + lua_pushnil(L); + lua_pushstring(L, "des_encrypt_init failed."); + return 2; + } + + EVP_CIPHER_CTX_set_key_length(ctx, EVP_MAX_KEY_LENGTH); + + int out_size = tsize + EVP_MAX_BLOCK_LENGTH; + uint8_t *out = lua_newuserdata(L, out_size); + + int update_len = out_size; + if (0 == EVP_EncryptUpdate(ctx, out, &update_len, text, tsize)){ + EVP_CIPHER_CTX_cleanup(ctx); + EVP_CIPHER_CTX_free(ctx); + lua_pushnil(L); + lua_pushstring(L, "des_encrypt_update failed."); + return 2; + } + + int final_len = out_size; + if (0 == EVP_EncryptFinal(ctx, out + update_len, &final_len)){ + EVP_CIPHER_CTX_cleanup(ctx); + EVP_CIPHER_CTX_free(ctx); + lua_pushnil(L); + lua_pushstring(L, "des_encrypt_final failed."); + return 2; + } + + lua_pushlstring(L, (const char*)out, update_len + final_len); + EVP_CIPHER_CTX_cleanup(ctx); + EVP_CIPHER_CTX_free(ctx); + return 1; +} + +// 解密函数 +static inline int do_des_decrypt(lua_State *L, size_t des_mode, const uint8_t *key, const uint8_t *iv, const uint8_t *cipher, size_t csize) { + + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + if (!ctx) + return luaL_error(L, "allocate EVP failed."); + + EVP_CIPHER_CTX_set_padding(ctx, 0); + + if (1 != EVP_DecryptInit_ex(ctx, des_get_cipher(des_mode), NULL, key, iv)){ + EVP_CIPHER_CTX_cleanup(ctx); + EVP_CIPHER_CTX_free(ctx); + lua_pushnil(L); + lua_pushstring(L, "des_decrypt_init failed."); + return 2; + } + + EVP_CIPHER_CTX_set_key_length(ctx, EVP_MAX_KEY_LENGTH); + + int out_size = csize + EVP_MAX_BLOCK_LENGTH; + uint8_t *out = lua_newuserdata(L, out_size); + + int update_len = out_size; + if (1 != EVP_DecryptUpdate(ctx, out, &update_len, cipher, csize)){ + EVP_CIPHER_CTX_cleanup(ctx); + EVP_CIPHER_CTX_free(ctx); + lua_pushnil(L); + lua_pushstring(L, "des_decrypt_update failed."); + return 2; + } + + int final_len = out_size; + if (1 != EVP_DecryptFinal_ex(ctx, out + update_len, &final_len)){ + EVP_CIPHER_CTX_cleanup(ctx); + EVP_CIPHER_CTX_free(ctx); + lua_pushnil(L); + lua_pushstring(L, "des_decrypt_final failed."); + return 2; + } + + lua_pushlstring(L, (const char*)out, update_len + final_len); + EVP_CIPHER_CTX_cleanup(ctx); + EVP_CIPHER_CTX_free(ctx); + return 1; +} + +static inline int lua_getargs(lua_State *L, size_t *des_mode, uint8_t **text, size_t *tsize, uint8_t **iv, uint8_t **key) { + *des_mode = luaL_checkinteger(L, 1); + if (*des_mode > 8) + return luaL_error(L, "Invalid des_mode"); + + *key = (uint8_t *)luaL_checkstring(L, 2); + if (!key) + return luaL_error(L, "Invalid key"); + + size_t size = 0; + *text = (uint8_t *)luaL_checklstring(L, 3, &size); + if (!text) + return luaL_error(L, "Invalid text"); + *tsize = size; + + *iv = (uint8_t *)luaL_checkstring(L, 4); + if (!iv) + return luaL_error(L, "Invalid iv"); + + return 1; +} + +int ldes_encrypt(lua_State *L) { + size_t text_sz = 0; size_t mode = 0; + + uint8_t* iv = NULL; uint8_t* key = NULL; uint8_t* text = NULL; + + return lua_getargs(L, &mode, &text, &text_sz, &iv, &key) && do_des_encrypt(L, mode, (const uint8_t*)key, (const uint8_t*)iv, (const uint8_t*)text, text_sz); +} + +int ldes_decrypt(lua_State *L) { + size_t cipher_sz = 0; size_t mode = 0; + + uint8_t* iv = NULL; uint8_t* key = NULL; uint8_t* cipher = NULL; + + return lua_getargs(L, &mode, &cipher, &cipher_sz, &iv, &key) && do_des_decrypt(L, mode, (const uint8_t*)key, (const uint8_t*)iv, (const uint8_t*)cipher, cipher_sz); +} \ No newline at end of file diff --git a/luaclib/src/lcrypt/dh.c b/luaclib/src/lcrypt/dh.c new file mode 100644 index 00000000..e950afa4 --- /dev/null +++ b/luaclib/src/lcrypt/dh.c @@ -0,0 +1,111 @@ +#include "lcrypt.h" + +// powmodp64 for DH-key exchange + +// The biggest 64bit prime +#define P 0xffffffffffffffc5ull + +static inline void read64(lua_State *L, uint32_t xx[2], uint32_t yy[2]) { + size_t sz = 0; + const uint8_t *x = (const uint8_t *)luaL_checklstring(L, 1, &sz); + if (sz != 8) { + luaL_error(L, "Invalid uint64 x"); + } + const uint8_t *y = (const uint8_t *)luaL_checklstring(L, 2, &sz); + if (sz != 8) { + luaL_error(L, "Invalid uint64 y"); + } + xx[0] = x[0] | x[1]<<8 | x[2]<<16 | x[3]<<24; + xx[1] = x[4] | x[5]<<8 | x[6]<<16 | x[7]<<24; + yy[0] = y[0] | y[1]<<8 | y[2]<<16 | y[3]<<24; + yy[1] = y[4] | y[5]<<8 | y[6]<<16 | y[7]<<24; +} + +static inline uint64_t mul_mod_p(uint64_t a, uint64_t b) { + uint64_t m = 0; + while(b) { + if(b&1) { + uint64_t t = P-a; + if ( m >= t) { + m -= t; + } else { + m += a; + } + } + if (a >= P - a) { + a = a * 2 - P; + } else { + a = a * 2; + } + b>>=1; + } + return m; +} + +static inline uint64_t pow_mod_p(uint64_t a, uint64_t b) { + if (b==1) { + return a; + } + uint64_t t = pow_mod_p(a, b>>1); + t = mul_mod_p(t,t); + if (b % 2) { + t = mul_mod_p(t, a); + } + return t; +} + +// calc a^b % p +static inline uint64_t powmodp(uint64_t a, uint64_t b) { + if (a > P) + a%=P; + return pow_mod_p(a,b); +} + +static inline void push64(lua_State *L, uint64_t r) { + uint8_t tmp[8]; + tmp[0] = r & 0xff; + tmp[1] = (r >> 8 )& 0xff; + tmp[2] = (r >> 16 )& 0xff; + tmp[3] = (r >> 24 )& 0xff; + tmp[4] = (r >> 32 )& 0xff; + tmp[5] = (r >> 40 )& 0xff; + tmp[6] = (r >> 48 )& 0xff; + tmp[7] = (r >> 56 )& 0xff; + + lua_pushlstring(L, (const char *)tmp, 8); +} + +int ldhsecret(lua_State *L) { + uint32_t x[2], y[2]; + read64(L, x, y); + uint64_t xx = (uint64_t)x[0] | (uint64_t)x[1]<<32; + uint64_t yy = (uint64_t)y[0] | (uint64_t)y[1]<<32; + if (xx == 0 || yy == 0) + return luaL_error(L, "Can't be 0"); + uint64_t r = powmodp(xx, yy); + + push64(L, r); + + return 1; +} + +#define G 5 + +int ldhexchange(lua_State *L) { + size_t sz = 0; + const uint8_t *x = (const uint8_t *)luaL_checklstring(L, 1, &sz); + if (sz != 8) { + luaL_error(L, "Invalid dh uint64 key"); + } + uint32_t xx[2]; + xx[0] = x[0] | x[1]<<8 | x[2]<<16 | x[3]<<24; + xx[1] = x[4] | x[5]<<8 | x[6]<<16 | x[7]<<24; + + uint64_t x64 = (uint64_t)xx[0] | (uint64_t)xx[1]<<32; + if (x64 == 0) + return luaL_error(L, "Can't be 0"); + + uint64_t r = powmodp(G, x64); + push64(L, r); + return 1; +} \ No newline at end of file diff --git a/luaclib/src/lcrypt/hex.c b/luaclib/src/lcrypt/hex.c new file mode 100644 index 00000000..db4f4dfe --- /dev/null +++ b/luaclib/src/lcrypt/hex.c @@ -0,0 +1,104 @@ +#include "lcrypt.h" +#include + +static char lencode[] = "0123456789abcdef"; + +static char hencode[] = "0123456789ABCDEF"; + +static const char deindex[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, +}; + +int lfromhex(lua_State *L) { + size_t tsize = 0; + const uint8_t* text = (const uint8_t *)luaL_checklstring(L, 1, &tsize); + if (!text || tsize < 2) + return luaL_error(L, "Invalid hexdecode text size %d", (int)tsize); + + luaL_Buffer B; + luaL_buffinit(L, &B); + + size_t idx = 0; int8_t hi; int8_t lo; + + while (idx < tsize){ + /* 跳过空格 */ + while(isspace((uint8_t)text[idx])) + idx++; + if (idx >= tsize) + break; + if (idx + 1 == tsize) + return luaL_error(L, "Invalid hexdecode ending."); + /* 解码计算 */ + hi = deindex[text[idx++]]; lo = deindex[text[idx++]]; + if (hi == -1 || lo == -1) + return luaL_error(L, "Invalid hexdecode char pos between %d and %d", idx - 2, idx - 1); + /* 还原数据 */ + luaL_addchar(&B, hi << 4 | lo); + } + luaL_pushresult(&B); + return 1; +} + +int ltohex(lua_State *L) { + size_t tsize = 0; + const uint8_t* text = (const uint8_t *)luaL_checklstring(L, 1, &tsize); + if (!text || tsize == 0) + return luaL_error(L, "Invalid hexencode text size %d", (int)tsize); + + /* 默认使用小写编码 */ + const char *etable = lencode; + if (lua_isboolean(L, 2) && lua_toboolean(L, 2)) + etable = hencode; + + /* 编码之间加上空格 */ + size_t n = 2; + if (lua_isboolean(L, 3) && lua_toboolean(L, 3)) + n = 3; + + luaL_Buffer B; + luaL_buffinit(L, &B); + + uint8_t code; + size_t i = 0; + while (i < tsize) + { + code = text[i++]; + luaL_addchar(&B, etable[code >> 4]); + luaL_addchar(&B, etable[code & 0xF]); + if (n == 3 && i < tsize) /* 编码结尾不添加空格 */ + luaL_addchar(&B, ' '); + } + luaL_pushresult(&B); + return 1; +} diff --git a/luaclib/src/lcrypt/hmac.c b/luaclib/src/lcrypt/hmac.c new file mode 100644 index 00000000..ab8d58ca --- /dev/null +++ b/luaclib/src/lcrypt/hmac.c @@ -0,0 +1,138 @@ +#include "lcrypt.h" + +/* +# define MD5_DIGEST_LENGTH 16 +# define SHA_DIGEST_LENGTH 20 +# define SHA224_DIGEST_LENGTH 28 +# define SHA256_DIGEST_LENGTH 32 +# define SHA384_DIGEST_LENGTH 48 +# define SHA512_DIGEST_LENGTH 64 +*/ + +static inline int hmac_digest(lua_State *L, const char *text, size_t tsize, const char *key, size_t ksize, const EVP_MD *md) { + unsigned int len = EVP_MAX_MD_SIZE; + unsigned char resualt[EVP_MAX_MD_SIZE]; + HMAC(md, key, ksize, (unsigned char *)text, tsize, resualt, &len); + lua_pushlstring(L, (const char *)resualt, len); + return 1; +} + +int lhmac_md4(lua_State *L) { + size_t key_sz = 0; + size_t text_sz = 0; + + const char * key = luaL_checklstring(L, 1, &key_sz); + if (!key || key_sz <= 0) + return luaL_error(L, "Invalid key value."); + + const char * text = luaL_checklstring(L, 2, &text_sz); + if (!text || text_sz <= 0) + return luaL_error(L, "Invalid text value."); + + return hmac_digest(L, text, text_sz, key, key_sz, EVP_md4()); +}; + +int lhmac_md5(lua_State *L) { + size_t key_sz = 0; + size_t text_sz = 0; + + const char * key = luaL_checklstring(L, 1, &key_sz); + if (!key || key_sz <= 0) + return luaL_error(L, "Invalid key value."); + + const char * text = luaL_checklstring(L, 2, &text_sz); + if (!text || text_sz <= 0) + return luaL_error(L, "Invalid text value."); + + return hmac_digest(L, text, text_sz, key, key_sz, EVP_md5()); +}; + +int lhmac_sha128(lua_State *L) { + size_t key_sz = 0; + size_t text_sz = 0; + + const char * key = luaL_checklstring(L, 1, &key_sz); + if (!key || key_sz <= 0) + return luaL_error(L, "Invalid key value."); + + const char * text = luaL_checklstring(L, 2, &text_sz); + if (!text || text_sz <= 0) + return luaL_error(L, "Invalid text value."); + + return hmac_digest(L, text, text_sz, key, key_sz, EVP_sha1()); +}; + +int lhmac_sha224(lua_State *L) { + size_t key_sz = 0; + size_t text_sz = 0; + + const char * key = luaL_checklstring(L, 1, &key_sz); + if (!key || key_sz <= 0) + return luaL_error(L, "Invalid key value."); + + const char * text = luaL_checklstring(L, 2, &text_sz); + if (!text || text_sz <= 0) + return luaL_error(L, "Invalid text value."); + + return hmac_digest(L, text, text_sz, key, key_sz, EVP_sha224()); +}; + +int lhmac_sha256(lua_State *L) { + size_t key_sz = 0; + size_t text_sz = 0; + + const char * key = luaL_checklstring(L, 1, &key_sz); + if (!key || key_sz <= 0) + return luaL_error(L, "Invalid key value."); + + const char * text = luaL_checklstring(L, 2, &text_sz); + if (!text || text_sz <= 0) + return luaL_error(L, "Invalid text value."); + + return hmac_digest(L, text, text_sz, key, key_sz, EVP_sha256()); +}; + +int lhmac_sha384(lua_State *L) { + size_t key_sz = 0; + size_t text_sz = 0; + + const char * key = luaL_checklstring(L, 1, &key_sz); + if (!key || key_sz <= 0) + return luaL_error(L, "Invalid key value."); + + const char * text = luaL_checklstring(L, 2, &text_sz); + if (!text || text_sz <= 0) + return luaL_error(L, "Invalid text value."); + + return hmac_digest(L, text, text_sz, key, key_sz, EVP_sha384()); +}; + +int lhmac_sha512(lua_State *L) { + size_t key_sz = 0; + size_t text_sz = 0; + + const char * key = luaL_checklstring(L, 1, &key_sz); + if (!key || key_sz <= 0) + return luaL_error(L, "Invalid key value."); + + const char * text = luaL_checklstring(L, 2, &text_sz); + if (!text || text_sz <= 0) + return luaL_error(L, "Invalid text value."); + + return hmac_digest(L, text, text_sz, key, key_sz, EVP_sha512()); +}; + +int lhmac_ripemd160(lua_State *L) { + size_t key_sz = 0; + size_t text_sz = 0; + + const char * key = luaL_checklstring(L, 1, &key_sz); + if (!key || key_sz <= 0) + return luaL_error(L, "Invalid key value."); + + const char * text = luaL_checklstring(L, 2, &text_sz); + if (!text || text_sz <= 0) + return luaL_error(L, "Invalid text value."); + + return hmac_digest(L, text, text_sz, key, key_sz, EVP_ripemd160()); +}; \ No newline at end of file diff --git a/luaclib/src/lcrypt/hmac_ex.c b/luaclib/src/lcrypt/hmac_ex.c new file mode 100644 index 00000000..f743a3c9 --- /dev/null +++ b/luaclib/src/lcrypt/hmac_ex.c @@ -0,0 +1,244 @@ +#include "lcrypt.h" + +/* -- Hashkey/Hmac_hash -- */ + +// Constants are the integer part of the sines of integers (in radians) * 2^32. +static const uint32_t k[64] = { +0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee , +0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501 , +0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be , +0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821 , +0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa , +0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8 , +0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed , +0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a , +0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c , +0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70 , +0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05 , +0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665 , +0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039 , +0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1 , +0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1 , +0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 }; + +// r specifies the per-round shift amounts +static const uint32_t r[] = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}; + +// leftrotate function definition +#define LEFTROTATE(x, c) (((x) << (c)) | ((x) >> (32 - (c)))) + +static inline void Hash(const char * str, int sz, uint8_t key[8]) { + uint32_t djb_hash = 5381L; + uint32_t js_hash = 1315423911L; + + int i; + for (i=0;i> 2)); + } + + key[0] = djb_hash & 0xff; + key[1] = (djb_hash >> 8) & 0xff; + key[2] = (djb_hash >> 16) & 0xff; + key[3] = (djb_hash >> 24) & 0xff; + + key[4] = js_hash & 0xff; + key[5] = (js_hash >> 8) & 0xff; + key[6] = (js_hash >> 16) & 0xff; + key[7] = (js_hash >> 24) & 0xff; +} + +static inline void digest_md5(uint32_t w[16], uint32_t result[4]) { + uint32_t a, b, c, d, f, g, temp; + int i; + + a = 0x67452301u; + b = 0xefcdab89u; + c = 0x98badcfeu; + d = 0x10325476u; + + for(i = 0; i<64; i++) { + if (i < 16) { + f = (b & c) | ((~b) & d); + g = i; + } else if (i < 32) { + f = (d & b) | ((~d) & c); + g = (5*i + 1) % 16; + } else if (i < 48) { + f = b ^ c ^ d; + g = (3*i + 5) % 16; + } else { + f = c ^ (b | (~d)); + g = (7*i) % 16; + } + + temp = d; + d = c; + c = b; + b = b + LEFTROTATE((a + f + k[i] + w[g]), r[i]); + a = temp; + } + + result[0] = a; + result[1] = b; + result[2] = c; + result[3] = d; +} + +// hmac64 use md5 algorithm without padding, and the result is (c^d .. a^b) +static inline void hmac(uint32_t x[2], uint32_t y[2], uint32_t result[2]) { + uint32_t w[16]; + uint32_t r[4]; + int i; + for (i=0;i<16;i+=4) { + w[i] = x[1]; + w[i+1] = x[0]; + w[i+2] = y[1]; + w[i+3] = y[0]; + } + + digest_md5(w,r); + + result[0] = r[2]^r[3]; + result[1] = r[0]^r[1]; +} + +static inline void hmac_md5(uint32_t x[2], uint32_t y[2], uint32_t result[2]) { + uint32_t w[16]; + uint32_t r[4]; + int i; + for (i=0;i<12;i+=4) { + w[i] = x[0]; + w[i+1] = x[1]; + w[i+2] = y[0]; + w[i+3] = y[1]; + } + + w[12] = 0x80; + w[13] = 0; + w[14] = 384; + w[15] = 0; + + digest_md5(w,r); + + result[0] = (r[0] + 0x67452301u) ^ (r[2] + 0x98badcfeu); + result[1] = (r[1] + 0xefcdab89u) ^ (r[3] + 0x10325476u); +} + +static inline void read64(lua_State *L, uint32_t xx[2], uint32_t yy[2]) { + size_t sz = 0; + const uint8_t *x = (const uint8_t *)luaL_checklstring(L, 1, &sz); + if (sz != 8) { + luaL_error(L, "Invalid uint64 x"); + } + const uint8_t *y = (const uint8_t *)luaL_checklstring(L, 2, &sz); + if (sz != 8) { + luaL_error(L, "Invalid uint64 y"); + } + xx[0] = x[0] | x[1]<<8 | x[2]<<16 | x[3]<<24; + xx[1] = x[4] | x[5]<<8 | x[6]<<16 | x[7]<<24; + yy[0] = y[0] | y[1]<<8 | y[2]<<16 | y[3]<<24; + yy[1] = y[4] | y[5]<<8 | y[6]<<16 | y[7]<<24; +} + +static inline int pushqword(lua_State *L, uint32_t result[2]) { + uint8_t tmp[8]; + tmp[0] = result[0] & 0xff; + tmp[1] = (result[0] >> 8 )& 0xff; + tmp[2] = (result[0] >> 16 )& 0xff; + tmp[3] = (result[0] >> 24 )& 0xff; + tmp[4] = result[1] & 0xff; + tmp[5] = (result[1] >> 8 )& 0xff; + tmp[6] = (result[1] >> 16 )& 0xff; + tmp[7] = (result[1] >> 24 )& 0xff; + + lua_pushlstring(L, (const char *)tmp, 8); + return 1; +} + +int lhmac64(lua_State *L) { + uint32_t x[2], y[2]; + read64(L, x, y); + uint32_t result[2]; + hmac(x,y,result); + return pushqword(L, result); +} + +/* + h1 = crypt.hmac64_md5(a,b) + m = md5.sum((a..b):rep(3)) + h2 = crypt.xor_str(m:sub(1,8), m:sub(9,16)) + assert(h1 == h2) + */ +int lhmac64_md5(lua_State *L) { + uint32_t x[2], y[2]; + read64(L, x, y); + uint32_t result[2]; + hmac_md5(x,y,result); + return pushqword(L, result); +} + +/* + 8bytes key + string text + */ +int lhmac_hash(lua_State *L) { + uint32_t key[2]; + size_t sz = 0; + const uint8_t *x = (const uint8_t *)luaL_checklstring(L, 1, &sz); + if (sz != 8) { + luaL_error(L, "Invalid uint64 key"); + } + key[0] = x[0] | x[1]<<8 | x[2]<<16 | x[3]<<24; + key[1] = x[4] | x[5]<<8 | x[6]<<16 | x[7]<<24; + const char * text = luaL_checklstring(L, 2, &sz); + uint8_t h[8]; + Hash(text,(int)sz,h); + uint32_t htext[2]; + htext[0] = h[0] | h[1]<<8 | h[2]<<16 | h[3]<<24; + htext[1] = h[4] | h[5]<<8 | h[6]<<16 | h[7]<<24; + uint32_t result[2]; + hmac(htext,key,result); + return pushqword(L, result); +} + +int lhashkey(lua_State *L) { + size_t sz = 0; + const char * key = luaL_checklstring(L, 1, &sz); + uint8_t realkey[8]; + Hash(key,(int)sz,realkey); + lua_pushlstring(L, (const char *)realkey, 8); + return 1; +} + +int lhmac_pbkdf2(lua_State *L) { + + EVP_MD *dig_md = (EVP_MD *)lua_touserdata(L, 1); + if (!dig_md) + return luaL_error(L, "Invalid pbkdf2 hash type."); + + size_t psize; + const char* password = (const char*)luaL_checklstring(L, 2, &psize); + if (psize < 1) + return luaL_error(L, "Invalid pbkdf2 password."); + + size_t sasize; + const unsigned char* salt = (const unsigned char*)luaL_tolstring(L, 3, &sasize); + if (sasize < 1) + salt = NULL; + + lua_Integer iter = luaL_checkinteger(L, 4); + + int bsize = EVP_MD_size(dig_md); + unsigned char buffer[bsize]; + + if (0 == PKCS5_PBKDF2_HMAC(password, psize, salt, sasize, iter > 0 ? iter : 1000, dig_md, bsize, buffer)) + return 0; + + lua_pushlstring(L, (const char*)buffer, bsize); + return 1; +} \ No newline at end of file diff --git a/luaclib/src/lcrypt/lcrypt.c b/luaclib/src/lcrypt/lcrypt.c new file mode 100644 index 00000000..6989b725 --- /dev/null +++ b/luaclib/src/lcrypt/lcrypt.c @@ -0,0 +1,232 @@ +#include "lcrypt.h" + +/* -- xor_str -- */ +static int lxor_str(lua_State *L) { + size_t len1 = 0; + const char *s1 = luaL_checklstring(L, 1, &len1); + if (!s1 || len1 == 0) + return luaL_error(L, "Can't xor empty string 1."); + + size_t len2 = 0; + const char *s2 = luaL_checklstring(L, 2, &len2); + if (!s2 || len2 == 0) + return luaL_error(L, "Can't xor empty string 2."); + + luaL_Buffer b; + char * buffer = luaL_buffinitsize(L, &b, len1); + + int i; + for (i = 0; i < len1; i ++) + buffer[i] = s1[i] ^ s2[i % len2]; + + luaL_addsize(&b, len1); + luaL_pushresult(&b); + return 1; +} + +static int lrandomkey(lua_State *L) { + lua_Integer len = lua_tointeger(L, 1); + if (len < 8 || len > 64) + return luaL_error(L, "randomkey error: 8 <= len <= 64."); + + uint8_t random_buf[len]; RAND_bytes(random_buf, len); + uint8_t random_key[len]; RAND_bytes(random_key, len); + uint8_t random_tmp[len]; RAND_bytes(random_tmp, len); + + int i; + for (i = 0; i < len; i++) + random_buf[i] = ((random_key[i] ^ random_tmp[i]) ^ random_buf[i]) & 0xff; + + lua_pushlstring(L, (const char *)random_buf, len); + return 1; +} +/* -- xor_str -- */ + + +/* 获取证书序列号 */ +static int lcert_get_sn(lua_State *L) { + + size_t tsize = 0; + const char *text = luaL_checklstring(L, 1, &tsize); + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + + /* 从字符串读取 */ + BIO *io = NULL; X509 *cert = NULL; + io = BIO_new(BIO_s_mem()); BIO_write(io, text, tsize); + cert = PEM_read_bio_X509(io, NULL, NULL, NULL); BIO_free(io); + if (!cert) + { + io = BIO_new_file(text, "rb"); + if (!io) + { lua_pushnil(L); lua_pushliteral(L, "[x509 ERROR]: Can't load cert."); return 2; } + + cert = PEM_read_bio_X509(io, NULL, NULL, NULL); BIO_free(io); + if (!cert) + { + char buf[512]; memset(buf, 0, sizeof(buf)); + ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); + lua_pushnil(L); lua_pushfstring(L, "[ssl load_certificate]: %s.", buf); + return 2; + } + } + + BIGNUM *bn = ASN1_INTEGER_to_BN(X509_get0_serialNumber(cert), NULL); + io = BIO_new(BIO_s_mem()); BN_print(io, bn); + const char *buf; int len = BIO_get_mem_data(io, &buf); + lua_pushlstring(L, buf, len); BIO_free(io); BN_free(bn); X509_free(cert); + return 1; +#else + return luaL_error(L, "[x509 ERROR]: can't load cert serial Number"); +#endif +} + + +#define lua_set_key_INT(L, key, value) ({ lua_pushstring((L), (key)); lua_pushinteger((L), (value)); lua_rawset((L), -3); }) +#define lua_set_key_STR(L, key, value) ({ lua_pushstring((L), (key)); lua_pushstring((L), (value)); lua_rawset((L), -3); }) +#define lua_set_key_PTR(L, key, value) ({ lua_pushstring((L), (key)); lua_pushlightuserdata((L), (void*)(value)); lua_rawset((L), -3); }) + +static int crypt_set_key_value(lua_State *L) { + /* OPENSSL VERSION NUMBER */ + lua_set_key_INT(L, "OPENSSL_VERSION_NUMBER", OPENSSL_VERSION_NUMBER); + lua_set_key_STR(L, "OPENSSL_VERSION_TEXT", OPENSSL_VERSION_TEXT); + + /* 增加aes填充方式常量 */ + lua_set_key_INT(L, "AES_PADDING_ZERO", EVP_PADDING_ZERO); + lua_set_key_INT(L, "AES_PADDING_PKCS7", EVP_PADDING_PKCS7); + lua_set_key_INT(L, "AES_PADDING_ISO7816", EVP_PADDING_ISO7816_4); + lua_set_key_INT(L, "AES_PADDING_ANSI923", EVP_PADDING_ANSI923); + + /* 增加rsa填充方式常量 */ + lua_set_key_INT(L, "RSA_NO_PADDING", RSA_NO_PADDING); + lua_set_key_INT(L, "RSA_PKCS1_PADDING", RSA_PKCS1_PADDING); + lua_set_key_INT(L, "RSA_PKCS1_OAEP_PADDING", RSA_PKCS1_OAEP_PADDING); + + /* 增加rsa_sign/rsa_verify算法常量*/ + lua_set_key_INT(L, "nid_md5", NID_md5); + lua_set_key_INT(L, "nid_sha1", NID_sha1); + lua_set_key_INT(L, "nid_sha256", NID_sha256); + lua_set_key_INT(L, "nid_sha512", NID_sha512); + + lua_set_key_PTR(L, "EVP_aes_128_ecb", EVP_aes_128_ecb()); + lua_set_key_PTR(L, "EVP_aes_128_cbc", EVP_aes_128_cbc()); + lua_set_key_PTR(L, "EVP_aes_128_cfb", EVP_aes_128_cfb()); + lua_set_key_PTR(L, "EVP_aes_128_ofb", EVP_aes_128_ofb()); + lua_set_key_PTR(L, "EVP_aes_128_ctr", EVP_aes_128_ctr()); + lua_set_key_PTR(L, "EVP_aes_128_ocb", EVP_aes_128_ocb()); + lua_set_key_PTR(L, "EVP_aes_128_gcm", EVP_aes_128_gcm()); + lua_set_key_PTR(L, "EVP_aes_128_ccm", EVP_aes_128_ccm()); + + lua_set_key_PTR(L, "EVP_aes_192_ecb", EVP_aes_192_ecb()); + lua_set_key_PTR(L, "EVP_aes_192_cbc", EVP_aes_192_cbc()); + lua_set_key_PTR(L, "EVP_aes_192_cfb", EVP_aes_192_cfb()); + lua_set_key_PTR(L, "EVP_aes_192_ofb", EVP_aes_192_ofb()); + lua_set_key_PTR(L, "EVP_aes_192_ctr", EVP_aes_192_ctr()); + lua_set_key_PTR(L, "EVP_aes_192_ocb", EVP_aes_192_ocb()); + lua_set_key_PTR(L, "EVP_aes_192_gcm", EVP_aes_192_gcm()); + lua_set_key_PTR(L, "EVP_aes_192_ccm", EVP_aes_192_ccm()); + + lua_set_key_PTR(L, "EVP_aes_256_ecb", EVP_aes_256_ecb()); + lua_set_key_PTR(L, "EVP_aes_256_cbc", EVP_aes_256_cbc()); + lua_set_key_PTR(L, "EVP_aes_256_cfb", EVP_aes_256_cfb()); + lua_set_key_PTR(L, "EVP_aes_256_ofb", EVP_aes_256_ofb()); + lua_set_key_PTR(L, "EVP_aes_256_ctr", EVP_aes_256_ctr()); + lua_set_key_PTR(L, "EVP_aes_256_ocb", EVP_aes_256_ocb()); + lua_set_key_PTR(L, "EVP_aes_256_gcm", EVP_aes_256_gcm()); + lua_set_key_PTR(L, "EVP_aes_256_ccm", EVP_aes_256_ccm()); + + /* 增加EVP的摘要方法模型 */ + lua_set_key_PTR(L, "EVP_md5", EVP_md5()); + // lua_set_key_PTR(L, "EVP_blake256", EVP_blake2s256()); + // lua_set_key_PTR(L, "EVP_blake512", EVP_blake2b512()); + lua_set_key_PTR(L, "EVP_sha128", EVP_sha1()); + lua_set_key_PTR(L, "EVP_sha224", EVP_sha224()); + lua_set_key_PTR(L, "EVP_sha256", EVP_sha256()); + lua_set_key_PTR(L, "EVP_sha384", EVP_sha384()); + lua_set_key_PTR(L, "EVP_sha512", EVP_sha512()); + return 1; +} + + +LUAMOD_API int luaopen_lcrypt(lua_State *L) { + luaL_checkversion(L); + luaL_Reg lcrypt[] = { + { "uuid", luuid }, + { "guid", lguid }, + { "hashkey", lhashkey }, + { "randomkey", lrandomkey }, + { "hexencode", ltohex }, + { "hexdecode", lfromhex }, + { "hmac64", lhmac64 }, + { "hmac64_md5", lhmac64_md5 }, + { "dhexchange", ldhexchange }, + { "dhsecret", ldhsecret }, + { "base64encode", lb64encode }, + { "base64decode", lb64decode }, + { "urlencode", lurlencode }, + { "urldecode", lurldecode }, + // SHA + { "md4", lmd4 }, + { "md5", lmd5 }, + { "crc32", lcrc32 }, + { "crc64", lcrc64 }, + { "adler32", ladler32 }, + { "sha1", lsha128 }, + { "sha128", lsha128 }, + { "sha224", lsha224 }, + { "sha256", lsha256 }, + { "sha384", lsha384 }, + { "sha512", lsha512 }, + { "ripemd160", lripemd160}, + // HMAC + { "hmac_md4", lhmac_md4 }, + { "hmac_md5", lhmac_md5 }, + { "hmac_sha1", lhmac_sha128 }, + { "hmac_sha128", lhmac_sha128 }, + { "hmac_sha224", lhmac_sha224 }, + { "hmac_sha256", lhmac_sha256 }, + { "hmac_sha384", lhmac_sha384 }, + { "hmac_sha512", lhmac_sha512 }, + { "hmac_hash", lhmac_hash }, + { "hmac_ripemd160", lhmac_ripemd160}, + { "hmac_pbkdf2", lhmac_pbkdf2 }, + { "xor_str", lxor_str }, + // 公钥加密 -> 私钥解密 + { "rsa_public_key_encode", lrsa_public_key_encode }, + { "rsa_private_key_decode", lrsa_private_key_decode }, + // 私钥加密 -> 公钥解密 + { "rsa_private_key_encode", lrsa_private_key_encode }, + { "rsa_public_key_decode", lrsa_public_key_decode }, + //md5/sha128/sha256/sha512 with rsa + {"rsa_sign", lrsa_sign}, + {"rsa_verify", lrsa_verify}, + // aes 加密 + { "aes_enc", laes_enc }, + { "aes_dec", laes_dec }, + { "rc4", lrc4 }, + // DES加密/解密 + { "desencode", ldesencode }, + { "desdecode", ldesdecode }, + { "des_encrypt", ldes_encrypt }, + { "des_decrypt", ldes_decrypt }, + // SM2/SM3/SM4 国密 + { "sm3", lsm3 }, + { "hmac_sm3", lhmac_sm3 }, + { "sm2keygen", lsm2keygen }, + { "sm2sign", lsm2sign }, + { "sm2verify", lsm2verify }, + { "sm4_cbc_encrypt", lsm4_cbc_encrypt }, + { "sm4_cbc_decrypt", lsm4_cbc_decrypt }, + { "sm4_ecb_encrypt", lsm4_ecb_encrypt }, + { "sm4_ecb_decrypt", lsm4_ecb_decrypt }, + { "sm4_ofb_encrypt", lsm4_ofb_encrypt }, + { "sm4_ofb_decrypt", lsm4_ofb_decrypt }, + { "sm4_ctr_encrypt", lsm4_ctr_encrypt }, + { "sm4_ctr_decrypt", lsm4_ctr_decrypt }, + // 证书相关 + { "get_cert_sn", lcert_get_sn}, + { NULL, NULL }, + }; + luaL_newlib(L, lcrypt); + return crypt_set_key_value(L); +} diff --git a/luaclib/src/lcrypt/lcrypt.h b/luaclib/src/lcrypt/lcrypt.h new file mode 100644 index 00000000..14fa9a95 --- /dev/null +++ b/luaclib/src/lcrypt/lcrypt.h @@ -0,0 +1,97 @@ +#define LUA_LIB + +#include +#include +#include +#include +#include +#include +#include + +#define SMALL_CHUNK 256 + +int luuid(lua_State *L); +int lguid(lua_State *L); + +int ltohex(lua_State *L); +int lfromhex(lua_State *L); + +int lcrc32(lua_State *L); +int lcrc64(lua_State *L); + +int ladler32(lua_State *L); + +int lb64encode(lua_State *L); +int lb64decode(lua_State *L); + +int lurlencode(lua_State *L); +int lurldecode(lua_State *L); + +int ldesencode(lua_State *L); +int ldesdecode(lua_State *L); + +int ldes_encrypt(lua_State *L); +int ldes_decrypt(lua_State *L); + +int ldhsecret(lua_State *L); +int ldhexchange(lua_State *L); + +int lhashkey(lua_State *L); +int lhmac_hash(lua_State *L); + +int lhmac64(lua_State *L); +int lhmac64_md5(lua_State *L); + +int lmd4(lua_State *L); +int lmd5(lua_State *L); +int lsha128(lua_State *L); +int lsha224(lua_State *L); +int lsha256(lua_State *L); +int lsha384(lua_State *L); +int lsha512(lua_State *L); +int lripemd160(lua_State *L); + +int lhmac_md4(lua_State *L); +int lhmac_md5(lua_State *L); +int lhmac_sha128(lua_State *L); +int lhmac_sha224(lua_State *L); +int lhmac_sha256(lua_State *L); +int lhmac_sha384(lua_State *L); +int lhmac_sha512(lua_State *L); +int lhmac_ripemd160(lua_State *L); +int lhmac_pbkdf2(lua_State *L); + +int lrc4(lua_State *L); + +int laes_enc(lua_State *L); +int laes_dec(lua_State *L); + +int lrsa_public_key_encode(lua_State *L); +int lrsa_private_key_decode(lua_State *L); + +int lrsa_private_key_encode(lua_State *L); +int lrsa_public_key_decode(lua_State *L); + + +int lrsa_sign(lua_State *L); +int lrsa_verify(lua_State *L); + +int lsm3(lua_State *L); +int lhmac_sm3(lua_State *L); + +int lsm2keygen(lua_State *L); + +int lsm2sign(lua_State *L); +int lsm2verify(lua_State *L); + +int lsm4_cbc_encrypt(lua_State *L); +int lsm4_cbc_decrypt(lua_State *L); + +int lsm4_ecb_encrypt(lua_State *L); +int lsm4_ecb_decrypt(lua_State *L); + +int lsm4_ofb_encrypt(lua_State *L); +int lsm4_ofb_decrypt(lua_State *L); + +int lsm4_ctr_encrypt(lua_State *L); +int lsm4_ctr_decrypt(lua_State *L); \ No newline at end of file diff --git a/luaclib/src/lcrypt/rc.c b/luaclib/src/lcrypt/rc.c new file mode 100644 index 00000000..e04f23ef --- /dev/null +++ b/luaclib/src/lcrypt/rc.c @@ -0,0 +1,138 @@ +#include "lcrypt.h" + +#define RC4_set_key(key, len, data) tc_rc4_set_key((key), (data), (len)) +#define RC4(key, len, indata, outdata) tc_rc4((key), (indata), (len), (outdata)) +#define RC4_encrypt(key, len, indata, outdata) RC4(key, len, indata, outdata) +#define RC4_decrypt(key, len, indata, outdata) RC4(key, len, indata, outdata) + +typedef struct tc_rc4_ctx { + unsigned int x, y; + unsigned int data[256]; +} RC4_KEY; + +int tc_rc4_set_key(RC4_KEY *key, const void *text, unsigned int tsize) { + if (!key || !text || text == 0) + return 0; + memset(key, 0x0, sizeof(RC4_KEY)); + + register uint32_t tmp; + register uint32_t *d; + register uint32_t id1, id2, i; + + id1 = id2 = 0; + d = &(key->data[0]); + for (i = 0; i < 256; i++) + d[i] = i; + +#define SK_LOOP(d,n) \ + { \ + tmp = d[(n)]; \ + id2 = (((uint8_t*)text)[id1] + tmp + id2) & 0xff; \ + if (++id1 == tsize) id1=0; \ + d[(n)]=d[id2]; \ + d[id2]=tmp; \ + } + + for (i = 0; i < 256; i += 4) { + SK_LOOP(d, i + 0); + SK_LOOP(d, i + 1); + SK_LOOP(d, i + 2); + SK_LOOP(d, i + 3); + } + return 1; +} + +void* tc_rc4(RC4_KEY *key, const void *text, unsigned int tsize, unsigned char *md) { + if (key == NULL || text == NULL || tsize == 0) + return NULL; + + md = malloc(tsize); + + uint32_t *d; uint32_t i; + uint32_t x, y, tx, ty; + + const unsigned char *indata = text; + unsigned char *outdata = md; + + x = key->x; y = key->y; d = key->data; + +#define LOOP(in,out) \ + x=((x+1)&0xff); \ + tx=d[x]; \ + y=(tx+y)&0xff; \ + d[x]=ty=d[y]; \ + d[y]=tx; \ + (out) = d[(tx+ty)&0xff] ^ (in); + + i = tsize >> 3; + if (i) { + for (;;) { + LOOP(indata[0], outdata[0]); + LOOP(indata[1], outdata[1]); + LOOP(indata[2], outdata[2]); + LOOP(indata[3], outdata[3]); + LOOP(indata[4], outdata[4]); + LOOP(indata[5], outdata[5]); + LOOP(indata[6], outdata[6]); + LOOP(indata[7], outdata[7]); + indata += 8; outdata += 8; + if (--i == 0) + break; + } + } + i = tsize & 0x07; + if (i) { + for (;;) { + LOOP(indata[0], outdata[0]); + if (--i == 0) + break; + LOOP(indata[1], outdata[1]); + if (--i == 0) + break; + LOOP(indata[2], outdata[2]); + if (--i == 0) + break; + LOOP(indata[3], outdata[3]); + if (--i == 0) + break; + LOOP(indata[4], outdata[4]); + if (--i == 0) + break; + LOOP(indata[5], outdata[5]); + if (--i == 0) + break; + LOOP(indata[6], outdata[6]); + if (--i == 0) + break; + } + } + key->x = x; key->y = y; + return md; +} + +// RC4 对称加密/解密算法 +int lrc4(lua_State *L) { + size_t ksize = 0; size_t tsize = 0; + + const uint8_t* key = (const uint8_t*)luaL_checklstring(L, 1, &ksize); + if (!key || ksize <= 0) + return luaL_error(L, "Invalid rc4 key."); + + const uint8_t* text = (const uint8_t*)luaL_checklstring(L, 2, &tsize); + if (!text || tsize <= 0) + return luaL_error(L, "Invalid rc4 text."); + + char *buffer = lua_newuserdata(L, tsize); + memset(buffer, 0x00, tsize); + + RC4_KEY rc4key; + // 设置密钥 + RC4_set_key(&rc4key, ksize, key); + // 加密/解密数据 + RC4(&rc4key, tsize, text, (uint8_t *)buffer); + + // 返回解密/解密的数据 + lua_pushlstring(L, buffer, tsize); + + return 1; +} \ No newline at end of file diff --git a/luaclib/src/lcrypt/rsa.c b/luaclib/src/lcrypt/rsa.c new file mode 100644 index 00000000..197d9b5d --- /dev/null +++ b/luaclib/src/lcrypt/rsa.c @@ -0,0 +1,224 @@ +#include "lcrypt.h" + +static inline EVP_PKEY* new_public_key(lua_State *L) { + size_t p_size = 0; + const uint8_t* path = (const uint8_t*)luaL_checklstring(L, 2, &p_size); + if (!path || p_size <= 0) + return NULL; + + EVP_PKEY* key; + /* 读字符串 */ + BIO* IO = BIO_new(BIO_s_mem()); BIO_write(IO, (const char *)path, p_size); + key = PEM_read_bio_PUBKEY(IO, NULL, NULL, NULL); + BIO_free(IO); + if (key) + return key; + /* 读文件 */ + FILE* f = fopen((const char *)path, "rb"); + if (!f) + return NULL; + + key = PEM_read_PUBKEY(f, NULL, NULL, NULL); + fclose(f); + return key; +} + +static inline EVP_PKEY* new_private_key(lua_State *L) { + size_t p_size = 0; + const uint8_t* path = (const uint8_t*)luaL_checklstring(L, 2, &p_size); + if (!path || p_size <= 0) + return NULL; + + EVP_PKEY * key; + /* 读字符串 */ + BIO* IO = BIO_new(BIO_s_mem()); BIO_write(IO, (const char *)path, p_size); + key = PEM_read_bio_PrivateKey(IO, NULL, NULL, NULL); + BIO_free(IO); + if (key) + return key; + /* 读文件 */ + FILE* f = fopen((const char *)path, "rb"); + if (!f) + return NULL; + + key = PEM_read_PrivateKey(f, NULL, NULL, NULL); + fclose(f); + return key; +} + +static inline int rsa_encrypt(lua_State *L, EVP_PKEY *rsa, int padding, const uint8_t *text, size_t tsize) { + size_t outlen; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(rsa, NULL); + if ( + EVP_PKEY_encrypt_init(ctx) <= 0 || + EVP_PKEY_CTX_set_rsa_padding(ctx, padding) <= 0 || + EVP_PKEY_encrypt(ctx, NULL, &outlen, text, tsize) <= 0 + ) { + EVP_PKEY_CTX_free(ctx); EVP_PKEY_free(rsa); + lua_pushboolean(L, 0); + lua_pushliteral(L, "[rsa error]: rsa encrypt init failed."); + return 2; + } + // xrio_log("enc outlen = %zu\n", outlen); + unsigned char *out = lua_newuserdata(L, outlen); + int ret = EVP_PKEY_encrypt(ctx, out, &outlen, text, tsize); + if (ret <= 0) { + EVP_PKEY_CTX_free(ctx); EVP_PKEY_free(rsa); + return luaL_error(L, "[rsa error]: rsa encrypt finally failed. %d", ret); + } + + lua_pushlstring(L, (char*)out, outlen); + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(rsa); + return 1; +} + +static inline int rsa_decrypt(lua_State *L, EVP_PKEY *rsa, int padding, const uint8_t *cipher, size_t csize) { + size_t outlen; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(rsa, NULL); + if ( + EVP_PKEY_decrypt_init(ctx) <= 0 || + EVP_PKEY_CTX_set_rsa_padding(ctx, padding) <= 0 || + EVP_PKEY_decrypt(ctx, NULL, &outlen, cipher, csize) <= 0 + ) { + EVP_PKEY_CTX_free(ctx); EVP_PKEY_free(rsa); + lua_pushboolean(L, 0); + lua_pushliteral(L, "[rsa error]: rsa decrypt init failed."); + return 2; + } + // xrio_log("dec outlen = %zu\n", outlen); + unsigned char *out = lua_newuserdata(L, outlen); + int ret = EVP_PKEY_decrypt(ctx, out, &outlen, cipher, csize); + if (ret <= 0) { + EVP_PKEY_CTX_free(ctx); EVP_PKEY_free(rsa); + return luaL_error(L, "[rsa error]: rsa decrypt finally failed. %d", ret); + } + lua_pushlstring(L, (char*)out, outlen); + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(rsa); + return 1; +} + +int lrsa_public_key_encode(lua_State *L) { + size_t tsize = 0; + const uint8_t* text = (const uint8_t*)luaL_checklstring(L, 1, &tsize); + if (!text || tsize < 1) + return luaL_error(L, "[rsa error]: Invalid text"); + + EVP_PKEY *rsa = new_public_key(L); + if (!rsa) + return luaL_error(L, "[rsa error]: Can't load rsa public key."); + + return rsa_encrypt(L, rsa, luaL_checkinteger(L, 3), text, tsize); +} + +int lrsa_public_key_decode(lua_State *L) { + size_t csize = 0; + const uint8_t* cipher = (const uint8_t*)luaL_checklstring(L, 1, &csize); + if (!cipher || csize < 1) + return luaL_error(L, "[rsa error]: Invalid cipher"); + + EVP_PKEY *rsa = new_public_key(L); + if (!rsa) + return luaL_error(L, "[rsa error]: Can't load rsa public key."); + + return rsa_decrypt(L, rsa, luaL_checkinteger(L, 3), cipher, csize); +} + +int lrsa_private_key_encode(lua_State *L) { + size_t tsize = 0; + const uint8_t* text = (const uint8_t*)luaL_checklstring(L, 1, &tsize); + if (!text || tsize < 1) + return luaL_error(L, "[rsa error]: Invalid text"); + + EVP_PKEY *rsa = new_private_key(L); + if (!rsa) + return luaL_error(L, "[rsa error]: Can't load rsa private key."); + + return rsa_encrypt(L, rsa, luaL_checkinteger(L, 3), text, tsize); +} + +int lrsa_private_key_decode(lua_State *L) { + size_t csize = 0; + const uint8_t* cipher = (const uint8_t*)luaL_checklstring(L, 1, &csize); + if (!cipher || csize < 1) + return luaL_error(L, "[rsa error]: Invalid cipher"); + + EVP_PKEY *rsa = new_private_key(L); + if (!rsa) + return luaL_error(L, "[rsa error]: Can't load rsa private key."); + + return rsa_decrypt(L, rsa, luaL_checkinteger(L, 3), cipher, csize); +} + +// 获取签名方法 +#define rsa_nid_mode(L, pos) EVP_get_digestbynid(lua_tointeger((L), (pos))) + +// RSA签名算法 +int lrsa_sign(lua_State *L) { + size_t tsize = 0; + const uint8_t* text = (const uint8_t*)luaL_checklstring(L, 1, &tsize); + if (!text || tsize < 1) + return luaL_error(L, "[rsa sign]: Invalid text"); + + EVP_PKEY *rsa = new_private_key(L); + if (!rsa) + return luaL_error(L, "[rsa sign]: Can't find valide private rsa."); + + size_t siglen; + EVP_MD_CTX* ctx = EVP_MD_CTX_create(); + EVP_MD_CTX_init(ctx); + + if ( + EVP_DigestSignInit(ctx, NULL, rsa_nid_mode(L, 3), NULL, rsa) <= 0 || + EVP_DigestSignUpdate(ctx, text, tsize) <= 0 || + EVP_DigestSignFinal(ctx, NULL, &siglen) <= 0 + ) { + EVP_MD_CTX_destroy(ctx); EVP_PKEY_free(rsa); + return luaL_error(L, "[rsa sign]: init failed."); + } + + char sig[siglen]; + if (EVP_DigestSignFinal(ctx, (unsigned char *)sig, &siglen) <= 0) { + EVP_MD_CTX_destroy(ctx); EVP_PKEY_free(rsa); + return luaL_error(L, "[rsa sign]: finally failed."); + } + + lua_pushlstring(L, sig, siglen); + EVP_MD_CTX_destroy(ctx); + EVP_PKEY_free(rsa); + return 1; +} + +// RSA验签算法 +int lrsa_verify(lua_State *L) { + size_t tsize = 0; + const uint8_t* text = (const uint8_t*)luaL_checklstring(L, 1, &tsize); + if (!text || tsize < 1) + return luaL_error(L, "[rsa verify]: Invalid text"); + + size_t siglen = 0; + const uint8_t *sig = (const uint8_t*)luaL_checklstring(L, 3, &siglen); + if (!sig || siglen < 1) + return luaL_error(L, "[rsa verify]: Invalid sign"); + + EVP_PKEY* rsa = new_public_key(L); + if (!rsa) + return luaL_error(L, "[rsa verify]: Can't find valide public rsa."); + + EVP_MD_CTX* ctx = EVP_MD_CTX_create(); + EVP_MD_CTX_init(ctx); + + if ( + EVP_DigestVerifyInit(ctx, NULL, rsa_nid_mode(L, 4), NULL, rsa) <= 0 || + EVP_DigestVerifyUpdate(ctx, text, tsize) <= 0 || + EVP_DigestVerifyFinal(ctx, sig, siglen) <= 0 + ) + lua_pushboolean(L, 0); + else + lua_pushboolean(L, 1); + + EVP_MD_CTX_destroy(ctx); + EVP_PKEY_free(rsa); + return 1; +} \ No newline at end of file diff --git a/luaclib/src/lcrypt/sha.c b/luaclib/src/lcrypt/sha.c new file mode 100644 index 00000000..d86fffac --- /dev/null +++ b/luaclib/src/lcrypt/sha.c @@ -0,0 +1,82 @@ +#include "lcrypt.h" + +/* +# define MD5_DIGEST_LENGTH 16 +# define SHA_DIGEST_LENGTH 20 +# define SHA224_DIGEST_LENGTH 28 +# define SHA256_DIGEST_LENGTH 32 +# define SHA384_DIGEST_LENGTH 48 +# define SHA512_DIGEST_LENGTH 64 +*/ + +static inline int sha_digest(lua_State *L, const char* text, size_t tsize, const EVP_MD *type) { + unsigned int rsize = EVP_MAX_MD_SIZE; + unsigned char result[rsize]; + EVP_Digest(text, tsize, result, &rsize, type, NULL); + lua_pushlstring(L, (const char*)result, rsize); + return 1; +} + +int lmd4(lua_State *L) { + size_t sz = 0; + const char * text = luaL_checklstring(L, 1, &sz); + if (!text || sz <= 0) + return luaL_error(L, "Invalid text value."); + return sha_digest(L, text, sz, EVP_md4()); +} + +int lmd5(lua_State *L) { + size_t sz = 0; + const char * text = luaL_checklstring(L, 1, &sz); + if (!text || sz <= 0) + return luaL_error(L, "Invalid text value."); + return sha_digest(L, text, sz, EVP_md5()); +}; + +int lsha128(lua_State *L) { + size_t sz = 0; + const char * text = luaL_checklstring(L, 1, &sz); + if (!text || sz <= 0) + return luaL_error(L, "Invalid text value."); + return sha_digest(L, text, sz, EVP_sha1()); +}; + +int lsha224(lua_State *L) { + size_t sz = 0; + const char * text = luaL_checklstring(L, 1, &sz); + if (!text || sz <= 0) + return luaL_error(L, "Invalid text value."); + return sha_digest(L, text, sz, EVP_sha224()); +}; + +int lsha256(lua_State *L) { + size_t sz = 0; + const char * text = luaL_checklstring(L, 1, &sz); + if (!text || sz <= 0) + return luaL_error(L, "Invalid text value."); + return sha_digest(L, text, sz, EVP_sha256()); +}; + +int lsha384(lua_State *L) { + size_t sz = 0; + const char * text = luaL_checklstring(L, 1, &sz); + if (!text || sz <= 0) + return luaL_error(L, "Invalid text value."); + return sha_digest(L, text, sz, EVP_sha384()); +}; + +int lsha512(lua_State *L) { + size_t sz = 0; + const char * text = luaL_checklstring(L, 1, &sz); + if (!text || sz <= 0) + return luaL_error(L, "Invalid text value."); + return sha_digest(L, text, sz, EVP_sha512()); +}; + +int lripemd160(lua_State *L) { + size_t sz = 0; + const char * text = luaL_checklstring(L, 1, &sz); + if (!text || sz <= 0) + return luaL_error(L, "Invalid text value."); + return sha_digest(L, text, sz, EVP_ripemd160()); +}; \ No newline at end of file diff --git a/luaclib/src/lcrypt/sm.c b/luaclib/src/lcrypt/sm.c new file mode 100644 index 00000000..84859fde --- /dev/null +++ b/luaclib/src/lcrypt/sm.c @@ -0,0 +1,369 @@ +#include "lcrypt.h" + +#if OPENSSL_VERSION_NUMBER < 0x10101000L || defined(OPENSSL_NO_SM2) || defined(OPENSSL_NO_SM3) || defined(OPENSSL_NO_SM4) + +/* 不支持的情况下使用需要抛出异常. */ +#define SM_THROW(L) luaL_error(L, "The current environment does not support the SM2/SM3/SM4 algorithm.") + +int lsm3(lua_State *L) { return SM_THROW(L); } +int lhmac_sm3(lua_State *L) { return SM_THROW(L); } + +int lsm4_cbc_encrypt(lua_State *L) { return SM_THROW(L); } +int lsm4_cbc_decrypt(lua_State *L) { return SM_THROW(L); } + +int lsm4_ecb_encrypt(lua_State *L) { return SM_THROW(L); } +int lsm4_ecb_decrypt(lua_State *L) { return SM_THROW(L); } + +int lsm4_ofb_encrypt(lua_State *L) { return SM_THROW(L); } +int lsm4_ofb_decrypt(lua_State *L) { return SM_THROW(L); } + +int lsm4_ctr_encrypt(lua_State *L) { return SM_THROW(L); } +int lsm4_ctr_decrypt(lua_State *L) { return SM_THROW(L); } + +int lsm2keygen(lua_State *L){ return SM_THROW(L); } + +int lsm2sign(lua_State *L) { return SM_THROW(L); } +int lsm2verify(lua_State *L) { return SM_THROW(L); } + +#else + +#ifndef SM3_BLOCK_SIZE + #define SM3_BLOCK_SIZE (32) +#endif + +int lsm3(lua_State *L) { + size_t textsize = 0; + const uint8_t * text = (const uint8_t *)luaL_checklstring(L, 1, &textsize); + EVP_MD_CTX *md_ctx = EVP_MD_CTX_new(); + EVP_DigestInit_ex(md_ctx, EVP_sm3(), NULL); + EVP_DigestUpdate(md_ctx, text, textsize); + uint32_t result_size = SM3_BLOCK_SIZE; + uint8_t result[result_size]; + EVP_DigestFinal_ex(md_ctx, result, &result_size); + EVP_MD_CTX_free(md_ctx); + lua_pushlstring(L, (const char *)result, SM3_BLOCK_SIZE); + return 1; +} + +int lhmac_sm3(lua_State *L) { + size_t key_sz = 0; + size_t text_sz = 0; + const char * key = luaL_checklstring(L, 1, &key_sz); + if (!key || key_sz <= 0) + return luaL_error(L, "Invalid key value."); + + const char * text = luaL_checklstring(L, 2, &text_sz); + if (!text || text_sz <= 0) + return luaL_error(L, "Invalid text value."); + + uint32_t result_len = SM3_BLOCK_SIZE; + uint8_t result[result_len]; + memset(result, 0x0, result_len); + HMAC(EVP_sm3(), (const unsigned char*)key, key_sz, (const unsigned char*)text, text_sz, result, &result_len); + lua_pushlstring(L, (const char *)result, result_len); + return 1; +} + +/* 加密函数 */ +static inline int sm4_encrypt(lua_State *L, const EVP_CIPHER *evp_md, const uint8_t *iv, const uint8_t *key, const uint8_t *text, size_t tsize) { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + if (!ctx) + return luaL_error(L, "allocate EVP failed."); + + if (1 != EVP_EncryptInit_ex(ctx, evp_md, NULL, key, iv)){ + EVP_CIPHER_CTX_cleanup(ctx); + EVP_CIPHER_CTX_free(ctx); + return luaL_error(L, "SM4_ENCRYPT_INIT failed."); + } + + EVP_CIPHER_CTX_set_padding(ctx, 1); + // printf("key_len = %d\n", EVP_CIPHER_CTX_key_length(ctx)); + // printf("iv_len = %d\n", EVP_CIPHER_CTX_iv_length(ctx)); + // printf("block_size = %d\n", EVP_CIPHER_CTX_block_size(ctx)); + + int out_size = tsize + EVP_MAX_BLOCK_LENGTH; + uint8_t *out = lua_newuserdata(L, out_size); + + + int update_len = out_size; + if (1 != EVP_EncryptUpdate(ctx, out, &update_len, text, tsize)){ + EVP_CIPHER_CTX_cleanup(ctx); + EVP_CIPHER_CTX_free(ctx); + return luaL_error(L, "SM4_ENCRYPT_UPDATE failed."); + } + + int final_len = out_size; + if (1 != EVP_EncryptFinal(ctx, out + update_len, &final_len)){ + EVP_CIPHER_CTX_cleanup(ctx); + EVP_CIPHER_CTX_free(ctx); + return luaL_error(L, "SM4_ENCRYPT_FINAL failed."); + } + + lua_pushlstring(L, (const char*)out, update_len + final_len); + EVP_CIPHER_CTX_cleanup(ctx); + EVP_CIPHER_CTX_free(ctx); + return 1; +} + +/* 解密函数 */ +static inline int sm4_decrypt(lua_State *L, const EVP_CIPHER *evp_md, const uint8_t *iv, const uint8_t *key, const uint8_t *cipher, size_t csize) { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + if (!ctx) + return luaL_error(L, "allocate EVP failed."); + + if (1 != EVP_DecryptInit_ex(ctx, evp_md, NULL, key, iv)){ + EVP_CIPHER_CTX_cleanup(ctx); + EVP_CIPHER_CTX_free(ctx); + return luaL_error(L, "SM4_DECRYPT_INIT failed."); + } + + EVP_CIPHER_CTX_set_padding(ctx, 1); + // printf("key_len = %d\n", EVP_CIPHER_CTX_key_length(ctx)); + // printf("iv_len = %d\n", EVP_CIPHER_CTX_iv_length(ctx)); + // printf("block_size = %d\n", EVP_CIPHER_CTX_block_size(ctx)); + + int out_size = csize + EVP_MAX_BLOCK_LENGTH; + uint8_t *out = lua_newuserdata(L, out_size); + + int update_len = out_size; + if (1 != EVP_DecryptUpdate(ctx, out, &update_len, cipher, csize)){ + EVP_CIPHER_CTX_cleanup(ctx); + EVP_CIPHER_CTX_free(ctx); + return luaL_error(L, "SM4_DECRYPT_UPDATE failed."); + } + + int final_len = out_size; + if (1 != EVP_DecryptFinal_ex(ctx, out + update_len, &final_len)){ + EVP_CIPHER_CTX_cleanup(ctx); + EVP_CIPHER_CTX_free(ctx); + return luaL_error(L, "SM4_DECRYPT_FINAL failed."); + } + + lua_pushlstring(L, (const char*)out, update_len + final_len); + EVP_CIPHER_CTX_cleanup(ctx); + EVP_CIPHER_CTX_free(ctx); + return 1; +} + +static inline const EVP_CIPHER* get_cipher(lua_State *L, int mode) { + switch(mode){ + case 1: + return EVP_sm4_cbc(); + case 2: + return EVP_sm4_ecb(); + case 3: + return EVP_sm4_ofb(); + case 4: + return EVP_sm4_ctr(); + } + return luaL_error(L, "Invalid SM4 CIPHER."), NULL; +} + +static inline int lua_getarg(lua_State *L, const char **iv, const char **key, const char **text, size_t *tsize) { + *key = luaL_checkstring(L, 1); + if (!key) + return luaL_error(L, "Invalid key"); + + size_t size = 0; + *text = luaL_checklstring(L, 2, &size); + if (!text) + return luaL_error(L, "Invalid text"); + *tsize = size; + + *iv = luaL_checkstring(L, 3); + if (!iv) + return luaL_error(L, "Invalid iv"); + return 1; +} + + +/* SM4加密函数的分组类型封装 */ +int lsm4_cbc_encrypt(lua_State *L) { + const char *iv; const char *key; const char *text; size_t tsize; + // lua_getarg(L, &iv, &key, &text, &tsize); + // return 1; + return lua_getarg(L, &iv, &key, &text, &tsize) && sm4_encrypt(L, get_cipher(L, 1), (const uint8_t *)iv, (const uint8_t *)key, (const uint8_t *)text, tsize); +} + +int lsm4_ecb_encrypt(lua_State *L) { + const char *iv; const char *key; const char *text; size_t tsize; + // lua_getarg(L, &iv, &key, &text, &tsize); + // return 1; + return lua_getarg(L, &iv, &key, &text, &tsize) && sm4_encrypt(L, get_cipher(L, 2), (const uint8_t *)iv, (const uint8_t *)key, (const uint8_t *)text, tsize); +} + +int lsm4_ofb_encrypt(lua_State *L) { + const char *iv; const char *key; const char *text; size_t tsize; + // lua_getarg(L, &iv, &key, &text, &tsize); + // return 1; + return lua_getarg(L, &iv, &key, &text, &tsize) && sm4_encrypt(L, get_cipher(L, 3), (const uint8_t *)iv, (const uint8_t *)key, (const uint8_t *)text, tsize); +} + +int lsm4_ctr_encrypt(lua_State *L) { + const char *iv; const char *key; const char *text; size_t tsize; + // lua_getarg(L, &iv, &key, &text, &tsize); + // return 1; + return lua_getarg(L, &iv, &key, &text, &tsize) && sm4_encrypt(L, get_cipher(L, 4), (const uint8_t *)iv, (const uint8_t *)key, (const uint8_t *)text, tsize); +} + + +/* SM4解密函数的分组类型封装 */ +int lsm4_cbc_decrypt(lua_State *L) { + const char *iv; const char *key; const char *cipher; size_t csize; + // lua_getarg(L, &iv, &key, &cipher, &csize); + // return 1; + return lua_getarg(L, &iv, &key, &cipher, &csize) && sm4_decrypt(L, get_cipher(L, 1), (const uint8_t *)iv, (const uint8_t *)key, (const uint8_t *)cipher, csize); +} + +int lsm4_ecb_decrypt(lua_State *L) { + const char *iv; const char *key; const char *cipher; size_t csize; + // lua_getarg(L, &iv, &key, &cipher, &csize); + // return 1; + return lua_getarg(L, &iv, &key, &cipher, &csize) && sm4_decrypt(L, get_cipher(L, 2), (const uint8_t *)iv, (const uint8_t *)key, (const uint8_t *)cipher, csize); +} + +int lsm4_ofb_decrypt(lua_State *L) { + const char *iv; const char *key; const char *cipher; size_t csize; + // lua_getarg(L, &iv, &key, &cipher, &csize); + // return 1; + return lua_getarg(L, &iv, &key, &cipher, &csize) && sm4_decrypt(L, get_cipher(L, 3), (const uint8_t *)iv, (const uint8_t *)key, (const uint8_t *)cipher, csize); +} + +int lsm4_ctr_decrypt(lua_State *L) { + const char *iv; const char *key; const char *cipher; size_t csize; + // lua_getarg(L, &iv, &key, &cipher, &csize); + // return 1; + return lua_getarg(L, &iv, &key, &cipher, &csize) && sm4_decrypt(L, get_cipher(L, 4), (const uint8_t *)iv, (const uint8_t *)key, (const uint8_t *)cipher, csize); +} + +// 读取私钥 +static inline EVP_PKEY* load_sm2prikey(lua_State *L) { + const char* private_keyname = luaL_checkstring(L, 1); + FILE *fp = fopen(private_keyname, "rb"); + if (!fp) + return luaL_error(L, "Can't find `SM`2 privatekey in [%s] file.", private_keyname), NULL; + + EVP_PKEY *sm2key = PEM_read_PrivateKey(fp, NULL, NULL, NULL); + if (!sm2key) { + fclose(fp); + return luaL_error(L, "Invalid `SM2` private key in [%s] file.", private_keyname), NULL; + } + fclose(fp); + return sm2key; +} + +// 读取公钥 +static inline EVP_PKEY* load_sm2pubkey(lua_State *L) { + const char* public_keyname = luaL_checkstring(L, 1); + FILE *fp = fopen(public_keyname, "rb"); + if (!fp) + return luaL_error(L, "Can't find `SM`2 publickey in [%s] file.", public_keyname), NULL; + + EVP_PKEY *sm2key = PEM_read_PUBKEY(fp, NULL, NULL, NULL); + if (!sm2key) { + fclose(fp); + return luaL_error(L, "Invalid `SM2` publickey key in [%s] file.", public_keyname), NULL; + } + fclose(fp); + return sm2key; +} + +/* 生成 SM2 `私钥`与`公钥` */ +static inline int sm2keygen(lua_State *L) { + const char* private_keyname = luaL_checkstring(L, 1); + const char* public_keyname = luaL_checkstring(L, 2); + + EVP_PKEY *sm2key = EVP_PKEY_new(); + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); + EVP_PKEY_keygen_init(pctx); + + if (!EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, NID_sm2) || !EVP_PKEY_CTX_set_ec_param_enc(pctx, OPENSSL_EC_NAMED_CURVE) || EVP_PKEY_keygen(pctx, &sm2key) <= 0 ){ + EVP_PKEY_free(sm2key); + EVP_PKEY_CTX_free(pctx); + return luaL_error(L, "Generate SM2 key error."); + } + + FILE *private_fp = fopen(private_keyname, "wb"); + FILE *public_fp = fopen(public_keyname, "wb"); + if (!private_fp || !public_fp) { + if (private_fp) + fclose(public_fp); + if (public_fp) + fclose(private_fp); + EVP_PKEY_free(sm2key); + EVP_PKEY_CTX_free(pctx); + return luaL_error(L, "Write file failed after generate SM2 key."); + } + + if (1 != PEM_write_PrivateKey(private_fp, sm2key, NULL, NULL, 0, NULL, NULL) || 1 != PEM_write_PUBKEY(public_fp, sm2key)) { + fclose(private_fp); + fclose(public_fp); + EVP_PKEY_free(sm2key); + EVP_PKEY_CTX_free(pctx); + return luaL_error(L, "`SM2` privatekey/publickey write file failed."); + } + + fclose(private_fp); + fclose(public_fp); + // 回收内存 + EVP_PKEY_free(sm2key); + EVP_PKEY_CTX_free(pctx); + return 0; +} + +int lsm2keygen(lua_State *L){ + return sm2keygen(L); +} + +int lsm2sign(lua_State *L){ + size_t tsize = 0; + const char* text = luaL_checklstring(L, 2, &tsize); + + EVP_PKEY *sm2key = load_sm2prikey(L); + // EVP_PKEY_set_alias_type(sm2key, EVP_PKEY_SM2); + + size_t osize = EVP_PKEY_size(sm2key); + const char *out = lua_newuserdata(L, osize); + + EVP_MD_CTX *md_ctx = EVP_MD_CTX_new(); + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new(sm2key, NULL); + EVP_MD_CTX_set_pkey_ctx(md_ctx, pctx); + EVP_DigestSignInit(md_ctx, NULL, EVP_sm3(), NULL, sm2key); + + EVP_DigestSign(md_ctx, (uint8_t*)out, &osize, (uint8_t*)text, tsize); + + lua_pushlstring(L, out, osize); + + EVP_PKEY_free(sm2key); + EVP_PKEY_CTX_free(pctx); + EVP_MD_CTX_free(md_ctx); + return 1; +} + +int lsm2verify(lua_State *L){ + size_t tsize = 0; + const char* text = luaL_checklstring(L, 2, &tsize); + + size_t csize = 0; + const char* cipher = luaL_checklstring(L, 3, &csize); + + EVP_PKEY *sm2key = load_sm2pubkey(L); + // EVP_PKEY_set_alias_type(sm2key, EVP_PKEY_SM2); + + EVP_MD_CTX *md_ctx = EVP_MD_CTX_new(); + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new(sm2key, NULL); + EVP_MD_CTX_set_pkey_ctx(md_ctx, pctx); + EVP_DigestVerifyInit(md_ctx, NULL, EVP_sm3(), NULL, sm2key); + + if (1 == EVP_DigestVerify(md_ctx, (uint8_t*)cipher, csize, (uint8_t*)text, tsize)) + lua_pushboolean(L, 1); + else + lua_pushboolean(L, 0); + + EVP_PKEY_free(sm2key); + EVP_PKEY_CTX_free(pctx); + EVP_MD_CTX_free(md_ctx); + return 1; +} + +#endif \ No newline at end of file diff --git a/luaclib/src/lcrypt/url.c b/luaclib/src/lcrypt/url.c new file mode 100644 index 00000000..a61f3e63 --- /dev/null +++ b/luaclib/src/lcrypt/url.c @@ -0,0 +1,72 @@ +#include "lcrypt.h" + +#define hex_char(ch) ({(uint8_t)((ch) > 9 ? (ch) + 55: (ch) + 48);}) + +#define is_normal_char(ch) ({((ch) >= 'a' && (ch) <= 'z') || ((ch) >= 'A' && (ch) <= 'Z') || ((ch) >= '0' && (ch) <= '9') ? 1 : 0;}) + +/* url编码 */ +int lurlencode(lua_State *L){ + size_t url_len = 0; + const char* url = luaL_checklstring(L, 1, &url_len); + if (!url) + return luaL_error(L, "Invalid url text"); + + luaL_Buffer convert_url; + luaL_buffinit(L, &convert_url); + + int index; + for (index = 0; index < url_len;) { + uint8_t ch = (uint8_t)url[index++]; + if (ch == (uint8_t)' ') { + luaL_addlstring(&convert_url, "%20", 3); + continue; + } + if (is_normal_char(ch) || strchr("-_.!~*'()", ch)) { + luaL_addchar(&convert_url, ch); + continue; + } + char vert[] = {'%', hex_char(((uint8_t)ch) >> 4), hex_char(((uint8_t)ch) & 0xF)}; + luaL_addlstring(&convert_url, (const char *)vert, 3); + } + + luaL_pushresult(&convert_url); + return 1; +} + +/* url解码 */ +int lurldecode(lua_State *L){ + size_t url_len = 0; + const char* url = luaL_checklstring(L, 1, &url_len); + if (!url) + return luaL_error(L, "Invalid url text"); + + luaL_Buffer convert_url; + luaL_buffinit(L, &convert_url); + + int index; + for (index = 0; index < url_len;) { + + uint8_t ch = (uint8_t)url[index++]; + if (ch != (uint8_t)'%') { + luaL_addchar(&convert_url, ch == (uint8_t)'+' ? (uint8_t)' ' : ch); + continue; + } + + char vert[2]; + if (index++ == url_len) { + luaL_addchar(&convert_url, '%'); + break; + } + vert[0] = url[index - 1]; + + if (index++ == url_len) { + luaL_addlstring(&convert_url, url + url_len - 2, 2); + break; + } + vert[1] = url[index - 1]; + luaL_addchar(&convert_url, (uint8_t)((vert[0] - 48 - ((vert[0] >= 'A') ? 7 : 0) - ((vert[0] >= 'a') ? 32 : 0)) * 16 + (vert[1] - 48 - ((vert[1] >= 'A') ? 7 : 0) - ((vert[1] >= 'a') ? 32 : 0)))); + } + + luaL_pushresult(&convert_url); + return 1; +} diff --git a/luaclib/src/lcrypt/uuid.c b/luaclib/src/lcrypt/uuid.c new file mode 100644 index 00000000..b7661553 --- /dev/null +++ b/luaclib/src/lcrypt/uuid.c @@ -0,0 +1,58 @@ +#include "lcrypt.h" + +#define UUID_V4_LENGTH 36 +#define GUID_V1_LENGTH 35 + +static inline const char* uuid_v4_gen(char *buffer) { + union { + struct { + uint32_t time_low; + uint16_t time_mid; + uint16_t time_hi_and_version; + uint8_t clk_seq_hi_res; + uint8_t clk_seq_low; + uint8_t node[6]; + }; + uint8_t __rnd[16]; + } uuid; + + RAND_bytes(uuid.__rnd, sizeof(uuid)); + + uuid.clk_seq_hi_res = (uint8_t) ((uuid.clk_seq_hi_res & 0x3F) | 0x80); + uuid.time_hi_and_version = (uint16_t) ((uuid.time_hi_and_version & 0x0FFF) | 0x4000); + + snprintf(buffer, UUID_V4_LENGTH + 1, "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + uuid.time_low, uuid.time_mid, uuid.time_hi_and_version, + uuid.clk_seq_hi_res, uuid.clk_seq_low, + uuid.node[0], uuid.node[1], uuid.node[2], + uuid.node[3], uuid.node[4], uuid.node[5] + ); + + buffer[UUID_V4_LENGTH] = '\0'; + return (const char*) buffer; +} + +static inline const char* guid_v1_gen(const uint8_t *hash, char *buffer, uint32_t hi, uint32_t low) { + + uint8_t rand_bytes[2]; RAND_bytes(rand_bytes, 2); + + snprintf(buffer, GUID_V1_LENGTH + 1, "%02x%02x%02x%02x%02x%02x%02x%02x-%08x-%04x-%02x%02x", + hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7], + hi, low, rand_bytes[0], rand_bytes[1] + ); + + buffer[GUID_V1_LENGTH] = '\0'; + return (const char*) buffer; +} + +int luuid(lua_State *L) { + lua_pushlstring(L, (const char *)uuid_v4_gen(lua_newuserdata(L, UUID_V4_LENGTH + 1)), UUID_V4_LENGTH); + return 1; +} + +int lguid(lua_State *L) { + if (1 != lhashkey(L)) + return luaL_error(L, "Invalid hashkey."); + lua_pushlstring(L, (const char *)guid_v1_gen((const uint8_t *)lua_tostring(L, 4), lua_newuserdata(L, GUID_V1_LENGTH + 1), lua_tointeger(L, 2), lua_tointeger(L, 3)), GUID_V1_LENGTH); + return 1; +} \ No newline at end of file diff --git a/luaclib/src/lfs/Makefile b/luaclib/src/lfs/Makefile new file mode 100644 index 00000000..32929c78 --- /dev/null +++ b/luaclib/src/lfs/Makefile @@ -0,0 +1,20 @@ +.PHONY : build rebuild clean + +default : + @echo "=======================================" + @echo "Please use 'make build' command to build it.." + @echo "Please use 'make rebuild' command to build it.." + @echo "Please use 'make clean' command to clean all." + @echo "=======================================" + +CC = cc + +INCLUDES += -I../../../src -I/usr/local/include +LIBS = -L../ -L../../ -L../../../ -L/usr/local/lib + +CFLAGS = -O3 -Wall -shared -fPIC +DLL = -lcore + +build: + @$(CC) -o lfs.so lfs.c $(INCLUDES) $(LIBS) $(CFLAGS) $(DLL) + @mv *.so ../../ diff --git a/luaclib/src/lfs/lfs.c b/luaclib/src/lfs/lfs.c new file mode 100644 index 00000000..ff770775 --- /dev/null +++ b/luaclib/src/lfs/lfs.c @@ -0,0 +1,1170 @@ +/* +** LuaFileSystem +** Copyright Kepler Project 2003 - 2020 +** (http://keplerproject.github.io/luafilesystem) +** +** File system manipulation library. +** This library offers these functions: +** lfs.attributes (filepath [, attributename | attributetable]) +** lfs.chdir (path) +** lfs.currentdir () +** lfs.dir (path) +** lfs.link (old, new[, symlink]) +** lfs.lock (fh, mode) +** lfs.lock_dir (path) +** lfs.mkdir (path) +** lfs.rmdir (path) +** lfs.setmode (filepath, mode) +** lfs.symlinkattributes (filepath [, attributename]) +** lfs.touch (filepath [, atime [, mtime]]) +** lfs.unlock (fh) +*/ + +#ifndef LFS_DO_NOT_USE_LARGE_FILE + #ifndef _WIN32 + #ifndef _AIX + #define _FILE_OFFSET_BITS 64 /* Linux, Solaris and HP-UX */ +#else + #define _LARGE_FILES 1 /* AIX */ + #endif + #endif +#endif + +#ifndef LFS_DO_NOT_USE_LARGE_FILE + #define _LARGEFILE64_SOURCE +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 + +#include +#include +#include +#include + +#ifdef __BORLANDC__ +#include +#else +#include +#endif + +#include + +/* MAX_PATH seems to be 260. Seems kind of small. Is there a better one? */ +#define LFS_MAXPATHLEN MAX_PATH + +#else + +#include +#include +#include +#include +#include +#include /* for MAXPATHLEN */ + +#ifdef MAXPATHLEN +#define LFS_MAXPATHLEN MAXPATHLEN +#else +#include /* for _POSIX_PATH_MAX */ +#define LFS_MAXPATHLEN _POSIX_PATH_MAX +#endif + +#endif + +#include +#include +#include + +#include "lfs.h" + +#define LFS_VERSION "1.8.0" +#define LFS_LIBNAME "lfs" + +#if LUA_VERSION_NUM >= 503 /* Lua 5.3+ */ + +#ifndef luaL_optlong +#define luaL_optlong luaL_optinteger +#endif + +#endif + +#if LUA_VERSION_NUM >= 502 +#define new_lib(L, l) (luaL_newlib(L, l)) +#else +#define new_lib(L, l) (lua_newtable(L), luaL_register(L, NULL, l)) +#endif + +/* Define 'strerror' for systems that do not implement it */ +#ifdef NO_STRERROR +#define strerror(_) "System unable to describe the error" +#endif + +#define DIR_METATABLE "directory metatable" +typedef struct dir_data { + int closed; +#ifdef _WIN32 + intptr_t hFile; + char pattern[MAX_PATH + 1]; +#else + DIR *dir; +#endif +} dir_data; + +#define LOCK_METATABLE "lock metatable" + +#ifdef _WIN32 + +#ifdef __BORLANDC__ +#define lfs_setmode(file, m) (setmode(_fileno(file), m)) +#define STAT_STRUCT struct stati64 +#else +#define lfs_setmode(file, m) (_setmode(_fileno(file), m)) +#define STAT_STRUCT struct _stati64 +#endif + +#ifndef _S_IFLNK +#define _S_IFLNK 0x400 +#endif + +#ifndef S_ISDIR +#define S_ISDIR(mode) (mode&_S_IFDIR) +#endif +#ifndef S_ISREG +#define S_ISREG(mode) (mode&_S_IFREG) +#endif +#ifndef S_ISLNK +#define S_ISLNK(mode) (mode&_S_IFLNK) +#endif +#ifndef S_ISSOCK +#define S_ISSOCK(mode) (0) +#endif +#ifndef S_ISFIFO +#define S_ISFIFO(mode) (0) +#endif +#ifndef S_ISCHR +#define S_ISCHR(mode) (mode&_S_IFCHR) +#endif +#ifndef S_ISBLK +#define S_ISBLK(mode) (0) +#endif + +#define STAT_FUNC _stati64 +#define LSTAT_FUNC lfs_win32_lstat + +#else + +#define _O_TEXT 0 +#define _O_BINARY 0 +#define lfs_setmode(file, m) ((void)file, (void)m, 0) +#define STAT_STRUCT struct stat +#define STAT_FUNC stat +#define LSTAT_FUNC lstat + +#endif + +#ifdef _WIN32 +#define lfs_mkdir _mkdir +#else +#define lfs_mkdir(path) (mkdir((path), \ + S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH)) +#endif + +#ifdef _WIN32 + +int lfs_win32_pusherror(lua_State * L) +{ + int en = GetLastError(); + lua_pushnil(L); + if (en == ERROR_FILE_EXISTS || en == ERROR_SHARING_VIOLATION) + lua_pushstring(L, "File exists"); + else + lua_pushstring(L, strerror(en)); + return 2; +} + +#define TICKS_PER_SECOND 10000000 +#define EPOCH_DIFFERENCE 11644473600LL +time_t windowsToUnixTime(FILETIME ft) +{ + ULARGE_INTEGER uli; + uli.LowPart = ft.dwLowDateTime; + uli.HighPart = ft.dwHighDateTime; + return (time_t) (uli.QuadPart / TICKS_PER_SECOND - EPOCH_DIFFERENCE); +} + +int lfs_win32_lstat(const char *path, STAT_STRUCT * buffer) +{ + WIN32_FILE_ATTRIBUTE_DATA win32buffer; + if (GetFileAttributesEx(path, GetFileExInfoStandard, &win32buffer)) { + if (!(win32buffer.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { + return STAT_FUNC(path, buffer); + } + buffer->st_mode = _S_IFLNK; + buffer->st_dev = 0; + buffer->st_ino = 0; + buffer->st_nlink = 0; + buffer->st_uid = 0; + buffer->st_gid = 0; + buffer->st_rdev = 0; + buffer->st_atime = windowsToUnixTime(win32buffer.ftLastAccessTime); + buffer->st_mtime = windowsToUnixTime(win32buffer.ftLastWriteTime); + buffer->st_ctime = windowsToUnixTime(win32buffer.ftCreationTime); + buffer->st_size = 0; + return 0; + } else { + return 1; + } +} + +#endif + +/* +** Utility functions +*/ +static int pusherror(lua_State * L, const char *info) +{ + lua_pushnil(L); + if (info == NULL) + lua_pushstring(L, strerror(errno)); + else + lua_pushfstring(L, "%s: %s", info, strerror(errno)); + lua_pushinteger(L, errno); + return 3; +} + +static int pushresult(lua_State * L, int res, const char *info) +{ + if (res == -1) { + return pusherror(L, info); + } else { + lua_pushboolean(L, 1); + return 1; + } +} + + +/* +** This function changes the working (current) directory +*/ +static int change_dir(lua_State * L) +{ + const char *path = luaL_checkstring(L, 1); + if (chdir(path)) { + lua_pushnil(L); + lua_pushfstring(L, "Unable to change working directory to '%s'\n%s\n", + path, chdir_error); + return 2; + } else { + lua_pushboolean(L, 1); + return 1; + } +} + +/* +** This function returns the current directory +** If unable to get the current directory, it returns nil +** and a string describing the error +*/ +static int get_dir(lua_State * L) +{ +#ifdef NO_GETCWD + lua_pushnil(L); + lua_pushstring(L, "Function 'getcwd' not provided by system"); + return 2; +#else + char *path = NULL; + /* Passing (NULL, 0) is not guaranteed to work. + Use a temp buffer and size instead. */ + size_t size = LFS_MAXPATHLEN; /* initial buffer size */ + int result; + while (1) { + char *path2 = realloc(path, size); + if (!path2) { /* failed to allocate */ + result = pusherror(L, "get_dir realloc() failed"); + break; + } + path = path2; + if (getcwd(path, size) != NULL) { + /* success, push the path to the Lua stack */ + lua_pushstring(L, path); + result = 1; + break; + } + if (errno != ERANGE) { /* unexpected error */ + result = pusherror(L, "get_dir getcwd() failed"); + break; + } + /* ERANGE = insufficient buffer capacity, double size and retry */ + size *= 2; + } + free(path); + return result; +#endif +} + +/* +** Check if the given element on the stack is a file and returns it. +*/ +static FILE *check_file(lua_State * L, int idx, const char *funcname) +{ +#if LUA_VERSION_NUM == 501 + FILE **fh = (FILE **) luaL_checkudata(L, idx, "FILE*"); + if (*fh == NULL) { + luaL_error(L, "%s: closed file", funcname); + return 0; + } else + return *fh; +#elif LUA_VERSION_NUM >= 502 && LUA_VERSION_NUM <= 504 + luaL_Stream *fh = (luaL_Stream *) luaL_checkudata(L, idx, "FILE*"); + if (fh->closef == 0 || fh->f == NULL) { + luaL_error(L, "%s: closed file", funcname); + return 0; + } else + return fh->f; +#else +#error unsupported Lua version +#endif +} + + +/* +** +*/ +static int _file_lock(lua_State * L, FILE * fh, const char *mode, + const long start, long len, const char *funcname) +{ + int code; +#ifdef _WIN32 + /* lkmode valid values are: + LK_LOCK Locks the specified bytes. If the bytes cannot be locked, + the program immediately tries again after 1 second. + If, after 10 attempts, the bytes cannot be locked, + the constant returns an error. + LK_NBLCK Locks the specified bytes. If the bytes cannot be locked, + the constant returns an error. + LK_NBRLCK Same as _LK_NBLCK. + LK_RLCK Same as _LK_LOCK. + LK_UNLCK Unlocks the specified bytes, which must have been + previously locked. + Regions should be locked only briefly and should be unlocked + before closing a file or exiting the program. + http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_crt__locking.asp + */ + int lkmode; + switch (*mode) { + case 'r': + lkmode = LK_NBLCK; + break; + case 'w': + lkmode = LK_NBLCK; + break; + case 'u': + lkmode = LK_UNLCK; + break; + default: + return luaL_error(L, "%s: invalid mode", funcname); + } + if (!len) { + fseek(fh, 0L, SEEK_END); + len = ftell(fh); + } + fseek(fh, start, SEEK_SET); +#ifdef __BORLANDC__ + code = locking(fileno(fh), lkmode, len); +#else + code = _locking(fileno(fh), lkmode, len); +#endif +#else + struct flock f; + switch (*mode) { + case 'w': + f.l_type = F_WRLCK; + break; + case 'r': + f.l_type = F_RDLCK; + break; + case 'u': + f.l_type = F_UNLCK; + break; + default: + return luaL_error(L, "%s: invalid mode", funcname); + } + f.l_whence = SEEK_SET; + f.l_start = (off_t) start; + f.l_len = (off_t) len; + code = fcntl(fileno(fh), F_SETLK, &f); +#endif + return (code != -1); +} + +#ifdef _WIN32 +typedef struct lfs_Lock { + HANDLE fd; +} lfs_Lock; +static int lfs_lock_dir(lua_State * L) +{ + size_t pathl; + HANDLE fd; + lfs_Lock *lock; + char *ln; + const char *lockfile = "/lockfile.lfs"; + const char *path = luaL_checklstring(L, 1, &pathl); + ln = (char *) malloc(pathl + strlen(lockfile) + 1); + if (!ln) { + lua_pushnil(L); + lua_pushstring(L, strerror(errno)); + return 2; + } + strcpy(ln, path); + strcat(ln, lockfile); + fd = CreateFile(ln, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL); + free(ln); + if (fd == INVALID_HANDLE_VALUE) { + return lfs_win32_pusherror(L); + } + lock = (lfs_Lock *) lua_newuserdata(L, sizeof(lfs_Lock)); + lock->fd = fd; + luaL_getmetatable(L, LOCK_METATABLE); + lua_setmetatable(L, -2); + return 1; +} + +static int lfs_unlock_dir(lua_State * L) +{ + lfs_Lock *lock = (lfs_Lock *) luaL_checkudata(L, 1, LOCK_METATABLE); + if (lock->fd != INVALID_HANDLE_VALUE) { + CloseHandle(lock->fd); + lock->fd = INVALID_HANDLE_VALUE; + } + return 0; +} +#else +typedef struct lfs_Lock { + char *ln; +} lfs_Lock; +static int lfs_lock_dir(lua_State * L) +{ + lfs_Lock *lock; + size_t pathl; + char *ln; + const char *lockfile = "/lockfile.lfs"; + const char *path = luaL_checklstring(L, 1, &pathl); + lock = (lfs_Lock *) lua_newuserdata(L, sizeof(lfs_Lock)); + ln = (char *) malloc(pathl + strlen(lockfile) + 1); + if (!ln) { + lua_pushnil(L); + lua_pushstring(L, strerror(errno)); + return 2; + } + strcpy(ln, path); + strcat(ln, lockfile); + if (symlink("lock", ln) == -1) { + free(ln); + lua_pushnil(L); + lua_pushstring(L, strerror(errno)); + return 2; + } + lock->ln = ln; + luaL_getmetatable(L, LOCK_METATABLE); + lua_setmetatable(L, -2); + return 1; +} + +static int lfs_unlock_dir(lua_State * L) +{ + lfs_Lock *lock = (lfs_Lock *) luaL_checkudata(L, 1, LOCK_METATABLE); + if (lock->ln) { + unlink(lock->ln); + free(lock->ln); + lock->ln = NULL; + } + return 0; +} +#endif + +static int lfs_g_setmode(lua_State * L, FILE * f, int arg) +{ + static const int mode[] = { _O_BINARY, _O_TEXT }; + static const char *const modenames[] = { "binary", "text", NULL }; + int op = luaL_checkoption(L, arg, NULL, modenames); + int res = lfs_setmode(f, mode[op]); + if (res != -1) { + int i; + lua_pushboolean(L, 1); + for (i = 0; modenames[i] != NULL; i++) { + if (mode[i] == res) { + lua_pushstring(L, modenames[i]); + return 2; + } + } + lua_pushnil(L); + return 2; + } else { + return pusherror(L, NULL); + } +} + +static int lfs_f_setmode(lua_State * L) +{ + return lfs_g_setmode(L, check_file(L, 1, "setmode"), 2); +} + +/* +** Locks a file. +** @param #1 File handle. +** @param #2 String with lock mode ('w'rite, 'r'ead). +** @param #3 Number with start position (optional). +** @param #4 Number with length (optional). +*/ +static int file_lock(lua_State * L) +{ + FILE *fh = check_file(L, 1, "lock"); + const char *mode = luaL_checkstring(L, 2); + const long start = (long) luaL_optinteger(L, 3, 0); + long len = (long) luaL_optinteger(L, 4, 0); + if (_file_lock(L, fh, mode, start, len, "lock")) { + lua_pushboolean(L, 1); + return 1; + } else { + lua_pushnil(L); + lua_pushfstring(L, "%s", strerror(errno)); + return 2; + } +} + + +/* +** Unlocks a file. +** @param #1 File handle. +** @param #2 Number with start position (optional). +** @param #3 Number with length (optional). +*/ +static int file_unlock(lua_State * L) +{ + FILE *fh = check_file(L, 1, "unlock"); + const long start = (long) luaL_optinteger(L, 2, 0); + long len = (long) luaL_optinteger(L, 3, 0); + if (_file_lock(L, fh, "u", start, len, "unlock")) { + lua_pushboolean(L, 1); + return 1; + } else { + lua_pushnil(L); + lua_pushfstring(L, "%s", strerror(errno)); + return 2; + } +} + + +/* +** Creates a link. +** @param #1 Object to link to. +** @param #2 Name of link. +** @param #3 True if link is symbolic (optional). +*/ +static int make_link(lua_State * L) +{ + const char *oldpath = luaL_checkstring(L, 1); + const char *newpath = luaL_checkstring(L, 2); +#ifndef _WIN32 + return pushresult(L, + (lua_toboolean(L, 3) ? symlink : link) (oldpath, + newpath), + NULL); +#else + int symbolic = lua_toboolean(L, 3); + STAT_STRUCT oldpathinfo; + int is_dir = 0; + if (STAT_FUNC(oldpath, &oldpathinfo) == 0) { + is_dir = S_ISDIR(oldpathinfo.st_mode) != 0; + } + if (!symbolic && is_dir) { + lua_pushnil(L); + lua_pushstring(L, + "hard links to directories are not supported on Windows"); + return 2; + } + + int result = symbolic ? CreateSymbolicLink(newpath, oldpath, is_dir) + : CreateHardLink(newpath, oldpath, NULL); + + if (result) { + return pushresult(L, result, NULL); + } else { + lua_pushnil(L); + lua_pushstring(L, symbolic ? "make_link CreateSymbolicLink() failed" + : "make_link CreateHardLink() failed"); + return 2; + } +#endif +} + + +/* +** Creates a directory. +** @param #1 Directory path. +*/ +static int make_dir(lua_State * L) +{ + const char *path = luaL_checkstring(L, 1); + return pushresult(L, lfs_mkdir(path), NULL); +} + + +/* +** Removes a directory. +** @param #1 Directory path. +*/ +static int remove_dir(lua_State * L) +{ + const char *path = luaL_checkstring(L, 1); + return pushresult(L, rmdir(path), NULL); +} + + +/* +** Directory iterator +*/ +static int dir_iter(lua_State * L) +{ +#ifdef _WIN32 + struct _finddata_t c_file; +#else + struct dirent *entry; +#endif + dir_data *d = (dir_data *) luaL_checkudata(L, 1, DIR_METATABLE); + luaL_argcheck(L, d->closed == 0, 1, "closed directory"); +#ifdef _WIN32 + if (d->hFile == 0L) { /* first entry */ + if ((d->hFile = _findfirst(d->pattern, &c_file)) == -1L) { + lua_pushnil(L); + lua_pushstring(L, strerror(errno)); + d->closed = 1; + return 2; + } else { + lua_pushstring(L, c_file.name); + return 1; + } + } else { /* next entry */ + if (_findnext(d->hFile, &c_file) == -1L) { + /* no more entries => close directory */ + _findclose(d->hFile); + d->closed = 1; + return 0; + } else { + lua_pushstring(L, c_file.name); + return 1; + } + } +#else + if ((entry = readdir(d->dir)) != NULL) { + lua_pushstring(L, entry->d_name); + return 1; + } else { + /* no more entries => close directory */ + closedir(d->dir); + d->closed = 1; + return 0; + } +#endif +} + + +/* +** Closes directory iterators +*/ +static int dir_close(lua_State * L) +{ + dir_data *d = (dir_data *) lua_touserdata(L, 1); +#ifdef _WIN32 + if (!d->closed && d->hFile) { + _findclose(d->hFile); + } +#else + if (!d->closed && d->dir) { + closedir(d->dir); + } +#endif + d->closed = 1; + return 0; +} + + +/* +** Factory of directory iterators +*/ +static int dir_iter_factory(lua_State * L) +{ + const char *path = luaL_checkstring(L, 1); + dir_data *d; + lua_pushcfunction(L, dir_iter); + d = (dir_data *) lua_newuserdata(L, sizeof(dir_data)); + luaL_getmetatable(L, DIR_METATABLE); + lua_setmetatable(L, -2); + d->closed = 0; +#ifdef _WIN32 + d->hFile = 0L; + if (strlen(path) > MAX_PATH - 2) + luaL_error(L, "path too long: %s", path); + else + sprintf(d->pattern, "%s/*", path); +#else + d->dir = opendir(path); + if (d->dir == NULL) + luaL_error(L, "cannot open %s: %s", path, strerror(errno)); +#endif +#if LUA_VERSION_NUM >= 504 + lua_pushnil(L); + lua_pushvalue(L, -2); + return 4; +#else + return 2; +#endif +} + + +/* +** Creates directory metatable. +*/ +static int dir_create_meta(lua_State * L) +{ + luaL_newmetatable(L, DIR_METATABLE); + + /* Method table */ + lua_newtable(L); + lua_pushcfunction(L, dir_iter); + lua_setfield(L, -2, "next"); + lua_pushcfunction(L, dir_close); + lua_setfield(L, -2, "close"); + + /* Metamethods */ + lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, dir_close); + lua_setfield(L, -2, "__gc"); + +#if LUA_VERSION_NUM >= 504 + lua_pushcfunction(L, dir_close); + lua_setfield(L, -2, "__close"); +#endif + return 1; +} + + +/* +** Creates lock metatable. +*/ +static int lock_create_meta(lua_State * L) +{ + luaL_newmetatable(L, LOCK_METATABLE); + + /* Method table */ + lua_newtable(L); + lua_pushcfunction(L, lfs_unlock_dir); + lua_setfield(L, -2, "free"); + + /* Metamethods */ + lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, lfs_unlock_dir); + lua_setfield(L, -2, "__gc"); + return 1; +} + + +/* +** Convert the inode protection mode to a string. +*/ +#ifdef _WIN32 +static const char *mode2string(unsigned short mode) +{ +#else +static const char *mode2string(mode_t mode) +{ +#endif + if (S_ISREG(mode)) + return "file"; + else if (S_ISDIR(mode)) + return "directory"; + else if (S_ISLNK(mode)) + return "link"; + else if (S_ISSOCK(mode)) + return "socket"; + else if (S_ISFIFO(mode)) + return "named pipe"; + else if (S_ISCHR(mode)) + return "char device"; + else if (S_ISBLK(mode)) + return "block device"; + else + return "other"; +} + + +/* +** Set access time and modification values for a file. +** @param #1 File path. +** @param #2 Access time in seconds, current time is used if missing. +** @param #3 Modification time in seconds, access time is used if missing. +*/ +static int file_utime(lua_State * L) +{ + const char *file = luaL_checkstring(L, 1); + struct utimbuf utb, *buf; + + if (lua_gettop(L) == 1) /* set to current date/time */ + buf = NULL; + else { + utb.actime = (time_t) luaL_optnumber(L, 2, 0); + utb.modtime = (time_t) luaL_optinteger(L, 3, utb.actime); + buf = &utb; + } + + return pushresult(L, utime(file, buf), NULL); +} + + +/* inode protection mode */ +static void push_st_mode(lua_State * L, STAT_STRUCT * info) +{ + lua_pushstring(L, mode2string(info->st_mode)); +} + +/* device inode resides on */ +static void push_st_dev(lua_State * L, STAT_STRUCT * info) +{ + lua_pushinteger(L, (lua_Integer) info->st_dev); +} + +/* inode's number */ +static void push_st_ino(lua_State * L, STAT_STRUCT * info) +{ + lua_pushinteger(L, (lua_Integer) info->st_ino); +} + +/* number of hard links to the file */ +static void push_st_nlink(lua_State * L, STAT_STRUCT * info) +{ + lua_pushinteger(L, (lua_Integer) info->st_nlink); +} + +/* user-id of owner */ +static void push_st_uid(lua_State * L, STAT_STRUCT * info) +{ + lua_pushinteger(L, (lua_Integer) info->st_uid); +} + +/* group-id of owner */ +static void push_st_gid(lua_State * L, STAT_STRUCT * info) +{ + lua_pushinteger(L, (lua_Integer) info->st_gid); +} + +/* device type, for special file inode */ +static void push_st_rdev(lua_State * L, STAT_STRUCT * info) +{ + lua_pushinteger(L, (lua_Integer) info->st_rdev); +} + +/* time of last access */ +static void push_st_atime(lua_State * L, STAT_STRUCT * info) +{ + lua_pushinteger(L, (lua_Integer) info->st_atime); +} + +/* time of last data modification */ +static void push_st_mtime(lua_State * L, STAT_STRUCT * info) +{ + lua_pushinteger(L, (lua_Integer) info->st_mtime); +} + +/* time of last file status change */ +static void push_st_ctime(lua_State * L, STAT_STRUCT * info) +{ + lua_pushinteger(L, (lua_Integer) info->st_ctime); +} + +/* file size, in bytes */ +static void push_st_size(lua_State * L, STAT_STRUCT * info) +{ + lua_pushinteger(L, (lua_Integer) info->st_size); +} + +#ifndef _WIN32 +/* blocks allocated for file */ +static void push_st_blocks(lua_State * L, STAT_STRUCT * info) +{ + lua_pushinteger(L, (lua_Integer) info->st_blocks); +} + +/* optimal file system I/O blocksize */ +static void push_st_blksize(lua_State * L, STAT_STRUCT * info) +{ + lua_pushinteger(L, (lua_Integer) info->st_blksize); +} +#endif + + /* + ** Convert the inode protection mode to a permission list. + */ + +#ifdef _WIN32 +static const char *perm2string(unsigned short mode) +{ + static char perms[10] = "---------"; + int i; + for (i = 0; i < 9; i++) + perms[i] = '-'; + if (mode & _S_IREAD) { + perms[0] = 'r'; + perms[3] = 'r'; + perms[6] = 'r'; + } + if (mode & _S_IWRITE) { + perms[1] = 'w'; + perms[4] = 'w'; + perms[7] = 'w'; + } + if (mode & _S_IEXEC) { + perms[2] = 'x'; + perms[5] = 'x'; + perms[8] = 'x'; + } + return perms; +} +#else +static const char *perm2string(mode_t mode) +{ + static char perms[10] = "---------"; + int i; + for (i = 0; i < 9; i++) + perms[i] = '-'; + if (mode & S_IRUSR) + perms[0] = 'r'; + if (mode & S_IWUSR) + perms[1] = 'w'; + if (mode & S_IXUSR) + perms[2] = 'x'; + if (mode & S_IRGRP) + perms[3] = 'r'; + if (mode & S_IWGRP) + perms[4] = 'w'; + if (mode & S_IXGRP) + perms[5] = 'x'; + if (mode & S_IROTH) + perms[6] = 'r'; + if (mode & S_IWOTH) + perms[7] = 'w'; + if (mode & S_IXOTH) + perms[8] = 'x'; + return perms; +} +#endif + +/* permssions string */ +static void push_st_perm(lua_State * L, STAT_STRUCT * info) +{ + lua_pushstring(L, perm2string(info->st_mode)); +} + +typedef void (*_push_function)(lua_State * L, STAT_STRUCT * info); + +struct _stat_members { + const char *name; + _push_function push; +}; + +struct _stat_members members[] = { + { "mode", push_st_mode }, + { "dev", push_st_dev }, + { "ino", push_st_ino }, + { "nlink", push_st_nlink }, + { "uid", push_st_uid }, + { "gid", push_st_gid }, + { "rdev", push_st_rdev }, + { "access", push_st_atime }, + { "modification", push_st_mtime }, + { "change", push_st_ctime }, + { "size", push_st_size }, + { "permissions", push_st_perm }, +#ifndef _WIN32 + { "blocks", push_st_blocks }, + { "blksize", push_st_blksize }, +#endif + { NULL, NULL } +}; + +/* +** Get file or symbolic link information +*/ +static int _file_info_(lua_State * L, + int (*st)(const char *, STAT_STRUCT *)) +{ + STAT_STRUCT info; + const char *file = luaL_checkstring(L, 1); + int i; + + if (st(file, &info)) { + lua_pushnil(L); + lua_pushfstring(L, "cannot obtain information from file '%s': %s", + file, strerror(errno)); + lua_pushinteger(L, errno); + return 3; + } + if (lua_isstring(L, 2)) { + const char *member = lua_tostring(L, 2); + for (i = 0; members[i].name; i++) { + if (strcmp(members[i].name, member) == 0) { + /* push member value and return */ + members[i].push(L, &info); + return 1; + } + } + /* member not found */ + return luaL_error(L, "invalid attribute name '%s'", member); + } + /* creates a table if none is given, removes extra arguments */ + lua_settop(L, 2); + if (!lua_istable(L, 2)) { + lua_newtable(L); + } + /* stores all members in table on top of the stack */ + for (i = 0; members[i].name; i++) { + lua_pushstring(L, members[i].name); + members[i].push(L, &info); + lua_rawset(L, -3); + } + return 1; +} + + +/* +** Get file information using stat. +*/ +static int file_info(lua_State * L) +{ + return _file_info_(L, STAT_FUNC); +} + + +/* +** Push the symlink target to the top of the stack. +** Assumes the file name is at position 1 of the stack. +** Returns 1 if successful (with the target on top of the stack), +** 0 on failure (with stack unchanged, and errno set). +*/ +static int push_link_target(lua_State * L) +{ + const char *file = luaL_checkstring(L, 1); +#ifdef _WIN32 + HANDLE h = CreateFile(file, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (h == INVALID_HANDLE_VALUE) { + return lfs_win32_pusherror(L); + } +#endif + char *target = NULL; + int tsize, size = 256; /* size = initial buffer capacity */ + int ok = 0; + while (!ok) { + char *target2 = realloc(target, size); + if (!target2) { /* failed to allocate */ + break; + } + target = target2; +#ifdef _WIN32 + tsize = GetFinalPathNameByHandle(h, target, size, FILE_NAME_OPENED); +#else + tsize = readlink(file, target, size); +#endif + if (tsize < 0) { /* a readlink() error occurred */ + break; + } + if (tsize < size) { +#ifdef _WIN32 + if (tsize > 4 && strncmp(target, "\\\\?\\", 4) == 0) { + memmove_s(target, tsize - 3, target + 4, tsize - 3); + tsize -= 4; + } +#endif + ok = 1; + break; + } + /* possibly truncated readlink() result, double size and retry */ + size *= 2; + } + if (ok) { + target[tsize] = '\0'; + lua_pushlstring(L, target, tsize); + } +#ifdef _WIN32 + CloseHandle(h); +#endif + free(target); + return ok; +} + +/* +** Get symbolic link information using lstat. +*/ +static int link_info (lua_State *L) { + int ret; + if (lua_isstring(L, 2) && (strcmp(lua_tostring(L, 2), "target") == 0)) { + int ok = push_link_target(L); + return ok ? 1 : pusherror(L, "could not obtain link target"); + } + ret = _file_info_(L, LSTAT_FUNC); + if (ret == 1 && lua_type(L, -1) == LUA_TTABLE) { + int ok = push_link_target(L); + if (ok) { + lua_setfield(L, -2, "target"); + } + } + return ret; +} + + +/* +** Assumes the table is on top of the stack. +*/ +static void set_info (lua_State *L) { + lua_pushliteral(L, "Copyright (C) 2003-2020 Kepler Project"); + lua_setfield(L, -2, "_COPYRIGHT"); + lua_pushliteral(L, "LuaFileSystem is a Lua library developed to complement the set of functions related to file systems offered by the standard Lua distribution"); + lua_setfield(L, -2, "_DESCRIPTION"); + lua_pushliteral(L, "LuaFileSystem " LFS_VERSION); + lua_setfield(L, -2, "_VERSION"); +} + + +static const struct luaL_Reg fslib[] = { + {"attributes", file_info}, + {"chdir", change_dir}, + {"currentdir", get_dir}, + {"dir", dir_iter_factory}, + {"link", make_link}, + {"lock", file_lock}, + {"mkdir", make_dir}, + {"rmdir", remove_dir}, + {"symlinkattributes", link_info}, + {"setmode", lfs_f_setmode}, + {"touch", file_utime}, + {"unlock", file_unlock}, + {"lock_dir", lfs_lock_dir}, + {NULL, NULL}, +}; + +LFS_EXPORT int luaopen_lfs (lua_State *L) { + dir_create_meta (L); + lock_create_meta (L); + new_lib (L, fslib); + lua_pushvalue(L, -1); + lua_setglobal(L, LFS_LIBNAME); + set_info (L); + return 1; +} diff --git a/luaclib/src/lfs/lfs.h b/luaclib/src/lfs/lfs.h new file mode 100644 index 00000000..fe8f0a45 --- /dev/null +++ b/luaclib/src/lfs/lfs.h @@ -0,0 +1,35 @@ +/* +** LuaFileSystem +** Copyright Kepler Project 2003 - 2020 +** (http://keplerproject.github.io/luafilesystem) +*/ + +/* Define 'chdir' for systems that do not implement it */ +#ifdef NO_CHDIR + #define chdir(p) (-1) + #define chdir_error "Function 'chdir' not provided by system" +#else + #define chdir_error strerror(errno) +#endif + +#ifdef _WIN32 + #define chdir(p) (_chdir(p)) + #define getcwd(d, s) (_getcwd(d, s)) + #define rmdir(p) (_rmdir(p)) + #define LFS_EXPORT __declspec (dllexport) + #ifndef fileno + #define fileno(f) (_fileno(f)) + #endif +#else + #define LFS_EXPORT +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +LFS_EXPORT int luaopen_lfs(lua_State * L); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/luaclib/src/lhttpparser/Makefile b/luaclib/src/lhttpparser/Makefile index 10b9bd18..437e080b 100644 --- a/luaclib/src/lhttpparser/Makefile +++ b/luaclib/src/lhttpparser/Makefile @@ -3,32 +3,21 @@ default : @echo "=======================================" @echo "Please use 'make build' command to build it.." + @echo "Please use 'make rebuild' command to build it.." @echo "Please use 'make clean' command to clean all." @echo "=======================================" CC = cc -# 如果确认是intel的CPU并且有SSE4可以自行开启这个编译参数, 经过测试http解析速度至少快2倍 -# 这里为了兼容AMD CPU不作为默认开启选项 -# CFLAGS = -O3 -Wall -DNDEBUG -fPIC -msse4 +# 开启请先确认是否支持指令集, 经过测试http解析速度至少快2倍 +# CFLAGS = -O3 -Wall -shared -fPIC -msse4 -CFLAGS = -O3 -Wall -DNDEBUG -fPIC +INCLUDES += -I../../../src -I/usr/local/include +LIBS = -L../ -L../../ -L../../../ -L/usr/local/lib -INCLUDE = -I/usr/local/include -LIB = -L/usr/local/lib +CFLAGS = -O3 -Wall -shared -fPIC +DLL = -lcore -httpparser.o: httpparser.c httpparser.h -lhttpparser.o: lhttpparser.c httpparser.h - -OBJS = httpparser.o lhttpparser.o - -build: $(OBJS) - $(CC) -o httpparser.so $(OBJS) $(INCLUDE) $(LIB) -O3 -shared -fPIC -lcore - mv httpparser.so ../../ - -rebuild: $(OBJS) - $(CC) -o httpparser.so $(OBJS) $(INCLUDE) $(LIB) -O3 -shared -fPIC -lcore - mv httpparser.so ../../ - -clean: - rm -rf *.o *.so +build: + @$(CC) -o lhttpparser.so httpparser.c lhttpparser.c $(INCLUDES) $(LIBS) $(CFLAGS) $(DLL) + @mv *.so ../../ diff --git a/luaclib/src/lhttpparser/httpparser.c b/luaclib/src/lhttpparser/httpparser.c index 57d8c4ea..c069e505 100644 --- a/luaclib/src/lhttpparser/httpparser.c +++ b/luaclib/src/lhttpparser/httpparser.c @@ -36,8 +36,6 @@ #endif #include "httpparser.h" -/* $Id$ */ - #if __GNUC__ >= 3 #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) @@ -73,9 +71,9 @@ #define ADVANCE_TOKEN(tok, toklen) \ do { \ const char *tok_start = buf; \ - static const char ALIGNED(16) ranges2[] = "\000\040\177\177"; \ + static const char ALIGNED(16) ranges2[16] = "\000\040\177\177"; \ int found2; \ - buf = findchar_fast(buf, buf_end, ranges2, sizeof(ranges2) - 1, &found2); \ + buf = findchar_fast(buf, buf_end, ranges2, 4, &found2); \ if (!found2) { \ CHECK_EOF(); \ } \ @@ -138,15 +136,11 @@ static const char *get_token_to_eol(const char *buf, const char *buf_end, const const char *token_start = buf; #ifdef __SSE4_2__ - static const char ranges1[] = "\0\010" - /* allow HT */ - "\012\037" - /* allow SP and up to but not including DEL */ - "\177\177" - /* allow chars w. MSB set */ - ; + static const char ALIGNED(16) ranges1[16] = "\0\010" /* allow HT */ + "\012\037" /* allow SP and up to but not including DEL */ + "\177\177"; /* allow chars w. MSB set */ int found; - buf = findchar_fast(buf, buf_end, ranges1, sizeof(ranges1) - 1, &found); + buf = findchar_fast(buf, buf_end, ranges1, 6, &found); if (found) goto FOUND_CTL; #else @@ -247,6 +241,41 @@ static const char *is_complete(const char *buf, const char *buf_end, size_t last *valp_ += res_; \ } while (0) +/* returned pointer is always within [buf, buf_end), or null */ +static const char *parse_token(const char *buf, const char *buf_end, const char **token, size_t *token_len, char next_char, + int *ret) +{ + /* We use pcmpestri to detect non-token characters. This instruction can take no more than eight character ranges (8*2*8=128 + * bits that is the size of a SSE register). Due to this restriction, characters `|` and `~` are handled in the slow loop. */ + static const char ALIGNED(16) ranges[] = "\x00 " /* control chars and up to SP */ + "\"\"" /* 0x22 */ + "()" /* 0x28,0x29 */ + ",," /* 0x2c */ + "//" /* 0x2f */ + ":@" /* 0x3a-0x40 */ + "[]" /* 0x5b-0x5d */ + "{\xff"; /* 0x7b-0xff */ + const char *buf_start = buf; + int found; + buf = findchar_fast(buf, buf_end, ranges, sizeof(ranges) - 1, &found); + if (!found) { + CHECK_EOF(); + } + while (1) { + if (*buf == next_char) { + break; + } else if (!token_char_map[(unsigned char)*buf]) { + *ret = -1; + return NULL; + } + ++buf; + CHECK_EOF(); + } + *token = buf_start; + *token_len = buf - buf_start; + return buf; +} + /* returned pointer is always within [buf, buf_end), or null */ static const char *parse_http_version(const char *buf, const char *buf_end, int *minor_version, int *ret) { @@ -286,31 +315,10 @@ static const char *parse_headers(const char *buf, const char *buf_end, struct ph if (!(*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) { /* parsing name, but do not discard SP before colon, see * http://www.mozilla.org/security/announce/2006/mfsa2006-33.html */ - headers[*num_headers].name = buf; - static const char ALIGNED(16) ranges1[] = "\x00 " /* control chars and up to SP */ - "\"\"" /* 0x22 */ - "()" /* 0x28,0x29 */ - ",," /* 0x2c */ - "//" /* 0x2f */ - ":@" /* 0x3a-0x40 */ - "[]" /* 0x5b-0x5d */ - "{\377"; /* 0x7b-0xff */ - int found; - buf = findchar_fast(buf, buf_end, ranges1, sizeof(ranges1) - 1, &found); - if (!found) { - CHECK_EOF(); - } - while (1) { - if (*buf == ':') { - break; - } else if (!token_char_map[(unsigned char)*buf]) { - *ret = -1; - return NULL; - } - ++buf; - CHECK_EOF(); + if ((buf = parse_token(buf, buf_end, &headers[*num_headers].name, &headers[*num_headers].name_len, ':', ret)) == NULL) { + return NULL; } - if ((headers[*num_headers].name_len = buf - headers[*num_headers].name) == 0) { + if (headers[*num_headers].name_len == 0) { *ret = -1; return NULL; } @@ -358,10 +366,18 @@ static const char *parse_request(const char *buf, const char *buf_end, const cha } /* parse request line */ - ADVANCE_TOKEN(*method, *method_len); - ++buf; + if ((buf = parse_token(buf, buf_end, method, method_len, ' ', ret)) == NULL) { + return NULL; + } + do { + ++buf; + CHECK_EOF(); + } while (*buf == ' '); ADVANCE_TOKEN(*path, *path_len); - ++buf; + do { + ++buf; + CHECK_EOF(); + } while (*buf == ' '); if (*method_len == 0 || *path_len == 0) { *ret = -1; return NULL; @@ -387,7 +403,7 @@ int phr_parse_request(const char *buf_start, size_t len, const char **method, si { const char *buf = buf_start, *buf_end = buf_start + len; size_t max_headers = *num_headers; - int r; + int r = 0; *method = NULL; *method_len = 0; @@ -418,10 +434,14 @@ static const char *parse_response(const char *buf, const char *buf_end, int *min return NULL; } /* skip space */ - if (*buf++ != ' ') { + if (*buf != ' ') { *ret = -1; return NULL; } + do { + ++buf; + CHECK_EOF(); + } while (*buf == ' '); /* parse status code, we want at least [:digit:][:digit:][:digit:] to try to parse */ if (buf_end - buf < 4) { *ret = -2; @@ -429,13 +449,22 @@ static const char *parse_response(const char *buf, const char *buf_end, int *min } PARSE_INT_3(status); - /* skip space */ - if (*buf++ != ' ') { - *ret = -1; + /* get message includig preceding space */ + if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) { return NULL; } - /* get message */ - if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) { + if (*msg_len == 0) { + /* ok */ + } else if (**msg == ' ') { + /* Remove preceding space. Successful return from `get_token_to_eol` guarantees that we would hit something other than SP + * before running past the end of the given buffer. */ + do { + ++*msg; + --*msg_len; + } while (**msg == ' '); + } else { + /* garbage found after status code */ + *ret = -1; return NULL; } @@ -447,7 +476,7 @@ int phr_parse_response(const char *buf_start, size_t len, int *minor_version, in { const char *buf = buf_start, *buf_end = buf + len; size_t max_headers = *num_headers; - int r; + int r = 0; *minor_version = -1; *status = 0; @@ -472,7 +501,7 @@ int phr_parse_headers(const char *buf_start, size_t len, struct phr_header *head { const char *buf = buf_start, *buf_end = buf + len; size_t max_headers = *num_headers; - int r; + int r = 0; *num_headers = 0; diff --git a/luaclib/src/lhttpparser/lhttpparser.c b/luaclib/src/lhttpparser/lhttpparser.c index 854f542a..2d45dce9 100644 --- a/luaclib/src/lhttpparser/lhttpparser.c +++ b/luaclib/src/lhttpparser/lhttpparser.c @@ -1,123 +1,102 @@ #define LUA_LIB -#include -#include -#include -#include +#include #include "httpparser.h" -int -lparser_request_protocol(lua_State *L){ - size_t buf_len; - const char* buf = luaL_checklstring(L, 1, &buf_len); - if (!buf) return luaL_error(L, "lparser_request_protocol need a str buf."); +#define MAX_HEADER (128) - int min, ret; - const char *method; - const char *path; - size_t method_len, path_len, num_headers; +static int +lparser_response_chunked(lua_State *L){ + size_t buf_len; + const char* data = luaL_checklstring(L, 1, &buf_len); - struct phr_header headers[32]; - memset(headers, 0x0, sizeof(headers)); + char *buf = (buf_len < 65535) ? alloca(1 << 16) : lua_newuserdata(L, buf_len); + memcpy(buf, data, buf_len); - num_headers = sizeof(headers) / sizeof(headers[0]); - ret = phr_parse_request(buf, buf_len, &method, &method_len, &path, &path_len, &min, headers, &num_headers, 0); - if (0 > ret) return 0; + struct phr_chunked_decoder decoder = { .consume_trailer = 1 }; - lua_pushlstring(L, method, method_len); // METHOD - lua_pushlstring(L, path, path_len); // PATH - lua_pushnumber(L, min > 0 ? 1.1 : 1.0); // VERSION - return 3; -} - -int -lparser_response_protocol(lua_State *L){ - size_t buf_len; - const char* buf = luaL_checklstring(L, 1, &buf_len); - if (!buf) return luaL_error(L, "parser_response_protocol need a str buf."); - - int status, minor_version, ret; - size_t msg_len, num_headers; - const char* msg; - - struct phr_header headers[32]; - memset(headers, 0x0, sizeof(headers)); - - num_headers = sizeof(headers) / sizeof(headers[0]); - ret = phr_parse_response(buf, buf_len, &minor_version, &status, &msg, &msg_len, headers, &num_headers, 0); - if (0 > ret) return 0; - - lua_pushnumber(L, minor_version > 0 ? 1.1 : 1.0); // VERSION - lua_pushinteger(L, status); // STATUS CODE - lua_pushlstring(L, msg, msg_len); // STATUS MSG - return 3; + int last = phr_decode_chunked(&decoder, buf, &buf_len); + if (last >= 0) { + lua_pushlstring(L, (const char *)buf, buf_len); + return 1; + } + lua_pushnil(L); + lua_pushinteger(L, last); + return 2; } -int -lparser_request_header(lua_State *L){ - size_t buf_len; - const char* buf = luaL_checklstring(L, 1, &buf_len); - if (!buf) return luaL_error(L, "lparser_request_header need a str buf."); - - int minor_version, ret, i; - const char *method; - const char *path; - size_t method_len, path_len, num_headers; - - struct phr_header headers[64]; - memset(headers, 0x0, sizeof(headers)); - - num_headers = sizeof(headers) / sizeof(headers[0]); - ret = phr_parse_request(buf, buf_len, &method, &method_len, &path, &path_len, &minor_version, headers, &num_headers, 0); - if (0 > ret) return 0; - - lua_createtable(L, 0, 32); - for (i = 0; i < 32; i++){ - if (!headers[i].name || !headers[i].value) break; - lua_pushlstring(L, headers[i].name, headers[i].name_len); - lua_pushlstring(L, headers[i].value, headers[i].value_len); - lua_rawset(L, lua_gettop(L) - 2); - } - return 1; +static int +lparser_http_request(lua_State *L){ + size_t buf_len; + const char* buf = luaL_checklstring(L, 1, &buf_len); + + int minor_version, ret, i; + const char *method; + const char *path; + size_t method_len, path_len, num_headers; + + struct phr_header headers[MAX_HEADER]; + memset(headers, 0x0, sizeof(struct phr_header) * MAX_HEADER); + + num_headers = MAX_HEADER; + ret = phr_parse_request(buf, buf_len, &method, &method_len, &path, &path_len, &minor_version, headers, &num_headers, 0); + if (0 > ret) return 0; + + lua_pushlstring(L, method, method_len); // METHOD + lua_pushlstring(L, path, path_len); // PATH + lua_pushnumber(L, minor_version > 0 ? 1.1 : 1.0); // VERSION + + lua_createtable(L, 0, MAX_HEADER); + for (i = 0; i < MAX_HEADER; i++){ + if (!headers[i].name || !headers[i].value) + break; + lua_pushlstring(L, headers[i].name, headers[i].name_len); + lua_pushlstring(L, headers[i].value, headers[i].value_len); + lua_rawset(L, lua_gettop(L) - 2); + } + return 4; } -int -lparser_response_header(lua_State *L){ - size_t buf_len; - const char* buf = luaL_checklstring(L, 1, &buf_len); - if (!buf) return luaL_error(L, "parser_response_protocol need a str buf."); - - int status, minor_version, ret, i; - size_t msg_len, num_headers; - const char* msg; - - struct phr_header headers[64]; - memset(headers, 0x0, sizeof(headers)); - - num_headers = sizeof(headers) / sizeof(headers[0]); - ret = phr_parse_response(buf, buf_len, &minor_version, &status, &msg, &msg_len, headers, &num_headers, 0); - if (0 > ret) return 0; - - lua_createtable(L, 0, 32); - for (i = 0; i < 32; i++){ - if (!headers[i].name || !headers[i].value) break; - lua_pushlstring(L, headers[i].name, headers[i].name_len); - lua_pushlstring(L, headers[i].value, headers[i].value_len); - lua_rawset(L, lua_gettop(L) - 2); - } - return 1; +static int +lparser_http_response(lua_State *L){ + size_t buf_len; + const char* buf = luaL_checklstring(L, 1, &buf_len); + + int status, minor_version, ret, i; + size_t msg_len, num_headers; + const char* msg; + + struct phr_header headers[MAX_HEADER]; + memset(headers, 0x0, sizeof(struct phr_header) * MAX_HEADER); + + num_headers = MAX_HEADER; + ret = phr_parse_response(buf, buf_len, &minor_version, &status, &msg, &msg_len, headers, &num_headers, 0); + if (0 > ret) return 0; + + lua_pushnumber(L, minor_version > 0 ? 1.1 : 1.0); // VERSION + lua_pushinteger(L, status); // STATUS CODE + lua_pushlstring(L, msg, msg_len); // STATUS MSG + + lua_createtable(L, 0, MAX_HEADER); + for (i = 0; i < MAX_HEADER; i++){ + if (!headers[i].name || !headers[i].value) + break; + lua_pushlstring(L, headers[i].name, headers[i].name_len); + lua_pushlstring(L, headers[i].value, headers[i].value_len); + lua_rawset(L, lua_gettop(L) - 2); + } + return 4; } LUAMOD_API int -luaopen_httpparser(lua_State *L) { +luaopen_lhttpparser(lua_State *L) { luaL_checkversion(L); luaL_Reg httpparser_libs[] = { - {"parser_request_protocol", lparser_request_protocol}, - {"parser_response_protocol", lparser_response_protocol}, - {"parser_request_header", lparser_request_header}, - {"parser_response_header", lparser_response_header}, - {NULL, NULL} + {"parser_response_chunked", lparser_response_chunked}, + {"parser_http_request", lparser_http_request}, + {"parser_http_response", lparser_http_response}, + {NULL, NULL} }; luaL_newlib(L, httpparser_libs); return 1; -} \ No newline at end of file +} diff --git a/luaclib/src/lmsgpack/Makefile b/luaclib/src/lmsgpack/Makefile new file mode 100644 index 00000000..a2ec2411 --- /dev/null +++ b/luaclib/src/lmsgpack/Makefile @@ -0,0 +1,20 @@ +.PHONY : build rebuild clean + +default : + @echo "=======================================" + @echo "Please use 'make build' command to build it.." + @echo "Please use 'make rebuild' command to build it.." + @echo "Please use 'make clean' command to clean all." + @echo "=======================================" + +CC = cc + +INCLUDES += -I../../../src -I/usr/local/include +LIBS = -L../ -L../../ -L../../../ -L/usr/local/lib + +CFLAGS = -O3 -Wall -shared -fPIC +DLL = -lcore + +build: + @$(CC) -o lmsgpack.so lmsgpack.c $(INCLUDES) $(LIBS) $(CFLAGS) $(DLL) + @mv *.so ../../ diff --git a/luaclib/src/lmsgpack/lmsgpack.c b/luaclib/src/lmsgpack/lmsgpack.c new file mode 100644 index 00000000..d40db7ef --- /dev/null +++ b/luaclib/src/lmsgpack/lmsgpack.c @@ -0,0 +1,981 @@ +#define LUA_LIB + +#include + +// #include +// #include +// #include +// #include +// #include +// +// #include "lua.h" +// #include "lauxlib.h" + +#define LUACMSGPACK_NAME "cmsgpack" +#define LUACMSGPACK_SAFE_NAME "cmsgpack_safe" +#define LUACMSGPACK_VERSION "lua-cmsgpack 0.4.1" +#define LUACMSGPACK_COPYRIGHT "Copyright (C) 2018, Salvatore Sanfilippo" +#define LUACMSGPACK_DESCRIPTION "MessagePack C implementation for Lua" + +/* Allows a preprocessor directive to override MAX_NESTING */ +#ifndef LUACMSGPACK_MAX_NESTING + #define LUACMSGPACK_MAX_NESTING 16 /* Max tables nesting. */ +#endif + +/* Check if float or double can be an integer without loss of precision */ +#define IS_INT_TYPE_EQUIVALENT(x, T) (!isinf(x) && (T)(x) == (x)) + +#define IS_INT64_EQUIVALENT(x) IS_INT_TYPE_EQUIVALENT(x, int64_t) +#define IS_INT_EQUIVALENT(x) IS_INT_TYPE_EQUIVALENT(x, int) + +/* If size of pointer is equal to a 4 byte integer, we're on 32 bits. */ +#if UINTPTR_MAX == UINT_MAX + #define BITS_32 1 +#else + #define BITS_32 0 +#endif + +#if BITS_32 + #define lua_pushunsigned(L, n) lua_pushnumber(L, n) +#else + #define lua_pushunsigned(L, n) lua_pushinteger(L, n) +#endif + +/* ============================================================================= + * MessagePack implementation and bindings for Lua 5.1/5.2. + * Copyright(C) 2012 Salvatore Sanfilippo + * + * http://github.com/antirez/lua-cmsgpack + * + * For MessagePack specification check the following web site: + * http://wiki.msgpack.org/display/MSGPACK/Format+specification + * + * See Copyright Notice at the end of this file. + * + * CHANGELOG: + * 19-Feb-2012 (ver 0.1.0): Initial release. + * 20-Feb-2012 (ver 0.2.0): Tables encoding improved. + * 20-Feb-2012 (ver 0.2.1): Minor bug fixing. + * 20-Feb-2012 (ver 0.3.0): Module renamed lua-cmsgpack (was lua-msgpack). + * 04-Apr-2014 (ver 0.3.1): Lua 5.2 support and minor bug fix. + * 07-Apr-2014 (ver 0.4.0): Multiple pack/unpack, lua allocator, efficiency. + * ========================================================================== */ + +/* -------------------------- Endian conversion -------------------------------- + * We use it only for floats and doubles, all the other conversions performed + * in an endian independent fashion. So the only thing we need is a function + * that swaps a binary string if arch is little endian (and left it untouched + * otherwise). */ + +/* Reverse memory bytes if arch is little endian. Given the conceptual + * simplicity of the Lua build system we prefer check for endianess at runtime. + * The performance difference should be acceptable. */ +void memrevifle(void *ptr, size_t len) { + unsigned char *p = (unsigned char *)ptr, + *e = (unsigned char *)p+len-1, + aux; + int test = 1; + unsigned char *testp = (unsigned char*) &test; + + if (testp[0] == 0) return; /* Big endian, nothing to do. */ + len /= 2; + while(len--) { + aux = *p; + *p = *e; + *e = aux; + p++; + e--; + } +} + +/* ---------------------------- String buffer ---------------------------------- + * This is a simple implementation of string buffers. The only operation + * supported is creating empty buffers and appending bytes to it. + * The string buffer uses 2x preallocation on every realloc for O(N) append + * behavior. */ + +typedef struct mp_buf { + unsigned char *b; + size_t len, free; +} mp_buf; + +void *mp_realloc(lua_State *L, void *target, size_t osize,size_t nsize) { + void *(*local_realloc) (void *, void *, size_t osize, size_t nsize) = NULL; + void *ud; + + local_realloc = lua_getallocf(L, &ud); + + return local_realloc(ud, target, osize, nsize); +} + +mp_buf *mp_buf_new(lua_State *L) { + mp_buf *buf = NULL; + + /* Old size = 0; new size = sizeof(*buf) */ + buf = (mp_buf*)mp_realloc(L, NULL, 0, sizeof(*buf)); + + buf->b = NULL; + buf->len = buf->free = 0; + return buf; +} + +void mp_buf_append(lua_State *L, mp_buf *buf, const unsigned char *s, size_t len) { + if (buf->free < len) { + size_t newsize = (buf->len+len)*2; + + buf->b = (unsigned char*)mp_realloc(L, buf->b, buf->len + buf->free, newsize); + buf->free = newsize - buf->len; + } + memcpy(buf->b+buf->len,s,len); + buf->len += len; + buf->free -= len; +} + +void mp_buf_free(lua_State *L, mp_buf *buf) { + mp_realloc(L, buf->b, buf->len + buf->free, 0); /* realloc to 0 = free */ + mp_realloc(L, buf, sizeof(*buf), 0); +} + +/* ---------------------------- String cursor ---------------------------------- + * This simple data structure is used for parsing. Basically you create a cursor + * using a string pointer and a length, then it is possible to access the + * current string position with cursor->p, check the remaining length + * in cursor->left, and finally consume more string using + * mp_cur_consume(cursor,len), to advance 'p' and subtract 'left'. + * An additional field cursor->error is set to zero on initialization and can + * be used to report errors. */ + +#define MP_CUR_ERROR_NONE 0 +#define MP_CUR_ERROR_EOF 1 /* Not enough data to complete operation. */ +#define MP_CUR_ERROR_BADFMT 2 /* Bad data format */ + +typedef struct mp_cur { + const unsigned char *p; + size_t left; + int err; +} mp_cur; + +void mp_cur_init(mp_cur *cursor, const unsigned char *s, size_t len) { + cursor->p = s; + cursor->left = len; + cursor->err = MP_CUR_ERROR_NONE; +} + +#define mp_cur_consume(_c,_len) do { _c->p += _len; _c->left -= _len; } while(0) + +/* When there is not enough room we set an error in the cursor and return. This + * is very common across the code so we have a macro to make the code look + * a bit simpler. */ +#define mp_cur_need(_c,_len) do { \ + if (_c->left < _len) { \ + _c->err = MP_CUR_ERROR_EOF; \ + return; \ + } \ +} while(0) + +/* ------------------------- Low level MP encoding -------------------------- */ + +void mp_encode_bytes(lua_State *L, mp_buf *buf, const unsigned char *s, size_t len) { + unsigned char hdr[5]; + int hdrlen; + + if (len < 32) { + hdr[0] = 0xa0 | (len&0xff); /* fix raw */ + hdrlen = 1; + } else if (len <= 0xff) { + hdr[0] = 0xd9; + hdr[1] = len; + hdrlen = 2; + } else if (len <= 0xffff) { + hdr[0] = 0xda; + hdr[1] = (len&0xff00)>>8; + hdr[2] = len&0xff; + hdrlen = 3; + } else { + hdr[0] = 0xdb; + hdr[1] = (len&0xff000000)>>24; + hdr[2] = (len&0xff0000)>>16; + hdr[3] = (len&0xff00)>>8; + hdr[4] = len&0xff; + hdrlen = 5; + } + mp_buf_append(L,buf,hdr,hdrlen); + mp_buf_append(L,buf,s,len); +} + +/* we assume IEEE 754 internal format for single and double precision floats. */ +void mp_encode_double(lua_State *L, mp_buf *buf, double d) { + unsigned char b[9]; + float f = d; + + assert(sizeof(f) == 4 && sizeof(d) == 8); + if (d == (double)f) { + b[0] = 0xca; /* float IEEE 754 */ + memcpy(b+1,&f,4); + memrevifle(b+1,4); + mp_buf_append(L,buf,b,5); + } else if (sizeof(d) == 8) { + b[0] = 0xcb; /* double IEEE 754 */ + memcpy(b+1,&d,8); + memrevifle(b+1,8); + mp_buf_append(L,buf,b,9); + } +} + +void mp_encode_int(lua_State *L, mp_buf *buf, int64_t n) { + unsigned char b[9]; + int enclen; + + if (n >= 0) { + if (n <= 127) { + b[0] = n & 0x7f; /* positive fixnum */ + enclen = 1; + } else if (n <= 0xff) { + b[0] = 0xcc; /* uint 8 */ + b[1] = n & 0xff; + enclen = 2; + } else if (n <= 0xffff) { + b[0] = 0xcd; /* uint 16 */ + b[1] = (n & 0xff00) >> 8; + b[2] = n & 0xff; + enclen = 3; + } else if (n <= 0xffffffffLL) { + b[0] = 0xce; /* uint 32 */ + b[1] = (n & 0xff000000) >> 24; + b[2] = (n & 0xff0000) >> 16; + b[3] = (n & 0xff00) >> 8; + b[4] = n & 0xff; + enclen = 5; + } else { + b[0] = 0xcf; /* uint 64 */ + b[1] = (n & 0xff00000000000000LL) >> 56; + b[2] = (n & 0xff000000000000LL) >> 48; + b[3] = (n & 0xff0000000000LL) >> 40; + b[4] = (n & 0xff00000000LL) >> 32; + b[5] = (n & 0xff000000) >> 24; + b[6] = (n & 0xff0000) >> 16; + b[7] = (n & 0xff00) >> 8; + b[8] = n & 0xff; + enclen = 9; + } + } else { + if (n >= -32) { + b[0] = ((signed char)n); /* negative fixnum */ + enclen = 1; + } else if (n >= -128) { + b[0] = 0xd0; /* int 8 */ + b[1] = n & 0xff; + enclen = 2; + } else if (n >= -32768) { + b[0] = 0xd1; /* int 16 */ + b[1] = (n & 0xff00) >> 8; + b[2] = n & 0xff; + enclen = 3; + } else if (n >= -2147483648LL) { + b[0] = 0xd2; /* int 32 */ + b[1] = (n & 0xff000000) >> 24; + b[2] = (n & 0xff0000) >> 16; + b[3] = (n & 0xff00) >> 8; + b[4] = n & 0xff; + enclen = 5; + } else { + b[0] = 0xd3; /* int 64 */ + b[1] = (n & 0xff00000000000000LL) >> 56; + b[2] = (n & 0xff000000000000LL) >> 48; + b[3] = (n & 0xff0000000000LL) >> 40; + b[4] = (n & 0xff00000000LL) >> 32; + b[5] = (n & 0xff000000) >> 24; + b[6] = (n & 0xff0000) >> 16; + b[7] = (n & 0xff00) >> 8; + b[8] = n & 0xff; + enclen = 9; + } + } + mp_buf_append(L,buf,b,enclen); +} + +void mp_encode_array(lua_State *L, mp_buf *buf, int64_t n) { + unsigned char b[5]; + int enclen; + + if (n <= 15) { + b[0] = 0x90 | (n & 0xf); /* fix array */ + enclen = 1; + } else if (n <= 65535) { + b[0] = 0xdc; /* array 16 */ + b[1] = (n & 0xff00) >> 8; + b[2] = n & 0xff; + enclen = 3; + } else { + b[0] = 0xdd; /* array 32 */ + b[1] = (n & 0xff000000) >> 24; + b[2] = (n & 0xff0000) >> 16; + b[3] = (n & 0xff00) >> 8; + b[4] = n & 0xff; + enclen = 5; + } + mp_buf_append(L,buf,b,enclen); +} + +void mp_encode_map(lua_State *L, mp_buf *buf, int64_t n) { + unsigned char b[5]; + int enclen; + + if (n <= 15) { + b[0] = 0x80 | (n & 0xf); /* fix map */ + enclen = 1; + } else if (n <= 65535) { + b[0] = 0xde; /* map 16 */ + b[1] = (n & 0xff00) >> 8; + b[2] = n & 0xff; + enclen = 3; + } else { + b[0] = 0xdf; /* map 32 */ + b[1] = (n & 0xff000000) >> 24; + b[2] = (n & 0xff0000) >> 16; + b[3] = (n & 0xff00) >> 8; + b[4] = n & 0xff; + enclen = 5; + } + mp_buf_append(L,buf,b,enclen); +} + +/* --------------------------- Lua types encoding --------------------------- */ + +void mp_encode_lua_string(lua_State *L, mp_buf *buf) { + size_t len; + const char *s; + + s = lua_tolstring(L,-1,&len); + mp_encode_bytes(L,buf,(const unsigned char*)s,len); +} + +void mp_encode_lua_bool(lua_State *L, mp_buf *buf) { + unsigned char b = lua_toboolean(L,-1) ? 0xc3 : 0xc2; + mp_buf_append(L,buf,&b,1); +} + +/* Lua 5.3 has a built in 64-bit integer type */ +void mp_encode_lua_integer(lua_State *L, mp_buf *buf) { +#if (LUA_VERSION_NUM < 503) && BITS_32 + lua_Number i = lua_tonumber(L,-1); +#else + lua_Integer i = lua_tointeger(L,-1); +#endif + mp_encode_int(L, buf, (int64_t)i); +} + +/* Lua 5.2 and lower only has 64-bit doubles, so we need to + * detect if the double may be representable as an int + * for Lua < 5.3 */ +void mp_encode_lua_number(lua_State *L, mp_buf *buf) { + lua_Number n = lua_tonumber(L,-1); + + if (IS_INT64_EQUIVALENT(n)) { + mp_encode_lua_integer(L, buf); + } else { + mp_encode_double(L,buf,(double)n); + } +} + +void mp_encode_lua_type(lua_State *L, mp_buf *buf, int level); + +/* Convert a lua table into a message pack list. */ +void mp_encode_lua_table_as_array(lua_State *L, mp_buf *buf, int level) { +#if LUA_VERSION_NUM < 502 + size_t len = lua_objlen(L,-1), j; +#else + size_t len = lua_rawlen(L,-1), j; +#endif + + mp_encode_array(L,buf,len); + luaL_checkstack(L, 1, "in function mp_encode_lua_table_as_array"); + for (j = 1; j <= len; j++) { + lua_pushnumber(L,j); + lua_gettable(L,-2); + mp_encode_lua_type(L,buf,level+1); + } +} + +/* Convert a lua table into a message pack key-value map. */ +void mp_encode_lua_table_as_map(lua_State *L, mp_buf *buf, int level) { + size_t len = 0; + + /* First step: count keys into table. No other way to do it with the + * Lua API, we need to iterate a first time. Note that an alternative + * would be to do a single run, and then hack the buffer to insert the + * map opcodes for message pack. Too hackish for this lib. */ + luaL_checkstack(L, 3, "in function mp_encode_lua_table_as_map"); + lua_pushnil(L); + while(lua_next(L,-2)) { + lua_pop(L,1); /* remove value, keep key for next iteration. */ + len++; + } + + /* Step two: actually encoding of the map. */ + mp_encode_map(L,buf,len); + lua_pushnil(L); + while(lua_next(L,-2)) { + /* Stack: ... key value */ + lua_pushvalue(L,-2); /* Stack: ... key value key */ + mp_encode_lua_type(L,buf,level+1); /* encode key */ + mp_encode_lua_type(L,buf,level+1); /* encode val */ + } +} + +/* Returns true if the Lua table on top of the stack is exclusively composed + * of keys from numerical keys from 1 up to N, with N being the total number + * of elements, without any hole in the middle. */ +int table_is_an_array(lua_State *L) { + int count = 0, max = 0; +#if LUA_VERSION_NUM < 503 + lua_Number n; +#else + lua_Integer n; +#endif + + /* Stack top on function entry */ + int stacktop; + + stacktop = lua_gettop(L); + + lua_pushnil(L); + while(lua_next(L,-2)) { + /* Stack: ... key value */ + lua_pop(L,1); /* Stack: ... key */ + /* The <= 0 check is valid here because we're comparing indexes. */ +#if LUA_VERSION_NUM < 503 + if ((LUA_TNUMBER != lua_type(L,-1)) || (n = lua_tonumber(L, -1)) <= 0 || + !IS_INT_EQUIVALENT(n)) +#else + if (!lua_isinteger(L,-1) || (n = lua_tointeger(L, -1)) <= 0) +#endif + { + lua_settop(L, stacktop); + return 0; + } + max = (n > max ? n : max); + count++; + } + /* We have the total number of elements in "count". Also we have + * the max index encountered in "max". We can't reach this code + * if there are indexes <= 0. If you also note that there can not be + * repeated keys into a table, you have that if max==count you are sure + * that there are all the keys form 1 to count (both included). */ + lua_settop(L, stacktop); + return max == count; +} + +/* If the length operator returns non-zero, that is, there is at least + * an object at key '1', we serialize to message pack list. Otherwise + * we use a map. */ +void mp_encode_lua_table(lua_State *L, mp_buf *buf, int level) { + if (table_is_an_array(L)) + mp_encode_lua_table_as_array(L,buf,level); + else + mp_encode_lua_table_as_map(L,buf,level); +} + +void mp_encode_lua_null(lua_State *L, mp_buf *buf) { + unsigned char b[1]; + + b[0] = 0xc0; + mp_buf_append(L,buf,b,1); +} + +void mp_encode_lua_type(lua_State *L, mp_buf *buf, int level) { + int t = lua_type(L,-1); + + /* Limit the encoding of nested tables to a specified maximum depth, so that + * we survive when called against circular references in tables. */ + if (t == LUA_TTABLE && level == LUACMSGPACK_MAX_NESTING) t = LUA_TNIL; + switch(t) { + case LUA_TSTRING: mp_encode_lua_string(L,buf); break; + case LUA_TBOOLEAN: mp_encode_lua_bool(L,buf); break; + case LUA_TNUMBER: + #if LUA_VERSION_NUM < 503 + mp_encode_lua_number(L,buf); break; + #else + if (lua_isinteger(L, -1)) { + mp_encode_lua_integer(L, buf); + } else { + mp_encode_lua_number(L, buf); + } + break; + #endif + case LUA_TTABLE: mp_encode_lua_table(L,buf,level); break; + default: mp_encode_lua_null(L,buf); break; + } + lua_pop(L,1); +} + +/* + * Packs all arguments as a stream for multiple upacking later. + * Returns error if no arguments provided. + */ +int mp_pack(lua_State *L) { + int nargs = lua_gettop(L); + int i; + mp_buf *buf; + + if (nargs == 0) + return luaL_argerror(L, 0, "MessagePack pack needs input."); + + if (!lua_checkstack(L, nargs)) + return luaL_argerror(L, 0, "Too many arguments for MessagePack pack."); + + buf = mp_buf_new(L); + for(i = 1; i <= nargs; i++) { + /* Copy argument i to top of stack for _encode processing; + * the encode function pops it from the stack when complete. */ + luaL_checkstack(L, 1, "in function mp_check"); + lua_pushvalue(L, i); + + mp_encode_lua_type(L,buf,0); + + lua_pushlstring(L,(char*)buf->b,buf->len); + + /* Reuse the buffer for the next operation by + * setting its free count to the total buffer size + * and the current position to zero. */ + buf->free += buf->len; + buf->len = 0; + } + mp_buf_free(L, buf); + + /* Concatenate all nargs buffers together */ + lua_concat(L, nargs); + return 1; +} + +/* ------------------------------- Decoding --------------------------------- */ + +void mp_decode_to_lua_type(lua_State *L, mp_cur *c); + +void mp_decode_to_lua_array(lua_State *L, mp_cur *c, size_t len) { + assert(len <= UINT_MAX); + int index = 1; + + lua_newtable(L); + luaL_checkstack(L, 1, "in function mp_decode_to_lua_array"); + while(len--) { + lua_pushnumber(L,index++); + mp_decode_to_lua_type(L,c); + if (c->err) return; + lua_settable(L,-3); + } +} + +void mp_decode_to_lua_hash(lua_State *L, mp_cur *c, size_t len) { + assert(len <= UINT_MAX); + lua_newtable(L); + while(len--) { + mp_decode_to_lua_type(L,c); /* key */ + if (c->err) return; + mp_decode_to_lua_type(L,c); /* value */ + if (c->err) return; + lua_settable(L,-3); + } +} + +/* Decode a Message Pack raw object pointed by the string cursor 'c' to + * a Lua type, that is left as the only result on the stack. */ +void mp_decode_to_lua_type(lua_State *L, mp_cur *c) { + mp_cur_need(c,1); + + /* If we return more than 18 elements, we must resize the stack to + * fit all our return values. But, there is no way to + * determine how many objects a msgpack will unpack to up front, so + * we request a +1 larger stack on each iteration (noop if stack is + * big enough, and when stack does require resize it doubles in size) */ + luaL_checkstack(L, 1, + "too many return values at once; " + "use unpack_one or unpack_limit instead."); + + switch(c->p[0]) { + case 0xcc: /* uint 8 */ + mp_cur_need(c,2); + lua_pushunsigned(L,c->p[1]); + mp_cur_consume(c,2); + break; + case 0xd0: /* int 8 */ + mp_cur_need(c,2); + lua_pushinteger(L,(signed char)c->p[1]); + mp_cur_consume(c,2); + break; + case 0xcd: /* uint 16 */ + mp_cur_need(c,3); + lua_pushunsigned(L, + (c->p[1] << 8) | + c->p[2]); + mp_cur_consume(c,3); + break; + case 0xd1: /* int 16 */ + mp_cur_need(c,3); + lua_pushinteger(L,(int16_t) + (c->p[1] << 8) | + c->p[2]); + mp_cur_consume(c,3); + break; + case 0xce: /* uint 32 */ + mp_cur_need(c,5); + lua_pushunsigned(L, + ((uint32_t)c->p[1] << 24) | + ((uint32_t)c->p[2] << 16) | + ((uint32_t)c->p[3] << 8) | + (uint32_t)c->p[4]); + mp_cur_consume(c,5); + break; + case 0xd2: /* int 32 */ + mp_cur_need(c,5); + lua_pushinteger(L, + ((int32_t)c->p[1] << 24) | + ((int32_t)c->p[2] << 16) | + ((int32_t)c->p[3] << 8) | + (int32_t)c->p[4]); + mp_cur_consume(c,5); + break; + case 0xcf: /* uint 64 */ + mp_cur_need(c,9); + lua_pushunsigned(L, + ((uint64_t)c->p[1] << 56) | + ((uint64_t)c->p[2] << 48) | + ((uint64_t)c->p[3] << 40) | + ((uint64_t)c->p[4] << 32) | + ((uint64_t)c->p[5] << 24) | + ((uint64_t)c->p[6] << 16) | + ((uint64_t)c->p[7] << 8) | + (uint64_t)c->p[8]); + mp_cur_consume(c,9); + break; + case 0xd3: /* int 64 */ + mp_cur_need(c,9); +#if LUA_VERSION_NUM < 503 + lua_pushnumber(L, +#else + lua_pushinteger(L, +#endif + ((int64_t)c->p[1] << 56) | + ((int64_t)c->p[2] << 48) | + ((int64_t)c->p[3] << 40) | + ((int64_t)c->p[4] << 32) | + ((int64_t)c->p[5] << 24) | + ((int64_t)c->p[6] << 16) | + ((int64_t)c->p[7] << 8) | + (int64_t)c->p[8]); + mp_cur_consume(c,9); + break; + case 0xc0: /* nil */ + lua_pushnil(L); + mp_cur_consume(c,1); + break; + case 0xc3: /* true */ + lua_pushboolean(L,1); + mp_cur_consume(c,1); + break; + case 0xc2: /* false */ + lua_pushboolean(L,0); + mp_cur_consume(c,1); + break; + case 0xca: /* float */ + mp_cur_need(c,5); + assert(sizeof(float) == 4); + { + float f; + memcpy(&f,c->p+1,4); + memrevifle(&f,4); + lua_pushnumber(L,f); + mp_cur_consume(c,5); + } + break; + case 0xcb: /* double */ + mp_cur_need(c,9); + assert(sizeof(double) == 8); + { + double d; + memcpy(&d,c->p+1,8); + memrevifle(&d,8); + lua_pushnumber(L,d); + mp_cur_consume(c,9); + } + break; + case 0xd9: /* raw 8 */ + mp_cur_need(c,2); + { + size_t l = c->p[1]; + mp_cur_need(c,2+l); + lua_pushlstring(L,(char*)c->p+2,l); + mp_cur_consume(c,2+l); + } + break; + case 0xda: /* raw 16 */ + mp_cur_need(c,3); + { + size_t l = (c->p[1] << 8) | c->p[2]; + mp_cur_need(c,3+l); + lua_pushlstring(L,(char*)c->p+3,l); + mp_cur_consume(c,3+l); + } + break; + case 0xdb: /* raw 32 */ + mp_cur_need(c,5); + { + size_t l = ((size_t)c->p[1] << 24) | + ((size_t)c->p[2] << 16) | + ((size_t)c->p[3] << 8) | + (size_t)c->p[4]; + mp_cur_consume(c,5); + mp_cur_need(c,l); + lua_pushlstring(L,(char*)c->p,l); + mp_cur_consume(c,l); + } + break; + case 0xdc: /* array 16 */ + mp_cur_need(c,3); + { + size_t l = (c->p[1] << 8) | c->p[2]; + mp_cur_consume(c,3); + mp_decode_to_lua_array(L,c,l); + } + break; + case 0xdd: /* array 32 */ + mp_cur_need(c,5); + { + size_t l = ((size_t)c->p[1] << 24) | + ((size_t)c->p[2] << 16) | + ((size_t)c->p[3] << 8) | + (size_t)c->p[4]; + mp_cur_consume(c,5); + mp_decode_to_lua_array(L,c,l); + } + break; + case 0xde: /* map 16 */ + mp_cur_need(c,3); + { + size_t l = (c->p[1] << 8) | c->p[2]; + mp_cur_consume(c,3); + mp_decode_to_lua_hash(L,c,l); + } + break; + case 0xdf: /* map 32 */ + mp_cur_need(c,5); + { + size_t l = ((size_t)c->p[1] << 24) | + ((size_t)c->p[2] << 16) | + ((size_t)c->p[3] << 8) | + (size_t)c->p[4]; + mp_cur_consume(c,5); + mp_decode_to_lua_hash(L,c,l); + } + break; + default: /* types that can't be idenitified by first byte value. */ + if ((c->p[0] & 0x80) == 0) { /* positive fixnum */ + lua_pushunsigned(L,c->p[0]); + mp_cur_consume(c,1); + } else if ((c->p[0] & 0xe0) == 0xe0) { /* negative fixnum */ + lua_pushinteger(L,(signed char)c->p[0]); + mp_cur_consume(c,1); + } else if ((c->p[0] & 0xe0) == 0xa0) { /* fix raw */ + size_t l = c->p[0] & 0x1f; + mp_cur_need(c,1+l); + lua_pushlstring(L,(char*)c->p+1,l); + mp_cur_consume(c,1+l); + } else if ((c->p[0] & 0xf0) == 0x90) { /* fix map */ + size_t l = c->p[0] & 0xf; + mp_cur_consume(c,1); + mp_decode_to_lua_array(L,c,l); + } else if ((c->p[0] & 0xf0) == 0x80) { /* fix map */ + size_t l = c->p[0] & 0xf; + mp_cur_consume(c,1); + mp_decode_to_lua_hash(L,c,l); + } else { + c->err = MP_CUR_ERROR_BADFMT; + } + } +} + +int mp_unpack_full(lua_State *L, int limit, int offset) { + size_t len; + const char *s; + mp_cur c; + int cnt; /* Number of objects unpacked */ + int decode_all = (!limit && !offset); + + s = luaL_checklstring(L,1,&len); /* if no match, exits */ + + if (offset < 0 || limit < 0) /* requesting negative off or lim is invalid */ + return luaL_error(L, + "Invalid request to unpack with offset of %d and limit of %d.", + offset, len); + else if (offset > len) + return luaL_error(L, + "Start offset %d greater than input length %d.", offset, len); + + if (decode_all) limit = INT_MAX; + + mp_cur_init(&c,(const unsigned char *)s+offset,len-offset); + + /* We loop over the decode because this could be a stream + * of multiple top-level values serialized together */ + for(cnt = 0; c.left > 0 && cnt < limit; cnt++) { + mp_decode_to_lua_type(L,&c); + + if (c.err == MP_CUR_ERROR_EOF) { + return luaL_error(L,"Missing bytes in input."); + } else if (c.err == MP_CUR_ERROR_BADFMT) { + return luaL_error(L,"Bad data format in input."); + } + } + + if (!decode_all) { + /* c->left is the remaining size of the input buffer. + * subtract the entire buffer size from the unprocessed size + * to get our next start offset */ + int offset = len - c.left; + + luaL_checkstack(L, 1, "in function mp_unpack_full"); + + /* Return offset -1 when we have have processed the entire buffer. */ + lua_pushinteger(L, c.left == 0 ? -1 : offset); + /* Results are returned with the arg elements still + * in place. Lua takes care of only returning + * elements above the args for us. + * In this case, we have one arg on the stack + * for this function, so we insert our first return + * value at position 2. */ + lua_insert(L, 2); + cnt += 1; /* increase return count by one to make room for offset */ + } + + return cnt; +} + +int mp_unpack(lua_State *L) { + return mp_unpack_full(L, 0, 0); +} + +int mp_unpack_one(lua_State *L) { + int offset = luaL_optinteger(L, 2, 0); + /* Variable pop because offset may not exist */ + lua_pop(L, lua_gettop(L)-1); + return mp_unpack_full(L, 1, offset); +} + +int mp_unpack_limit(lua_State *L) { + int limit = luaL_checkinteger(L, 2); + int offset = luaL_optinteger(L, 3, 0); + /* Variable pop because offset may not exist */ + lua_pop(L, lua_gettop(L)-1); + + return mp_unpack_full(L, limit, offset); +} + +int mp_safe(lua_State *L) { + int argc, err, total_results; + + argc = lua_gettop(L); + + /* This adds our function to the bottom of the stack + * (the "call this function" position) */ + lua_pushvalue(L, lua_upvalueindex(1)); + lua_insert(L, 1); + + err = lua_pcall(L, argc, LUA_MULTRET, 0); + total_results = lua_gettop(L); + + if (!err) { + return total_results; + } else { + lua_pushnil(L); + lua_insert(L,-2); + return 2; + } +} + +/* -------------------------------------------------------------------------- */ +const struct luaL_Reg cmds[] = { + {"pack", mp_pack}, + {"unpack", mp_unpack}, + {"unpack_one", mp_unpack_one}, + {"unpack_limit", mp_unpack_limit}, + {0} +}; + +int luaopen_create(lua_State *L) { + int i; + /* Manually construct our module table instead of + * relying on _register or _newlib */ + lua_newtable(L); + + for (i = 0; i < (sizeof(cmds)/sizeof(*cmds) - 1); i++) { + lua_pushcfunction(L, cmds[i].func); + lua_setfield(L, -2, cmds[i].name); + } + + /* Add metadata */ + lua_pushliteral(L, LUACMSGPACK_NAME); + lua_setfield(L, -2, "_NAME"); + lua_pushliteral(L, LUACMSGPACK_VERSION); + lua_setfield(L, -2, "_VERSION"); + lua_pushliteral(L, LUACMSGPACK_COPYRIGHT); + lua_setfield(L, -2, "_COPYRIGHT"); + lua_pushliteral(L, LUACMSGPACK_DESCRIPTION); + lua_setfield(L, -2, "_DESCRIPTION"); + return 1; +} + +LUALIB_API int luaopen_lmsgpack(lua_State *L) { + luaopen_create(L); + +#if LUA_VERSION_NUM < 502 + /* Register name globally for 5.1 */ + lua_pushvalue(L, -1); + lua_setglobal(L, LUACMSGPACK_NAME); +#endif + + return 1; +} + +LUALIB_API int luaopen_lmsgpack_safe(lua_State *L) { + int i; + + luaopen_lmsgpack(L); + + /* Wrap all functions in the safe handler */ + for (i = 0; i < (sizeof(cmds)/sizeof(*cmds) - 1); i++) { + lua_getfield(L, -1, cmds[i].name); + lua_pushcclosure(L, mp_safe, 1); + lua_setfield(L, -2, cmds[i].name); + } + +#if LUA_VERSION_NUM < 502 + /* Register name globally for 5.1 */ + lua_pushvalue(L, -1); + lua_setglobal(L, LUACMSGPACK_SAFE_NAME); +#endif + + return 1; +} + +/****************************************************************************** +* Copyright (C) 2012 Salvatore Sanfilippo. All rights reserved. +* +* Permission is hereby granted, free of charge, to any person obtaining +* a copy of this software and associated documentation files (the +* "Software"), to deal in the Software without restriction, including +* without limitation the rights to use, copy, modify, merge, publish, +* distribute, sublicense, and/or sell copies of the Software, and to +* permit persons to whom the Software is furnished to do so, subject to +* the following conditions: +* +* The above copyright notice and this permission notice shall be +* included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +******************************************************************************/ diff --git a/luaclib/src/lpack.c b/luaclib/src/lpack.c new file mode 100644 index 00000000..64d60cc4 --- /dev/null +++ b/luaclib/src/lpack.c @@ -0,0 +1,741 @@ +#define LUA_LIB + +#include + +#define TYPE_NIL 0 +#define TYPE_BOOLEAN 1 +// hibits 0 false 1 true + +#define TYPE_NUMBER 2 +// hibits 0 : 0 , 1: byte, 2:word, 4: dword, 6: qword, 8 : double +#define TYPE_NUMBER_ZERO 0 +#define TYPE_NUMBER_BYTE 1 +#define TYPE_NUMBER_WORD 2 +#define TYPE_NUMBER_DWORD 4 +#define TYPE_NUMBER_QWORD 6 +#define TYPE_NUMBER_REAL 8 + +#define TYPE_USERDATA 3 +#define TYPE_SHORT_STRING 4 +// hibits 0~31 : len +#define TYPE_LONG_STRING 5 +#define TYPE_TABLE 6 + +#define MAX_COOKIE 32 +#define COMBINE_TYPE(t,v) ((t) | (v) << 3) + +#define BLOCK_SIZE 128 +#define MAX_DEPTH 32 + +struct block { + struct block * next; + char buffer[BLOCK_SIZE]; +}; + +struct write_block { + struct block * head; + int len; + struct block * current; + int ptr; +}; + +struct read_block { + char * buffer; + struct block * current; + int len; + int ptr; +}; + +inline static struct block * +blk_alloc(void) { + struct block *b = malloc(sizeof(struct block)); + b->next = NULL; + return b; +} + +inline static void wb_push(struct write_block *b, const void *buf, int sz) { + const char * buffer = buf; + if (b->ptr == BLOCK_SIZE) { +_again: + b->current = b->current->next = blk_alloc(); + b->ptr = 0; + } + if (b->ptr <= BLOCK_SIZE - sz) { + memcpy(b->current->buffer + b->ptr, buffer, sz); + b->ptr+=sz; + b->len+=sz; + } else { + int copy = BLOCK_SIZE - b->ptr; + memcpy(b->current->buffer + b->ptr, buffer, copy); + buffer += copy; + b->len += copy; + sz -= copy; + goto _again; + } +} + +static void wb_init(struct write_block *wb , struct block *b) { + if (b==NULL) { + wb->head = blk_alloc(); + wb->len = 0; + wb->current = wb->head; + wb->ptr = 0; + wb_push(wb, &wb->len, sizeof(wb->len)); + } else { + wb->head = b; + int * plen = (int *)b->buffer; + int sz = *plen; + wb->len = sz; + while (b->next) { + sz -= BLOCK_SIZE; + b = b->next; + } + wb->current = b; + wb->ptr = sz; + } +} + +static struct block* wb_close(struct write_block *b) { + b->current = b->head; + b->ptr = 0; + wb_push(b, &b->len, sizeof(b->len)); + b->current = NULL; + return b->head; +} + +static void wb_free(struct write_block *wb) { + struct block *blk = wb->head; + while (blk) { + struct block * next = blk->next; + free(blk); + blk = next; + } + wb->head = NULL; + wb->current = NULL; + wb->ptr = 0; + wb->len = 0; +} + +static void rball_init(struct read_block * rb, char * buffer) { + rb->buffer = buffer; + rb->current = NULL; + uint8_t header[4]; + memcpy(header,buffer,4); + rb->len = header[0] | header[1] <<8 | header[2] << 16 | header[3] << 24; + rb->ptr = 4; + rb->len -= rb->ptr; +} + +static int rb_init(struct read_block *rb, struct block *b) { + rb->buffer = NULL; + rb->current = b; + memcpy(&(rb->len),b->buffer,sizeof(rb->len)); + rb->ptr = sizeof(rb->len); + rb->len -= rb->ptr; + return rb->len; +} + +static void* rb_read(struct read_block *rb, void *buffer, int sz) { + if (rb->len < sz) { + return NULL; + } + + if (rb->buffer) { + int ptr = rb->ptr; + rb->ptr += sz; + rb->len -= sz; + return rb->buffer + ptr; + } + + if (rb->ptr == BLOCK_SIZE) { + struct block * next = rb->current->next; + free(rb->current); + rb->current = next; + rb->ptr = 0; + } + + int copy = BLOCK_SIZE - rb->ptr; + + if (sz <= copy) { + void * ret = rb->current->buffer + rb->ptr; + rb->ptr += sz; + rb->len -= sz; + return ret; + } + + char * tmp = buffer; + + memcpy(tmp, rb->current->buffer + rb->ptr, copy); + sz -= copy; + tmp += copy; + rb->len -= copy; + + for (;;) { + struct block * next = rb->current->next; + free(rb->current); + rb->current = next; + + if (sz < BLOCK_SIZE) { + memcpy(tmp, rb->current->buffer, sz); + rb->ptr = sz; + rb->len -= sz; + return buffer; + } + memcpy(tmp, rb->current->buffer, BLOCK_SIZE); + sz -= BLOCK_SIZE; + tmp += BLOCK_SIZE; + rb->len -= BLOCK_SIZE; + } +} + +static void rb_close(struct read_block *rb) { + while (rb->current) { + struct block * next = rb->current->next; + free(rb->current); + rb->current = next; + } + rb->len = 0; + rb->ptr = 0; +} + +static inline void wb_nil(struct write_block *wb) { + int n = TYPE_NIL; + wb_push(wb, &n, 1); +} + +static inline void wb_boolean(struct write_block *wb, int boolean) { + int n = COMBINE_TYPE(TYPE_BOOLEAN , boolean ? 1 : 0); + wb_push(wb, &n, 1); +} + +static inline void wb_integer(struct write_block *wb, lua_Integer v) { + int type = TYPE_NUMBER; + if (v == 0) { + uint8_t n = COMBINE_TYPE(type , TYPE_NUMBER_ZERO); + wb_push(wb, &n, 1); + } else if (v != (int32_t)v) { + uint8_t n = COMBINE_TYPE(type , TYPE_NUMBER_QWORD); + int64_t v64 = v; + wb_push(wb, &n, 1); + wb_push(wb, &v64, sizeof(v64)); + } else if (v < 0) { + int32_t v32 = (int32_t)v; + uint8_t n = COMBINE_TYPE(type , TYPE_NUMBER_DWORD); + wb_push(wb, &n, 1); + wb_push(wb, &v32, sizeof(v32)); + } else if (v<0x100) { + uint8_t n = COMBINE_TYPE(type , TYPE_NUMBER_BYTE); + wb_push(wb, &n, 1); + uint8_t byte = (uint8_t)v; + wb_push(wb, &byte, sizeof(byte)); + } else if (v<0x10000) { + uint8_t n = COMBINE_TYPE(type , TYPE_NUMBER_WORD); + wb_push(wb, &n, 1); + uint16_t word = (uint16_t)v; + wb_push(wb, &word, sizeof(word)); + } else { + uint8_t n = COMBINE_TYPE(type , TYPE_NUMBER_DWORD); + wb_push(wb, &n, 1); + uint32_t v32 = (uint32_t)v; + wb_push(wb, &v32, sizeof(v32)); + } +} + +static inline void +wb_real(struct write_block *wb, double v) { + uint8_t n = COMBINE_TYPE(TYPE_NUMBER , TYPE_NUMBER_REAL); + wb_push(wb, &n, 1); + wb_push(wb, &v, sizeof(v)); +} + +static inline void +wb_pointer(struct write_block *wb, void *v) { + int n = TYPE_USERDATA; + wb_push(wb, &n, 1); + wb_push(wb, &v, sizeof(v)); +} + +static inline void +wb_string(struct write_block *wb, const char *str, int len) { + if (len < MAX_COOKIE) { + int n = COMBINE_TYPE(TYPE_SHORT_STRING, len); + wb_push(wb, &n, 1); + if (len > 0) { + wb_push(wb, str, len); + } + } else { + int n; + if (len < 0x10000) { + n = COMBINE_TYPE(TYPE_LONG_STRING, 2); + wb_push(wb, &n, 1); + uint16_t x = (uint16_t) len; + wb_push(wb, &x, 2); + } else { + n = COMBINE_TYPE(TYPE_LONG_STRING, 4); + wb_push(wb, &n, 1); + uint32_t x = (uint32_t) len; + wb_push(wb, &x, 4); + } + wb_push(wb, str, len); + } +} + +static void pack_one(lua_State *L, struct write_block *b, int index, int depth); + +static int wb_table_array(lua_State *L, struct write_block * wb, int index, int depth) { + int array_size = lua_rawlen(L,index); + if (array_size >= MAX_COOKIE-1) { + int n = COMBINE_TYPE(TYPE_TABLE, MAX_COOKIE-1); + wb_push(wb, &n, 1); + wb_integer(wb, array_size); + } else { + int n = COMBINE_TYPE(TYPE_TABLE, array_size); + wb_push(wb, &n, 1); + } + + int i; + for (i=1;i<=array_size;i++) { + lua_rawgeti(L,index,i); + pack_one(L, wb, -1, depth); + lua_pop(L,1); + } + + return array_size; +} + +static void wb_table_hash(lua_State *L, struct write_block * wb, int index, int depth, int array_size) { + lua_pushnil(L); + while (lua_next(L, index) != 0) { + if (lua_type(L,-2) == LUA_TNUMBER) { + if (lua_isinteger(L, -2)) { + lua_Integer x = lua_tointeger(L,-2); + if (x>0 && x<=array_size) { + lua_pop(L,1); + continue; + } + } + } + pack_one(L,wb,-2,depth); + pack_one(L,wb,-1,depth); + lua_pop(L, 1); + } + wb_nil(wb); +} + +static void wb_table(lua_State *L, struct write_block *wb, int index, int depth) { + luaL_checkstack(L,LUA_MINSTACK,NULL); + if (index < 0) { + index = lua_gettop(L) + index + 1; + } + int array_size = wb_table_array(L, wb, index, depth); + wb_table_hash(L, wb, index, depth, array_size); +} + +#if LUA_VERSION_NUM < 503 + +static int lua_isinteger(lua_State *L, int index) { + int32_t x = (int32_t)lua_tointeger(L,index); + lua_Number n = lua_tonumber(L,index); + return ((lua_Number)x==n); +} + +#endif + +static void pack_one(lua_State *L, struct write_block *b, int index, int depth) { + if (depth > MAX_DEPTH) { + wb_free(b); + luaL_error(L, "serialize can't pack too depth table"); + } + int type = lua_type(L,index); + switch(type) { + case LUA_TNIL: + wb_nil(b); + break; + case LUA_TNUMBER: { + if (lua_isinteger(L, index)) { + lua_Integer x = lua_tointeger(L,index); + wb_integer(b, x); + } else { + lua_Number n = lua_tonumber(L,index); + wb_real(b,n); + } + break; + } + case LUA_TBOOLEAN: + wb_boolean(b, lua_toboolean(L,index)); + break; + case LUA_TSTRING: { + size_t sz = 0; + const char *str = lua_tolstring(L,index,&sz); + wb_string(b, str, (int)sz); + break; + } + case LUA_TLIGHTUSERDATA: + wb_pointer(b, lua_touserdata(L,index)); + break; + case LUA_TTABLE: { + if (index < 0) { + index = lua_gettop(L) + index + 1; + } + wb_table(L, b, index, depth+1); + break; + } + default: + wb_free(b); + luaL_error(L, "Unsupport type %s to serialize", lua_typename(L, type)); + } +} + +static void pack_from(lua_State *L, struct write_block *b, int from) { + int n = lua_gettop(L) - from; + int i; + for (i=1;i<=n;i++) { + pack_one(L, b , from + i, 0); + } +} + +static int lpack(lua_State *L) { + struct write_block b; + wb_init(&b, NULL); + pack_from(L,&b,0); + struct block * ret = wb_close(&b); + lua_pushlightuserdata(L,ret); + return 1; +} + +static int lappend(lua_State *L) { + struct write_block b; + wb_init(&b, lua_touserdata(L,1)); + pack_from(L,&b,1); + struct block * ret = wb_close(&b); + lua_pushlightuserdata(L,ret); + return 1; +} + +static inline void invalid_stream_line(lua_State *L, struct read_block *rb, int line) { + int len = rb->len; + luaL_error(L, "Invalid serialize stream %d (line:%d)", len, line); +} + +#define invalid_stream(L,rb) invalid_stream_line(L, rb, __LINE__) + +static lua_Integer get_integer(lua_State *L, struct read_block *rb, int cookie) { + switch (cookie) { + case TYPE_NUMBER_ZERO: + return 0; + case TYPE_NUMBER_BYTE: { + uint8_t n = 0; + uint8_t * pn = rb_read(rb, &n, sizeof(n)); + if (pn == NULL) + invalid_stream(L,rb); + n = *pn; + return n; + } + case TYPE_NUMBER_WORD: { + uint16_t n = 0; + uint16_t * pn = rb_read(rb, &n, sizeof(n)); + if (pn == NULL) + invalid_stream(L,rb); + memcpy(&n, pn, sizeof(n)); + return n; + } + case TYPE_NUMBER_DWORD: { + int32_t n = 0; + int32_t * pn = rb_read(rb, &n, sizeof(n)); + if (pn == NULL) + invalid_stream(L,rb); + memcpy(&n, pn, sizeof(n)); + return n; + } + case TYPE_NUMBER_QWORD: { + int64_t n=0; + int64_t * pn = rb_read(rb, &n, sizeof(n)); + if (pn == NULL) + invalid_stream(L,rb); + memcpy(&n, pn, sizeof(n)); + return n; + } + default: + invalid_stream(L,rb); + return 0; + } +} + +static double get_real(lua_State *L, struct read_block *rb) { + double n = 0; + double * pn = rb_read(rb, &n, sizeof(n)); + if (pn == NULL) + invalid_stream(L,rb); + memcpy(&n, pn, sizeof(n)); + return n; +} + +static void* get_pointer(lua_State *L, struct read_block *rb) { + void * userdata = 0; + void ** v = (void **)rb_read(rb,&userdata,sizeof(userdata)); + if (v == NULL) { + invalid_stream(L,rb); + } + return *v; +} + +static void get_buffer(lua_State *L, struct read_block *rb, int len) { + char tmp[len]; + char * p = rb_read(rb,tmp,len); + if (p == NULL) { + invalid_stream(L, rb); + } + lua_pushlstring(L,p,len); +} + +static void unpack_one(lua_State *L, struct read_block *rb); + +static void unpack_table(lua_State *L, struct read_block *rb, int array_size) { + if (array_size == MAX_COOKIE-1) { + uint8_t type = 0; + uint8_t *t = rb_read(rb, &type, sizeof(type)); + if (t==NULL) { + invalid_stream(L,rb); + } + type = *t; + int cookie = type >> 3; + if ((type & 7) != TYPE_NUMBER || cookie == TYPE_NUMBER_REAL) { + invalid_stream(L,rb); + } + array_size = get_integer(L,rb,cookie); + } + luaL_checkstack(L,LUA_MINSTACK,NULL); + lua_createtable(L,array_size,0); + int i; + for (i=1;i<=array_size;i++) { + unpack_one(L,rb); + lua_rawseti(L,-2,i); + } + for (;;) { + unpack_one(L,rb); + if (lua_isnil(L,-1)) { + lua_pop(L,1); + return; + } + unpack_one(L,rb); + lua_rawset(L,-3); + } +} + +static void push_value(lua_State *L, struct read_block *rb, int type, int cookie) { + switch(type) { + case TYPE_NIL: + lua_pushnil(L); + break; + case TYPE_BOOLEAN: + lua_pushboolean(L,cookie); + break; + case TYPE_NUMBER: + if (cookie == TYPE_NUMBER_REAL) { + lua_pushnumber(L,get_real(L,rb)); + } else { + lua_pushinteger(L, get_integer(L, rb, cookie)); + } + break; + case TYPE_USERDATA: + lua_pushlightuserdata(L,get_pointer(L,rb)); + break; + case TYPE_SHORT_STRING: + get_buffer(L,rb,cookie); + break; + case TYPE_LONG_STRING: { + uint32_t len = 0; + if (cookie == 2) { + uint16_t *plen = rb_read(rb, &len, 2); + if (plen == NULL) { + invalid_stream(L,rb); + } + uint16_t n; + memcpy(&n, plen, sizeof(n)); + get_buffer(L,rb,n); + } else { + if (cookie != 4) { + invalid_stream(L,rb); + } + uint32_t *plen = rb_read(rb, &len, 4); + if (plen == NULL) { + invalid_stream(L,rb); + } + uint32_t n; + memcpy(&n, plen, sizeof(n)); + get_buffer(L,rb,n); + } + break; + } + case TYPE_TABLE: { + unpack_table(L,rb,cookie); + break; + } + default: { + invalid_stream(L,rb); + break; + } + } +} + +static void unpack_one(lua_State *L, struct read_block *rb) { + uint8_t type = 0; + uint8_t *t = rb_read(rb, &type, sizeof(type)); + if (t==NULL) { + invalid_stream(L, rb); + } + type = *t; + push_value(L, rb, type & 0x7, type>>3); +} + +static int lunpack(lua_State *L) { + struct block * blk = lua_touserdata(L,1); + if (blk == NULL) { + return luaL_error(L, "Need a block to unpack"); + } + lua_settop(L,0); + struct read_block rb; + rb_init(&rb, blk); + + int i; + for (i=0;;i++) { + if (i%8==7) { + luaL_checkstack(L,LUA_MINSTACK,NULL); + } + uint8_t type = 0; + uint8_t *t = rb_read(&rb, &type, 1); + if (t==NULL) + break; + push_value(L, &rb, *t & 0x7, *t>>3); + } + + rb_close(&rb); + + return lua_gettop(L); +} + +static int _dump_mem(const char * buffer, int len, int size) { + int i; + for (i=0;ibuffer ,sizeof(len)); + len -= sizeof(len); + printf("Len = %d\n",len); + len = _dump_mem(b->buffer + sizeof(len), BLOCK_SIZE - sizeof(len) , len); + while (len > 0) { + b=b->next; + len = _dump_mem(b->buffer, BLOCK_SIZE , len); + } + printf("\n"); + return 0; +} + +static int lserialize(lua_State *L) { + struct block *b = lua_touserdata(L,1); + if (b==NULL) { + return luaL_error(L, "dump null pointer"); + } + + uint32_t len = 0; + memcpy(&len, b->buffer ,sizeof(len)); + + uint8_t * buffer = malloc(len); + uint8_t * ptr = buffer; + int sz = len; + while(len>0) { + if (len >= BLOCK_SIZE) { + memcpy(ptr, b->buffer, BLOCK_SIZE); + ptr += BLOCK_SIZE; + len -= BLOCK_SIZE; + } else { + memcpy(ptr, b->buffer, len); + break; + } + b = b->next; + } + + buffer[0] = sz & 0xff; + buffer[1] = (sz>>8) & 0xff; + buffer[2] = (sz>>16) & 0xff; + buffer[3] = (sz>>24) & 0xff; + + lua_pushlightuserdata(L, buffer); + lua_pushinteger(L, sz); + + return 2; +} + +static void deserialize_buffer(lua_State *L, void * buffer) { + struct read_block rb; + rball_init(&rb, buffer); + + int i; + for (i=0;;i++) { + if (i%16==15) { + lua_checkstack(L,i); + } + uint8_t type = 0; + uint8_t *t = rb_read(&rb, &type, 1); + if (t==NULL) + break; + push_value(L, &rb, *t & 0x7, *t>>3); + } +} + +static int ldeserialize(lua_State *L) { + void * buffer = lua_touserdata(L,1); + if (buffer == NULL) { + return luaL_error(L, "deserialize null pointer"); + } + + lua_settop(L,0); + deserialize_buffer(L, buffer); + + // Need not free buffer + + return lua_gettop(L); +} + +static int seristring(lua_State *L) { + struct write_block b; + wb_init(&b, NULL); + pack_from(L,&b,0); + struct block * ret = wb_close(&b); + lua_settop(L,0); + lua_pushlightuserdata(L,ret); + lserialize(L); + wb_free(&b); + void *buffer = lua_touserdata(L, -2); + int sz = lua_tointeger(L, -1); + lua_pushlstring(L, buffer, sz); + free(buffer); + return 1; +} + +static int deseristring(lua_State *L) { + const char * buffer = luaL_checkstring(L, 1); + deserialize_buffer(L, (void *)buffer); + + return lua_gettop(L) - 1; +} + +int luaopen_pack(lua_State *L) { + luaL_checkversion(L); + luaL_Reg lpack_libs[] = { + { "encode", seristring }, + { "decode", deseristring }, + { NULL, NULL }, + }; + luaL_newlib(L, lpack_libs); + return 1; +} \ No newline at end of file diff --git a/luaclib/src/lpbc/Makefile b/luaclib/src/lpbc/Makefile new file mode 100644 index 00000000..eb6cb2ca --- /dev/null +++ b/luaclib/src/lpbc/Makefile @@ -0,0 +1,20 @@ +.PHONY : build rebuild clean + +default : + @echo "=======================================" + @echo "Please use 'make build' command to build it.." + @echo "Please use 'make rebuild' command to build it.." + @echo "Please use 'make clean' command to clean all." + @echo "=======================================" + +CC = cc + +INCLUDES += -I../../../src -I/usr/local/include +LIBS = -L../ -L../../../ -L/usr/local/lib + +CFLAGS = -O3 -Wall -shared -fPIC -fno-strict-aliasing +DLL = -lcore + +build: + @$(CC) -o lprotobuf.so lpb.c $(INCLUDES) $(LIBS) $(CFLAGS) $(DLL) + @mv *.so ../../ diff --git a/luaclib/src/lpbc/lpb.c b/luaclib/src/lpbc/lpb.c new file mode 100644 index 00000000..fda0f717 --- /dev/null +++ b/luaclib/src/lpbc/lpb.c @@ -0,0 +1,1918 @@ +#ifdef _MSC_VER +# define _CRT_SECURE_NO_WARNINGS +# define _CRT_NONSTDC_NO_WARNINGS +# pragma warning(disable: 4244) /* int -> char */ +# pragma warning(disable: 4706) /* = in if condition */ +# pragma warning(disable: 4709) /* comma in array index */ +# pragma warning(disable: 4127) /* const in if condition */ +#endif + +#define PB_STATIC_API +#include "lpb.h" + +PB_NS_BEGIN + + +#define LUA_LIB +#include + + +/* Lua util routines */ + +#define PB_STATE "pb.State" +#define PB_BUFFER "pb.Buffer" +#define PB_SLICE "pb.Slice" + +#define check_buffer(L,idx) ((pb_Buffer*)luaL_checkudata(L,idx,PB_BUFFER)) +#define test_buffer(L,idx) ((pb_Buffer*)luaL_testudata(L,idx,PB_BUFFER)) +#define check_slice(L,idx) ((pb_Slice*)luaL_checkudata(L,idx,PB_SLICE)) +#define test_slice(L,idx) ((pb_Slice*)luaL_testudata(L,idx,PB_SLICE)) +#define push_slice(L,s) lua_pushlstring((L), (s).p, pb_len((s))) +#define return_self(L) { lua_settop(L, 1); return 1; } + +#if LUA_VERSION_NUM < 502 +#include + +# define LUA_OK 0 +# define lua_rawlen lua_objlen +# define luaL_setfuncs(L,l,n) (assert(n==0), luaL_register(L,NULL,l)) +# define luaL_setmetatable(L, name) \ + (luaL_getmetatable((L), (name)), lua_setmetatable(L, -2)) + +static int relindex(int idx, int offset) +{ return idx < 0 && idx > LUA_REGISTRYINDEX ? idx - offset : idx; } + +static void lua_rawgetp(lua_State *L, int idx, const void *p) { + lua_pushlightuserdata(L, (void*)p); + lua_rawget(L, relindex(idx, 1)); +} + +static void lua_rawsetp(lua_State *L, int idx, const void *p) { + lua_pushlightuserdata(L, (void*)p); + lua_insert(L, -2); + lua_rawset(L, relindex(idx, 1)); +} + +#ifndef luaL_newlib /* not LuaJIT 2.1 */ +#define luaL_newlib(L,l) (lua_newtable(L), luaL_register(L,NULL,l)) + +static lua_Integer lua_tointegerx(lua_State *L, int idx, int *isint) { + lua_Integer i = lua_tointeger(L, idx); + if (isint) *isint = (i != 0 || lua_type(L, idx) == LUA_TNUMBER); + return i; +} + +static lua_Number lua_tonumberx(lua_State *L, int idx, int *isnum) { + lua_Number i = lua_tonumber(L, idx); + if (isnum) *isnum = (i != 0 || lua_type(L, idx) == LUA_TNUMBER); + return i; +} + +static void *luaL_testudata(lua_State *L, int idx, const char *type) { + void *p = lua_touserdata(L, idx); + if (p != NULL && lua_getmetatable(L, idx)) { + lua_getfield(L, LUA_REGISTRYINDEX, type); + if (!lua_rawequal(L, -2, -1)) + p = NULL; + lua_pop(L, 2); + return p; + } + return NULL; +} + +#endif + +#ifdef LUAI_BITSINT /* not LuaJIT */ +#include + +static int luaL_fileresult(lua_State *L, int stat, const char *fname) { + int en = errno; + if (stat) { lua_pushboolean(L, 1); return 1; } + lua_pushnil(L); + lua_pushfstring(L, "%s: %s", fname, strerror(en)); + /*if (fname) lua_pushfstring(L, "%s: %s", fname, strerror(en)); + else lua_pushstring(L, strerror(en));*//* NOT USED */ + lua_pushinteger(L, en); + return 3; +} + +#endif /* not LuaJIT */ + +#endif + +#if LUA_VERSION_NUM >= 503 +# define lua53_getfield lua_getfield +# define lua53_rawgeti lua_rawgeti +# define lua53_rawgetp lua_rawgetp +#else /* not Lua 5.3 */ +static int lua53_getfield(lua_State *L, int idx, const char *field) +{ lua_getfield(L, idx, field); return lua_type(L, -1); } +static int lua53_rawgeti(lua_State *L, int idx, lua_Integer i) +{ lua_rawgeti(L, idx, i); return lua_type(L, -1); } +static int lua53_rawgetp(lua_State *L, int idx, const void *p) +{ lua_rawgetp(L, idx, p); return lua_type(L, -1); } +#endif + + +/* protobuf global state */ + +#define default_state(L) (default_lstate(L)->state) +#define lpb_state(LS) ((LS)->state) +#define lpb_name(LS,s) pb_name(lpb_state(LS), (s), &(LS)->cache) + +static const pb_State *global_state = NULL; +static const char state_name[] = PB_STATE; + +enum lpb_Int64Mode { LPB_NUMBER, LPB_STRING, LPB_HEXSTRING }; +enum lpb_DefMode { LPB_DEFDEF, LPB_COPYDEF, LPB_METADEF, LPB_NODEF }; + +typedef struct lpb_State { + const pb_State *state; + pb_State local; + pb_Cache cache; + pb_Buffer buffer; + int defs_index; + int hooks_index; + unsigned use_hooks : 1; /* lpb_Int64Mode */ + unsigned enum_as_value : 1; + unsigned default_mode : 2; /* lpb_DefMode */ + unsigned int64_mode : 2; /* lpb_Int64Mode */ +} lpb_State; + +static int lpb_reftable(lua_State *L, int ref) { + if (ref != LUA_NOREF) { + lua_rawgeti(L, LUA_REGISTRYINDEX, ref); + return ref; + } else { + lua_newtable(L); + lua_pushvalue(L, -1); + return luaL_ref(L, LUA_REGISTRYINDEX); + } +} + +static void lpb_pushdeftable(lua_State *L, lpb_State *LS) +{ LS->defs_index = lpb_reftable(L, LS->defs_index); } + +static void lpb_pushhooktable(lua_State *L, lpb_State *LS) +{ LS->hooks_index = lpb_reftable(L, LS->hooks_index); } + +static int Lpb_delete(lua_State *L) { + lpb_State *LS = (lpb_State*)luaL_testudata(L, 1, PB_STATE); + if (LS != NULL) { + const pb_State *GS = global_state; + pb_free(&LS->local); + if (&LS->local == GS) + global_state = NULL; + LS->state = NULL; + pb_resetbuffer(&LS->buffer); + luaL_unref(L, LUA_REGISTRYINDEX, LS->defs_index); + luaL_unref(L, LUA_REGISTRYINDEX, LS->hooks_index); + } + return 0; +} + +static lpb_State *default_lstate(lua_State *L) { + lpb_State *LS; + if (lua53_rawgetp(L, LUA_REGISTRYINDEX, state_name) == LUA_TUSERDATA) { + LS = (lpb_State*)lua_touserdata(L, -1); + lua_pop(L, 1); + } else { + lua_pop(L, 1); + LS = (lpb_State*)lua_newuserdata(L, sizeof(lpb_State)); + memset(LS, 0, sizeof(lpb_State)); + LS->defs_index = LUA_NOREF; + LS->hooks_index = LUA_NOREF; + LS->state = &LS->local; + pb_init(&LS->local); + pb_initbuffer(&LS->buffer); + luaL_setmetatable(L, PB_STATE); + lua_rawsetp(L, LUA_REGISTRYINDEX, state_name); + } + return LS; +} + +static int Lpb_state(lua_State *L) { + int top = lua_gettop(L); + default_lstate(L); + lua_rawgetp(L, LUA_REGISTRYINDEX, state_name); + if (top != 0) { + if (lua_isnil(L, 1)) + lua_pushnil(L); + else { + luaL_checkudata(L, 1, PB_STATE); + lua_pushvalue(L, 1); + } + lua_rawsetp(L, LUA_REGISTRYINDEX, state_name); + } + return 1; +} + + +/* protobuf util routines */ + +static void lpb_addlength(lua_State *L, pb_Buffer *b, size_t len) +{ if (pb_addlength(b, len) == 0) luaL_error(L, "encode bytes fail"); } + +static int typeerror(lua_State *L, int idx, const char *type) { + lua_pushfstring(L, "%s expected, got %s", type, luaL_typename(L, idx)); + return luaL_argerror(L, idx, lua_tostring(L, -1)); +} + +static lua_Integer posrelat(lua_Integer pos, size_t len) { + if (pos >= 0) return pos; + else if (0u - (size_t)pos > len) return 0; + else return (lua_Integer)len + pos + 1; +} + +static lua_Integer rangerelat(lua_State *L, int idx, lua_Integer r[2], size_t len) { + r[0] = posrelat(luaL_optinteger(L, idx, 1), len); + r[1] = posrelat(luaL_optinteger(L, idx+1, len), len); + if (r[0] < 1) r[0] = 1; + if (r[1] > (lua_Integer)len) r[1] = len; + return r[0] <= r[1] ? r[1] - r[0] + 1 : 0; +} + +static int argcheck(lua_State *L, int cond, int idx, const char *fmt, ...) { + if (!cond) { + va_list l; + va_start(l, fmt); + lua_pushvfstring(L, fmt, l); + va_end(l); + return luaL_argerror(L, idx, lua_tostring(L, -1)); + } + return 1; +} + +static pb_Slice lpb_toslice(lua_State *L, int idx) { + int type = lua_type(L, idx); + if (type == LUA_TSTRING) { + size_t len; + const char *s = lua_tolstring(L, idx, &len); + return pb_lslice(s, len); + } else if (type == LUA_TUSERDATA) { + pb_Buffer *buffer; + pb_Slice *s; + if ((buffer = test_buffer(L, idx)) != NULL) + return pb_result(buffer); + else if ((s = test_slice(L, idx)) != NULL) + return *s; + } + return pb_slice(NULL); +} + +static pb_Slice lpb_checkslice(lua_State *L, int idx) { + pb_Slice ret = lpb_toslice(L, idx); + if (ret.p == NULL) typeerror(L, idx, "string/buffer/slice"); + return ret; +} + +static void lpb_readbytes(lua_State *L, pb_Slice *s, pb_Slice *pv) { + uint64_t len = 0; + if (pb_readvarint64(s, &len) == 0 || len > PB_MAX_SIZET) + luaL_error(L, "invalid bytes length: %d (at offset %d)", + (int)len, pb_pos(*s)+1); + if (pb_readslice(s, (size_t)len, pv) == 0 && len != 0) + luaL_error(L, "un-finished bytes (len %d at offset %d)", + (int)len, pb_pos(*s)+1); +} + +static int lpb_hexchar(char ch) { + switch (ch) { + case '0': return 0; + case '1': return 1; case '2': return 2; case '3': return 3; + case '4': return 4; case '5': return 5; case '6': return 6; + case '7': return 7; case '8': return 8; case '9': return 9; + case 'a': case 'A': return 10; case 'b': case 'B': return 11; + case 'c': case 'C': return 12; case 'd': case 'D': return 13; + case 'e': case 'E': return 14; case 'f': case 'F': return 15; + } + return -1; +} + +static uint64_t lpb_tointegerx(lua_State *L, int idx, int *isint) { + int neg = 0; + const char *s, *os; +#if LUA_VERSION_NUM >= 503 + uint64_t v = (uint64_t)lua_tointegerx(L, idx, isint); + if (*isint) return v; +#else + uint64_t v = 0; + lua_Number nv = lua_tonumberx(L, idx, isint); + if (*isint) { + if (nv < (lua_Number)INT64_MIN || nv > (lua_Number)INT64_MAX) + luaL_error(L, "number has no integer representation"); + return (uint64_t)(int64_t)nv; + } +#endif + if ((os = s = lua_tostring(L, idx)) == NULL) return 0; + while (*s == '#' || *s == '+' || *s == '-') + neg = (*s == '-') ^ neg, ++s; + if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { + for (s += 2; *s != '\0'; ++s) { + int n = lpb_hexchar(*s); + if (n < 0) break; + v = v << 4 | n; + } + } else { + for (; *s != '\0'; ++s) { + int n = lpb_hexchar(*s); + if (n < 0 || n > 10) break; + v = v * 10 + n; + } + } + if (*s != '\0') luaL_error(L, "integer format error: '%s'", os); + *isint = 1; + return neg ? ~v + 1 : v; +} + +static uint64_t lpb_checkinteger(lua_State *L, int idx) { + int isint; + uint64_t v = lpb_tointegerx(L, idx, &isint); + if (!isint) typeerror(L, idx, "number/string"); + return v; +} + +static void lpb_pushinteger(lua_State *L, int64_t n, int mode) { + if (mode != LPB_NUMBER && (n < INT_MIN || n > UINT_MAX)) { + char buff[32], *p = buff + sizeof(buff) - 1; + int neg = n < 0; + uint64_t un = neg ? ~(uint64_t)n + 1 : (uint64_t)n; + if (mode == LPB_STRING) { + for (*p = '\0'; un > 0; un /= 10) + *--p = "0123456789"[un % 10]; + } else if (mode == LPB_HEXSTRING) { + for (*p = '\0'; un > 0; un >>= 4) + *--p = "0123456789ABCDEF"[un & 0xF]; + *--p = 'x', *--p = '0'; + } + if (neg) *--p = '-'; + *--p = '#'; + lua_pushstring(L, p); + } else if (LUA_VERSION_NUM >= 503 && sizeof(lua_Integer) >= 8) + lua_pushinteger(L, (lua_Integer)n); + else + lua_pushnumber(L, (lua_Number)n); +} + +typedef union lpb_Value { + pb_Slice s[1]; + uint32_t u32; + uint64_t u64; + lua_Integer lint; + lua_Number lnum; +} lpb_Value; + +static int lpb_addtype(lua_State *L, pb_Buffer *b, int idx, int type, size_t *plen) { + int ret = 0, expected = LUA_TNUMBER; + lpb_Value v; + size_t len = 0; + switch (type) { + case PB_Tbool: + len = pb_addvarint32(b, ret = lua_toboolean(L, idx)); + if (ret) len = 0; + ret = 1; + break; + case PB_Tdouble: + v.lnum = lua_tonumberx(L, idx, &ret); + if (ret) len = pb_addfixed64(b, pb_encode_double((double)v.lnum)); + if (v.lnum != 0.0) len = 0; + break; + case PB_Tfloat: + v.lnum = lua_tonumberx(L, idx, &ret); + if (ret) len = pb_addfixed32(b, pb_encode_float((float)v.lnum)); + if (v.lnum != 0.0) len = 0; + break; + case PB_Tfixed32: + v.u64 = lpb_tointegerx(L, idx, &ret); + if (ret) len = pb_addfixed32(b, v.u32); + if (v.u64 != 0) len = 0; + break; + case PB_Tsfixed32: + v.u64 = lpb_tointegerx(L, idx, &ret); + if (ret) len = pb_addfixed32(b, v.u32); + if (v.u64 != 0) len = 0; + break; + case PB_Tint32: + v.u64 = lpb_tointegerx(L, idx, &ret); + if (ret) len = pb_addvarint64(b, pb_expandsig((uint32_t)v.u64)); + if (v.u64 != 0) len = 0; + break; + case PB_Tuint32: + v.u64 = lpb_tointegerx(L, idx, &ret); + if (ret) len = pb_addvarint32(b, v.u32); + if (v.u64 != 0) len = 0; + break; + case PB_Tsint32: + v.u64 = lpb_tointegerx(L, idx, &ret); + if (ret) len = pb_addvarint32(b, pb_encode_sint32(v.u32)); + if (v.u64 != 0) len = 0; + break; + case PB_Tfixed64: + v.u64 = lpb_tointegerx(L, idx, &ret); + if (ret) len = pb_addfixed64(b, v.u64); + if (v.u64 != 0) len = 0; + break; + case PB_Tsfixed64: + v.u64 = lpb_tointegerx(L, idx, &ret); + if (ret) len = pb_addfixed64(b, v.u64); + if (v.u64 != 0) len = 0; + break; + case PB_Tint64: case PB_Tuint64: + v.u64 = lpb_tointegerx(L, idx, &ret); + if (ret) len = pb_addvarint64(b, v.u64); + if (v.u64 != 0) len = 0; + break; + case PB_Tsint64: + v.u64 = lpb_tointegerx(L, idx, &ret); + if (ret) len = pb_addvarint64(b, pb_encode_sint64(v.u64)); + if (v.u64 != 0) len = 0; + break; + case PB_Tbytes: case PB_Tstring: + *v.s = lpb_toslice(L, idx); + if ((ret = (v.s->p != NULL))) len = pb_addbytes(b, *v.s); + if (pb_len(*v.s) != 0) len = 0; + expected = LUA_TSTRING; + break; + default: + lua_pushfstring(L, "unknown type %s", pb_typename(type, "")); + if (idx > 0) argcheck(L, 0, idx, lua_tostring(L, -1)); + lua_error(L); + } + if (plen) *plen = len; + return ret ? 0 : expected; +} + +static void lpb_readtype(lua_State *L, lpb_State *LS, int type, pb_Slice *s) { + lpb_Value v; + switch (type) { +#define pushinteger(n) lpb_pushinteger((L), (n), LS->int64_mode) + case PB_Tbool: case PB_Tenum: + case PB_Tint32: case PB_Tuint32: case PB_Tsint32: + case PB_Tint64: case PB_Tuint64: case PB_Tsint64: + if (pb_readvarint64(s, &v.u64) == 0) + luaL_error(L, "invalid varint value at offset %d", pb_pos(*s)+1); + switch (type) { + case PB_Tbool: lua_pushboolean(L, v.u64 != 0); break; + /*case PB_Tenum: pushinteger(v.u64); break; [> NOT REACHED <]*/ + case PB_Tint32: pushinteger((int32_t)v.u64); break; + case PB_Tuint32: pushinteger((uint32_t)v.u64); break; + case PB_Tsint32: pushinteger(pb_decode_sint32((uint32_t)v.u64)); break; + case PB_Tint64: pushinteger((int64_t)v.u64); break; + case PB_Tuint64: pushinteger((uint64_t)v.u64); break; + case PB_Tsint64: pushinteger(pb_decode_sint64(v.u64)); break; + } + break; + case PB_Tfloat: + case PB_Tfixed32: + case PB_Tsfixed32: + if (pb_readfixed32(s, &v.u32) == 0) + luaL_error(L, "invalid fixed32 value at offset %d", pb_pos(*s)+1); + switch (type) { + case PB_Tfloat: lua_pushnumber(L, pb_decode_float(v.u32)); break; + case PB_Tfixed32: pushinteger(v.u32); break; + case PB_Tsfixed32: pushinteger((int32_t)v.u32); break; + } + break; + case PB_Tdouble: + case PB_Tfixed64: + case PB_Tsfixed64: + if (pb_readfixed64(s, &v.u64) == 0) + luaL_error(L, "invalid fixed64 value at offset %d", pb_pos(*s)+1); + switch (type) { + case PB_Tdouble: lua_pushnumber(L, pb_decode_double(v.u64)); break; + case PB_Tfixed64: pushinteger(v.u64); break; + case PB_Tsfixed64: pushinteger((int64_t)v.u64); break; + } + break; + case PB_Tbytes: + case PB_Tstring: + case PB_Tmessage: + lpb_readbytes(L, s, v.s); + push_slice(L, *v.s); + break; + default: + luaL_error(L, "unknown type %s (%d)", pb_typename(type, NULL), type); + } +} + + +/* io routines */ + +#ifdef _WIN32 +# include +# include +#else +# define setmode(a,b) ((void)0) +#endif + +static int io_read(lua_State *L) { + FILE *fp = (FILE*)lua_touserdata(L, 1); + size_t nr; + luaL_Buffer b; + luaL_buffinit(L, &b); + do { /* read file in chunks of LUAL_BUFFERSIZE bytes */ + char *p = luaL_prepbuffer(&b); + nr = fread(p, sizeof(char), LUAL_BUFFERSIZE, fp); + luaL_addsize(&b, nr); + } while (nr == LUAL_BUFFERSIZE); + luaL_pushresult(&b); /* close buffer */ + return 1; +} + +static int io_write(lua_State *L, FILE *f, int idx) { + int nargs = lua_gettop(L) - idx + 1; + int status = 1; + for (; nargs--; idx++) { + pb_Slice s = lpb_checkslice(L, idx); + size_t l = pb_len(s); + status = status && (fwrite(s.p, sizeof(char), l, f) == l); + } + return status ? 1 : luaL_fileresult(L, 0, NULL); +} + +static int Lio_read(lua_State *L) { + const char *fname = luaL_optstring(L, 1, NULL); + FILE *fp = stdin; + int ret; + if (fname == NULL) + (void)setmode(fileno(stdin), O_BINARY); + else if ((fp = fopen(fname, "rb")) == NULL) + return luaL_fileresult(L, 0, fname); + lua_pushcfunction(L, io_read); + lua_pushlightuserdata(L, fp); + ret = lua_pcall(L, 1, 1, 0); + if (fp != stdin) fclose(fp); + else (void)setmode(fileno(stdin), O_TEXT); + if (ret != LUA_OK) { lua_pushnil(L); lua_insert(L, -2); return 2; } + return 1; +} + +static int Lio_write(lua_State *L) { + int res; + (void)setmode(fileno(stdout), O_BINARY); + res = io_write(L, stdout, 1); + fflush(stdout); + (void)setmode(fileno(stdout), O_TEXT); + return res; +} + +static int Lio_dump(lua_State *L) { + int res; + const char *fname = luaL_checkstring(L, 1); + FILE *fp = fopen(fname, "wb"); + if (fp == NULL) return luaL_fileresult(L, 0, fname); + res = io_write(L, fp, 2); + fclose(fp); + return res; +} + +LUALIB_API int luaopen_lprotobuf_io(lua_State *L) { + luaL_Reg libs[] = { +#define ENTRY(name) { #name, Lio_##name } + ENTRY(read), + ENTRY(write), + ENTRY(dump), +#undef ENTRY + { NULL, NULL } + }; + luaL_newlib(L, libs); + return 1; +} + + +/* protobuf integer conversion */ + +static int Lconv_encode_int32(lua_State *L) { + unsigned mode = default_lstate(L)->int64_mode; + uint64_t v = pb_expandsig((int32_t)lpb_checkinteger(L, 1)); + lpb_pushinteger(L, v, mode); + return 1; +} + +static int Lconv_encode_uint32(lua_State *L) { + unsigned mode = default_lstate(L)->int64_mode; + lpb_pushinteger(L, (uint32_t)lpb_checkinteger(L, 1), mode); + return 1; +} + +static int Lconv_encode_sint32(lua_State *L) { + unsigned mode = default_lstate(L)->int64_mode; + lpb_pushinteger(L, pb_encode_sint32((int32_t)lpb_checkinteger(L, 1)), mode); + return 1; +} + +static int Lconv_decode_sint32(lua_State *L) { + unsigned mode = default_lstate(L)->int64_mode; + lpb_pushinteger(L, pb_decode_sint32((uint32_t)lpb_checkinteger(L, 1)), mode); + return 1; +} + +static int Lconv_encode_sint64(lua_State *L) { + unsigned mode = default_lstate(L)->int64_mode; + lpb_pushinteger(L, pb_encode_sint64(lpb_checkinteger(L, 1)), mode); + return 1; +} + +static int Lconv_decode_sint64(lua_State *L) { + unsigned mode = default_lstate(L)->int64_mode; + lpb_pushinteger(L, pb_decode_sint64(lpb_checkinteger(L, 1)), mode); + return 1; +} + +static int Lconv_encode_float(lua_State *L) { + unsigned mode = default_lstate(L)->int64_mode; + lpb_pushinteger(L, pb_encode_float((float)luaL_checknumber(L, 1)), mode); + return 1; +} + +static int Lconv_decode_float(lua_State *L) { + lua_pushnumber(L, pb_decode_float((uint32_t)lpb_checkinteger(L, 1))); + return 1; +} + +static int Lconv_encode_double(lua_State *L) { + unsigned mode = default_lstate(L)->int64_mode; + lpb_pushinteger(L, pb_encode_double(luaL_checknumber(L, 1)), mode); + return 1; +} + +static int Lconv_decode_double(lua_State *L) { + lua_pushnumber(L, pb_decode_double(lpb_checkinteger(L, 1))); + return 1; +} + +LUALIB_API int luaopen_lprotobuf_conv(lua_State *L) { + luaL_Reg libs[] = { + { "decode_uint32", Lconv_encode_uint32 }, + { "decode_int32", Lconv_encode_int32 }, +#define ENTRY(name) { #name, Lconv_##name } + ENTRY(encode_int32), + ENTRY(encode_uint32), + ENTRY(encode_sint32), + ENTRY(encode_sint64), + ENTRY(decode_sint32), + ENTRY(decode_sint64), + ENTRY(decode_float), + ENTRY(decode_double), + ENTRY(encode_float), + ENTRY(encode_double), +#undef ENTRY + { NULL, NULL } + }; + luaL_newlib(L, libs); + return 1; +} + + +/* protobuf encode routine */ + +static int lpb_typefmt(int fmt) { + switch (fmt) { +#define X(name,type,fmt) case fmt: return PB_T##name; + PB_TYPES(X) +#undef X + } + return -1; +} + +static int lpb_packfmt(lua_State *L, int idx, pb_Buffer *b, const char **pfmt, int level) { + const char *fmt = *pfmt; + int type, ltype; + size_t len; + argcheck(L, level <= 100, 1, "format level overflow"); + for (; *fmt != '\0'; ++fmt) { + switch (*fmt) { + case 'v': pb_addvarint64(b, (uint64_t)lpb_checkinteger(L, idx++)); break; + case 'd': pb_addfixed32(b, (uint32_t)lpb_checkinteger(L, idx++)); break; + case 'q': pb_addfixed64(b, (uint64_t)lpb_checkinteger(L, idx++)); break; + case 'c': pb_addslice(b, lpb_checkslice(L, idx++)); break; + case 's': pb_addbytes(b, lpb_checkslice(L, idx++)); break; + case '#': lpb_addlength(L, b, (size_t)lpb_checkinteger(L, idx++)); break; + case '(': + len = pb_bufflen(b); + ++fmt; + idx = lpb_packfmt(L, idx, b, &fmt, level+1); + lpb_addlength(L, b, len); + break; + case ')': + if (level == 0) luaL_argerror(L, 1, "unexpected ')' in format"); + *pfmt = fmt; + return idx; + case '\0': + default: + argcheck(L, (type = lpb_typefmt(*fmt)) >= 0, + 1, "invalid formater: '%c'", *fmt); + ltype = lpb_addtype(L, b, idx, type, NULL); + argcheck(L, ltype == 0, idx, "%s expected for type '%s', got %s", + lua_typename(L, ltype), pb_typename(type, ""), + luaL_typename(L, idx)); + ++idx; + } + } + if (level != 0) luaL_argerror(L, 2, "unmatch '(' in format"); + *pfmt = fmt; + return idx; +} + +static int Lpb_tohex(lua_State *L) { + pb_Slice s = lpb_checkslice(L, 1); + const char *hexa = "0123456789ABCDEF"; + char hex[4] = "XX "; + lua_Integer r[2] = { 1, -1 }; + luaL_Buffer lb; + rangerelat(L, 2, r, pb_len(s)); + luaL_buffinit(L, &lb); + for (; r[0] <= r[1]; ++r[0]) { + unsigned int ch = s.p[r[0]-1]; + hex[0] = hexa[(ch>>4)&0xF]; + hex[1] = hexa[(ch )&0xF]; + if (r[0] == r[1]) hex[2] = '\0'; + luaL_addstring(&lb, hex); + } + luaL_pushresult(&lb); + return 1; +} + +static int Lpb_fromhex(lua_State *L) { + pb_Slice s = lpb_checkslice(L, 1); + lua_Integer r[2] = { 1, -1 }; + luaL_Buffer lb; + int curr = 0, idx = 0, num; + rangerelat(L, 2, r, pb_len(s)); + luaL_buffinit(L, &lb); + for (; r[0] <= r[1]; ++r[0]) { + switch (num = s.p[r[0]-1]) { + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + case '8': case '9': num -= '0'; break; + case 'A': case 'a': num = 10; break; + case 'B': case 'b': num = 11; break; + case 'C': case 'c': num = 12; break; + case 'D': case 'd': num = 13; break; + case 'E': case 'e': num = 14; break; + case 'F': case 'f': num = 15; break; + default: continue; + } + curr = curr<<4 | num; + if (++idx % 2 == 0) luaL_addchar(&lb, curr), curr = 0; + } + luaL_pushresult(&lb); + return 1; +} + +static int Lpb_result(lua_State *L) { + pb_Slice s = lpb_checkslice(L, 1); + lua_Integer r[2] = {1, -1}, range = rangerelat(L, 2, r, pb_len(s)); + lua_pushlstring(L, s.p+r[0]-1, (size_t)range); + return 1; +} + +static int Lbuf_new(lua_State *L) { + int i, top = lua_gettop(L); + pb_Buffer *buf = (pb_Buffer*)lua_newuserdata(L, sizeof(pb_Buffer)); + pb_initbuffer(buf); + luaL_setmetatable(L, PB_BUFFER); + for (i = 1; i <= top; ++i) + pb_addslice(buf, lpb_checkslice(L, i)); + return 1; +} + +static int Lbuf_delete(lua_State *L) { + pb_Buffer *buf = test_buffer(L, 1); + if (buf) pb_resetbuffer(buf); + return 0; +} + +static int Lbuf_libcall(lua_State *L) { + int i, top = lua_gettop(L); + pb_Buffer *buf = (pb_Buffer*)lua_newuserdata(L, sizeof(pb_Buffer)); + pb_initbuffer(buf); + luaL_setmetatable(L, PB_BUFFER); + for (i = 2; i <= top; ++i) + pb_addslice(buf, lpb_checkslice(L, i)); + return 1; +} + +static int Lbuf_tostring(lua_State *L) { + pb_Buffer *buf = check_buffer(L, 1); + lua_pushfstring(L, "pb.Buffer: %p", buf); + return 1; +} + +static int Lbuf_reset(lua_State *L) { + pb_Buffer *buf = check_buffer(L, 1); + int i, top = lua_gettop(L); + pb_bufflen(buf) = 0; + for (i = 2; i <= top; ++i) + pb_addslice(buf, lpb_checkslice(L, i)); + return_self(L); +} + +static int Lbuf_len(lua_State *L) { + pb_Buffer *buf = check_buffer(L, 1); + lua_pushinteger(L, (lua_Integer)buf->size); + return 1; +} + +static int Lbuf_pack(lua_State *L) { + pb_Buffer b, *pb = test_buffer(L, 1); + int idx = 1 + (pb != NULL); + const char *fmt = luaL_checkstring(L, idx++); + if (pb == NULL) pb_initbuffer(pb = &b); + lpb_packfmt(L, idx, pb, &fmt, 0); + if (pb != &b) + lua_settop(L, 1); + else { + pb_Slice ret = pb_result(pb); + push_slice(L, ret); + pb_resetbuffer(pb); + } + return 1; +} + +LUALIB_API int luaopen_lprotobuf_buffer(lua_State *L) { + luaL_Reg libs[] = { + { "__tostring", Lbuf_tostring }, + { "__len", Lbuf_len }, + { "__gc", Lbuf_delete }, + { "delete", Lbuf_delete }, + { "tohex", Lpb_tohex }, + { "fromhex", Lpb_fromhex }, + { "result", Lpb_result }, +#define ENTRY(name) { #name, Lbuf_##name } + ENTRY(new), + ENTRY(reset), + ENTRY(pack), +#undef ENTRY + { NULL, NULL } + }; + if (luaL_newmetatable(L, PB_BUFFER)) { + luaL_setfuncs(L, libs, 0); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + lua_createtable(L, 0, 1); + lua_pushcfunction(L, Lbuf_libcall); + lua_setfield(L, -2, "__call"); + lua_setmetatable(L, -2); + } + return 1; +} + + +/* protobuf decode routine */ + +#define LPB_INITSTACKLEN 2 + +typedef struct lpb_Slice { + pb_Slice curr; + pb_Slice *buff; + size_t used; + size_t size; + pb_Slice init_buff[LPB_INITSTACKLEN]; +} lpb_Slice; + +static void lpb_resetslice(lua_State *L, lpb_Slice *s, size_t size) { + if (size == sizeof(lpb_Slice)) { + if (s->buff != s->init_buff) free(s->buff); + memset(s, 0, sizeof(lpb_Slice)); + s->buff = s->init_buff; + s->size = LPB_INITSTACKLEN; + } + lua_pushnil(L); + lua_rawsetp(L, LUA_REGISTRYINDEX, s); +} + +static pb_Slice lpb_checkview(lua_State *L, int idx, pb_Slice *ps) { + pb_Slice src = lpb_checkslice(L, idx); + lua_Integer r[2] = {1, -1}, range = rangerelat(L, idx+1, r, pb_len(src)); + pb_Slice ret; + if (ps) *ps = src, ps->start = src.p; + ret.p = src.p + r[0] - 1; + ret.end = ret.p + range; + ret.start = src.p; + return ret; +} + +static void lpb_enterview(lua_State *L, lpb_Slice *s, pb_Slice view) { + if (s->used >= s->size) { + size_t newsize = s->size * 2; + pb_Slice *oldp = s->buff != s->init_buff ? s->buff : NULL; + pb_Slice *newp = (pb_Slice*)realloc(oldp, newsize*sizeof(pb_Slice)); + if (newp == NULL) { luaL_error(L, "out of memory"); return; } + if (oldp == NULL) memcpy(newp, s->buff, s->used*sizeof(pb_Slice)); + s->buff = newp; + s->size = newsize; + } + s->buff[s->used++] = s->curr; + s->curr = view; +} + +static void lpb_initslice(lua_State *L, int idx, lpb_Slice *s, size_t size) { + if (size == sizeof(lpb_Slice)) { + memset(s, 0, sizeof(lpb_Slice)); + s->buff = s->init_buff; + s->size = LPB_INITSTACKLEN; + } + if (!lua_isnoneornil(L, idx)) { + pb_Slice base, view = lpb_checkview(L, idx, &base); + s->curr = base; + if (size == sizeof(lpb_Slice)) lpb_enterview(L, s, view); + lua_pushvalue(L, idx); + lua_rawsetp(L, LUA_REGISTRYINDEX, s); + } +} + +static int lpb_unpackscalar(lua_State *L, int *pidx, int top, int fmt, pb_Slice *s) { + unsigned mode = default_lstate(L)->int64_mode; + lpb_Value v; memset(&v, 0x0, sizeof(v)); + switch (fmt) { + case 'v': + if (pb_readvarint64(s, &v.u64) == 0) + luaL_error(L, "invalid varint value at offset %d", pb_pos(*s)+1); + lpb_pushinteger(L, v.u64, mode); + break; + case 'd': + if (pb_readfixed32(s, &v.u32) == 0) + luaL_error(L, "invalid fixed32 value at offset %d", pb_pos(*s)+1); + lpb_pushinteger(L, v.u32, mode); + break; + case 'q': + if (pb_readfixed64(s, &v.u64) == 0) + luaL_error(L, "invalid fixed64 value at offset %d", pb_pos(*s)+1); + lpb_pushinteger(L, v.u64, mode); + break; + case 's': + if (pb_readbytes(s, v.s) == 0) + luaL_error(L, "invalid bytes value at offset %d", pb_pos(*s)+1); + push_slice(L, *v.s); + break; + case 'c': + argcheck(L, *pidx <= top, 1, "format argument exceed"); + v.lint = luaL_checkinteger(L, *pidx++); + if (pb_readslice(s, (size_t)v.lint, v.s) == 0) + luaL_error(L, "invalid sub string at offset %d", pb_pos(*s)+1); + push_slice(L, *v.s); + break; + default: + return 0; + } + return 1; +} + +static int lpb_unpackloc(lua_State *L, int *pidx, int top, int fmt, pb_Slice *s, int *prets) { + lua_Integer li; + size_t len = s->end - s->start; + switch (fmt) { + case '@': + lua_pushinteger(L, pb_pos(*s)+1); + ++*prets; + break; + + case '*': case '+': + argcheck(L, *pidx <= top, 1, "format argument exceed"); + if (fmt == '*') + li = posrelat(luaL_checkinteger(L, *pidx++), len); + else + li = pb_pos(*s) + luaL_checkinteger(L, *pidx++) + 1; + if (li == 0) li = 1; + if (li > (lua_Integer)len) li = (lua_Integer)len + 1; + s->p = s->start + li - 1; + break; + + default: + return 0; + } + return 1; +} + +static int lpb_unpackfmt(lua_State *L, int idx, const char *fmt, pb_Slice *s) { + int rets = 0, top = lua_gettop(L), type; + for (; *fmt != '\0'; ++fmt) { + if (lpb_unpackloc(L, &idx, top, *fmt, s, &rets)) + continue; + if (s->p >= s->end) { lua_pushnil(L); return rets + 1; } + luaL_checkstack(L, 1, "too many values"); + if (!lpb_unpackscalar(L, &idx, top, *fmt, s)) { + argcheck(L, (type = lpb_typefmt(*fmt)) >= 0, + 1, "invalid formater: '%c'", *fmt); + lpb_readtype(L, default_lstate(L), type, s); + } + ++rets; + } + return rets; +} + +static lpb_Slice *check_lslice(lua_State *L, int idx) { + pb_Slice *s = check_slice(L, idx); + argcheck(L, lua_rawlen(L, 1) == sizeof(lpb_Slice), + idx, "unsupport operation for raw mode slice"); + return (lpb_Slice*)s; +} + +static int Lslice_new(lua_State *L) { + lpb_Slice *s; + lua_settop(L, 3); + s = (lpb_Slice*)lua_newuserdata(L, sizeof(lpb_Slice)); + lpb_initslice(L, 1, s, sizeof(lpb_Slice)); + luaL_setmetatable(L, PB_SLICE); + return 1; +} + +static int Lslice_libcall(lua_State *L) { + lpb_Slice *s; + lua_settop(L, 4); + s = (lpb_Slice*)lua_newuserdata(L, sizeof(lpb_Slice)); + lpb_initslice(L, 2, s, sizeof(lpb_Slice)); + luaL_setmetatable(L, PB_SLICE); + return 1; +} + +static int Lslice_reset(lua_State *L) { + lpb_Slice *s = (lpb_Slice*)check_slice(L, 1); + size_t size = lua_rawlen(L, 1); + lpb_resetslice(L, s, size); + if (!lua_isnoneornil(L, 2)) + lpb_initslice(L, 2, s, size); + return_self(L); +} + +static int Lslice_tostring(lua_State *L) { + pb_Slice *s = check_slice(L, 1); + lua_pushfstring(L, "pb.Slice: %p%s", s, + lua_rawlen(L, 1) == sizeof(lpb_Slice) ? "" : " (raw)"); + return 1; +} + +static int Lslice_len(lua_State *L) { + pb_Slice *s = check_slice(L, 1); + lua_pushinteger(L, (lua_Integer)pb_len(*s)); + lua_pushinteger(L, (lua_Integer)pb_pos(*s)+1); + return 2; +} + +static int Lslice_unpack(lua_State *L) { + pb_Slice view, *s = test_slice(L, 1); + const char *fmt = luaL_checkstring(L, 2); + if (s == NULL) view = lpb_checkslice(L, 1), s = &view; + return lpb_unpackfmt(L, 3, fmt, s); +} + +static int Lslice_level(lua_State *L) { + lpb_Slice *s = check_lslice(L, 1); + if (!lua_isnoneornil(L, 2)) { + pb_Slice *se; + lua_Integer level = posrelat(luaL_checkinteger(L, 2), s->used); + if (level > (lua_Integer)s->used) + return 0; + else if (level == (lua_Integer)s->used) + se = &s->curr; + else + se = &s->buff[level]; + lua_pushinteger(L, (lua_Integer)(se->p - s->buff[0].start) + 1); + lua_pushinteger(L, (lua_Integer)(se->start - s->buff[0].start) + 1); + lua_pushinteger(L, (lua_Integer)(se->end - s->buff[0].start)); + return 3; + } + lua_pushinteger(L, s->used); + return 1; +} + +static int Lslice_enter(lua_State *L) { + lpb_Slice *s = check_lslice(L, 1); + pb_Slice view; + if (lua_isnoneornil(L, 2)) { + argcheck(L, pb_readbytes(&s->curr, &view) != 0, + 1, "bytes wireformat expected at offset %d", pb_pos(s->curr)+1); + view.start = view.p; + lpb_enterview(L, s, view); + } else { + lua_Integer r[] = {1, -1}; + lua_Integer range = rangerelat(L, 2, r, pb_len(s->curr)); + view.p = s->curr.start + r[0] - 1; + view.end = view.p + range; + view.start = s->curr.p; + lpb_enterview(L, s, view); + } + return_self(L); +} + +static int Lslice_leave(lua_State *L) { + lpb_Slice *s = check_lslice(L, 1); + lua_Integer count = posrelat(luaL_optinteger(L, 2, 1), s->used); + if (count > (lua_Integer)s->used) + argcheck(L, 0, 2, "level (%d) exceed max level %d", + (int)count, (int)s->used); + else if (count == (lua_Integer)s->used) { + s->curr = s->buff[0]; + s->used = 1; + } else { + s->used -= (size_t)count; + s->curr = s->buff[s->used]; + } + lua_settop(L, 1); + lua_pushinteger(L, s->used); + return 2; +} + +LUALIB_API int lpb_newslice(lua_State *L, const char *s, size_t len) { + pb_Slice *ls = (pb_Slice*)lua_newuserdata(L, sizeof(pb_Slice)); + *ls = pb_lslice(s, len); + luaL_setmetatable(L, PB_SLICE); + return 1; +} + +LUALIB_API int luaopen_lprotobuf_slice(lua_State *L) { + luaL_Reg libs[] = { + { "__tostring", Lslice_tostring }, + { "__len", Lslice_len }, + { "__gc", Lslice_reset }, + { "delete", Lslice_reset }, + { "tohex", Lpb_tohex }, + { "fromhex", Lpb_fromhex }, + { "result", Lpb_result }, +#define ENTRY(name) { #name, Lslice_##name } + ENTRY(new), + ENTRY(reset), + ENTRY(level), + ENTRY(enter), + ENTRY(leave), + ENTRY(unpack), +#undef ENTRY + { NULL, NULL } + }; + if (luaL_newmetatable(L, PB_SLICE)) { + luaL_setfuncs(L, libs, 0); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + lua_createtable(L, 0, 1); + lua_pushcfunction(L, Lslice_libcall); + lua_setfield(L, -2, "__call"); + lua_setmetatable(L, -2); + } + return 1; +} + + +/* high level typeinfo/encode/decode routines */ + +static const pb_Type *lpb_type(lpb_State *LS, pb_Slice s) { + const pb_Type *t; + if (s.p == NULL || *s.p == '.') + t = pb_type(lpb_state(LS), lpb_name(LS, s)); + else { + pb_Buffer b; + pb_initbuffer(&b); + *pb_prepbuffsize(&b, 1) = '.'; + pb_addsize(&b, 1); + pb_addslice(&b, s); + t = pb_type(lpb_state(LS), pb_name(lpb_state(LS),pb_result(&b),NULL)); + pb_resetbuffer(&b); + } + return t; +} + +static const pb_Field *lpb_field(lua_State *L, int idx, const pb_Type *t) { + lpb_State *LS = default_lstate(L); + int isint, number = (int)lua_tointegerx(L, idx, &isint); + if (isint) return pb_field(t, number); + return pb_fname(t, lpb_name(LS, lpb_checkslice(L, idx))); +} + +static int Lpb_load(lua_State *L) { + lpb_State *LS = default_lstate(L); + pb_Slice s = lpb_checkslice(L, 1); + int r = pb_load(&LS->local, &s); + if (r == PB_OK) global_state = &LS->local; + lua_pushboolean(L, r == PB_OK); + lua_pushinteger(L, pb_pos(s)+1); + return 2; +} + +static int Lpb_loadfile(lua_State *L) { + lpb_State *LS = default_lstate(L); + const char *filename = luaL_checkstring(L, 1); + size_t size; + pb_Buffer b; + pb_Slice s; + int ret; + FILE *fp = fopen(filename, "rb"); + if (fp == NULL) + return luaL_fileresult(L, 0, filename); + pb_initbuffer(&b); + do { + char *d = pb_prepbuffsize(&b, BUFSIZ); + if (d == NULL) { fclose(fp); return luaL_error(L, "out of memory"); } + size = fread(d, 1, BUFSIZ, fp); + pb_addsize(&b, size); + } while (size == BUFSIZ); + fclose(fp); + s = pb_result(&b); + ret = pb_load(&LS->local, &s); + if (ret == PB_OK) global_state = &LS->local; + pb_resetbuffer(&b); + lua_pushboolean(L, ret == PB_OK); + lua_pushinteger(L, pb_pos(s)+1); + return 2; +} + +static int lpb_pushtype(lua_State *L, const pb_Type *t) { + if (t == NULL) return 0; + lua_pushstring(L, (const char*)t->name); + lua_pushstring(L, (const char*)t->basename); + lua_pushstring(L, t->is_map ? "map" : t->is_enum ? "enum" : "message"); + return 3; +} + +static int lpb_pushfield(lua_State *L, const pb_Type *t, const pb_Field *f) { + if (f == NULL) return 0; + lua_pushstring(L, (const char*)f->name); + lua_pushinteger(L, f->number); + lua_pushstring(L, f->type ? + (const char*)f->type->name : + pb_typename(f->type_id, "")); + lua_pushstring(L, (const char*)f->default_value); + lua_pushstring(L, f->repeated ? + (f->packed ? "packed" : "repeated") : + "optional"); + if (f->oneof_idx > 0) { + lua_pushstring(L, (const char*)pb_oneofname(t, f->oneof_idx)); + lua_pushinteger(L, f->oneof_idx-1); + return 7; + } + return 5; +} + +static int Lpb_typesiter(lua_State *L) { + lpb_State *LS = default_lstate(L); + const pb_Type *t = lpb_type(LS, lpb_toslice(L, 2)); + if ((t == NULL && !lua_isnoneornil(L, 2))) + return 0; + pb_nexttype(lpb_state(LS), &t); + return lpb_pushtype(L, t); +} + +static int Lpb_types(lua_State *L) { + lua_pushcfunction(L, Lpb_typesiter); + lua_pushnil(L); + lua_pushnil(L); + return 3; +} + +static int Lpb_fieldsiter(lua_State *L) { + lpb_State *LS = default_lstate(L); + const pb_Type *t = lpb_type(LS, lpb_checkslice(L, 1)); + const pb_Field *f = pb_fname(t, lpb_name(LS, lpb_toslice(L, 2))); + if ((f == NULL && !lua_isnoneornil(L, 2)) || !pb_nextfield(t, &f)) + return 0; + return lpb_pushfield(L, t, f); +} + +static int Lpb_fields(lua_State *L) { + lua_pushcfunction(L, Lpb_fieldsiter); + lua_pushvalue(L, 1); + lua_pushnil(L); + return 3; +} + +static int Lpb_type(lua_State *L) { + lpb_State *LS = default_lstate(L); + const pb_Type *t = lpb_type(LS, lpb_checkslice(L, 1)); + if (t == NULL || t->is_dead) + return 0; + return lpb_pushtype(L, t); +} + +static int Lpb_field(lua_State *L) { + lpb_State *LS = default_lstate(L); + const pb_Type *t = lpb_type(LS, lpb_checkslice(L, 1)); + return lpb_pushfield(L, t, lpb_field(L, 2, t)); +} + +static int Lpb_enum(lua_State *L) { + lpb_State *LS = default_lstate(L); + const pb_Type *t = lpb_type(LS, lpb_checkslice(L, 1)); + const pb_Field *f = lpb_field(L, 2, t); + if (f == NULL) return 0; + if (lua_type(L, 2) == LUA_TNUMBER) + lua_pushstring(L, (const char*)f->name); + else + lpb_pushinteger(L, f->number, LS->int64_mode); + return 1; +} + +static int lpb_pushdefault(lua_State *L, lpb_State *LS, const pb_Field *f, int is_proto3) { + int ret = 0; + const pb_Type *type; + char *end; + if (f == NULL) return 0; + if (is_proto3 && f->repeated) { lua_newtable(L); return 1; } + switch (f->type_id) { + case PB_Tbytes: case PB_Tstring: + if (f->default_value) + ret = 1, lua_pushstring(L, (const char*)f->default_value); + else if (is_proto3) + ret = 1, lua_pushliteral(L, ""); + break; + case PB_Tenum: + if ((type = f ? f->type : NULL) == NULL) return 0; + if ((f = pb_fname(type, f->default_value)) != NULL) { + if (LS->enum_as_value) + ret = 1, lpb_pushinteger(L, f->number, LS->int64_mode); + else + ret = 1, lua_pushstring(L, (const char*)f->name); + } else if (is_proto3) { + if ((f = pb_field(type, 0)) == NULL || LS->enum_as_value) + ret = 1, lua_pushinteger(L, 0); + else + ret = 1, lua_pushstring(L, (const char*)f->name); + } + break; + case PB_Tmessage: + return 0; + case PB_Tbool: + if (f->default_value) { + if (f->default_value == lpb_name(LS, pb_slice("true"))) + ret = 1, lua_pushboolean(L, 1); + else if (f->default_value == lpb_name(LS, pb_slice("false"))) + ret = 1, lua_pushboolean(L, 0); + } else if (is_proto3) ret = 1, lua_pushboolean(L, 0); + break; + case PB_Tdouble: case PB_Tfloat: + if (f->default_value) { + lua_Number ln = (lua_Number)strtod((const char*)f->default_value, &end); + if ((const char*)f->default_value == end) return 0; + ret = 1, lua_pushnumber(L, ln); + } else if (is_proto3) ret = 1, lua_pushnumber(L, 0.0); + break; + + default: + if (f->default_value) { + lua_Integer li = (lua_Integer)strtol((const char*)f->default_value, &end, 10); + if ((const char*)f->default_value == end) return 0; + ret = 1, lpb_pushinteger(L, li, LS->int64_mode); + } else if (is_proto3) ret = 1, lua_pushinteger(L, 0); + } + return ret; +} + +static void lpb_pushdefaults(lua_State *L, lpb_State *LS, const pb_Type *t) { + lpb_pushdeftable(L, LS); + if (lua53_rawgetp(L, -1, t) != LUA_TTABLE) { + const pb_Field *f = NULL; + lua_pop(L, 1); + lua_newtable(L); + while (pb_nextfield(t, &f)) + if (!f->repeated && lpb_pushdefault(L, LS, f, t->is_proto3)) + lua_setfield(L, -2, (const char*)f->name); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + lua_pushvalue(L, -1); + lua_rawsetp(L, -3, t); + } + lua_remove(L, -2); +} + +static void lpb_cleardefaults(lua_State *L, lpb_State *LS, const pb_Type *t) { + lpb_pushdeftable(L, LS); + lua_pushnil(L); + lua_rawsetp(L, -2, t); + lua_pop(L, 1); +} + +static int Lpb_defaults(lua_State *L) { + lpb_State *LS = default_lstate(L); + const pb_Type *t = lpb_type(LS, lpb_checkslice(L, 1)); + int clear = lua_toboolean(L, 2); + if (t == NULL) luaL_argerror(L, 1, "type not found"); + lpb_pushdefaults(L, LS, t); + if (clear) lpb_cleardefaults(L, LS, t); + return 1; +} + +static int Lpb_hook(lua_State *L) { + lpb_State *LS = default_lstate(L); + const pb_Type *t = lpb_type(LS, lpb_checkslice(L, 1)); + int type = lua_type(L, 2); + if (t == NULL) luaL_argerror(L, 1, "type not found"); + if (type != LUA_TNONE && type != LUA_TNIL && type != LUA_TFUNCTION) + typeerror(L, 2, "function"); + lua_settop(L, 2); + lpb_pushhooktable(L, LS); + lua_rawgetp(L, 3, t); + if (type != LUA_TNONE) { + lua_pushvalue(L, 2); + lua_rawsetp(L, 3, t); + } + return 1; +} + +static int Lpb_clear(lua_State *L) { + lpb_State *LS = default_lstate(L); + pb_State *S = (pb_State*)LS->state; + pb_Type *t; + if (lua_isnoneornil(L, 1)) { + pb_free(&LS->local), pb_init(&LS->local); + luaL_unref(L, LUA_REGISTRYINDEX, LS->defs_index); + LS->defs_index = LUA_NOREF; + luaL_unref(L, LUA_REGISTRYINDEX, LS->hooks_index); + LS->hooks_index = LUA_NOREF; + return 0; + } + LS->state = &LS->local; + t = (pb_Type*)lpb_type(LS, lpb_checkslice(L, 1)); + if (lua_isnoneornil(L, 2)) pb_deltype(&LS->local, t); + else pb_delfield(&LS->local, t, (pb_Field*)lpb_field(L, 2, t)); + LS->state = S; + lpb_cleardefaults(L, LS, t); + return 0; +} + +static int Lpb_typefmt(lua_State *L) { + pb_Slice s = lpb_checkslice(L, 1); + const char *r = NULL; + char buf[2] = {0}; + int type; + if (pb_len(s) == 1) + r = pb_typename(type = lpb_typefmt(*s.p), "!"); + else if (lpb_type(default_lstate(L), s)) + r = "message", type = PB_TBYTES; + else if ((type = pb_typebyname(s.p, PB_Tmessage)) != PB_Tmessage) { + switch (type) { +#define X(name,type,fmt) case PB_T##name: buf[0] = fmt, r = buf; break; + PB_TYPES(X) +#undef X + } + type = pb_wtypebytype(type); + } else if ((type = pb_wtypebyname(s.p, PB_Tmessage)) != PB_Tmessage) { + switch (type) { +#define X(id,name,fmt) case PB_T##id: buf[0] = fmt, r = buf; break; + PB_WIRETYPES(X) +#undef X + } + } + lua_pushstring(L, r ? r : "!"); + lua_pushinteger(L, type); + return 2; +} + + +/* protobuf encode */ + +typedef struct lpb_Env { + lua_State *L; + lpb_State *LS; + pb_Buffer *b; + pb_Slice *s; +} lpb_Env; + +static void lpb_encode (lpb_Env *e, const pb_Type *t); + +static void lpb_checktable(lua_State *L, const pb_Field *f) { + argcheck(L, lua_istable(L, -1), + 2, "table expected at field '%s', got %s", + (const char*)f->name, luaL_typename(L, -1)); +} + +static void lpbE_enum(lpb_Env *e, const pb_Field *f) { + lua_State *L = e->L; + pb_Buffer *b = e->b; + const pb_Field *ev; + int type = lua_type(L, -1); + if (type == LUA_TNUMBER) + pb_addvarint64(b, (uint64_t)lua_tonumber(L, -1)); + else if ((ev = pb_fname(f->type, + lpb_name(e->LS, lpb_toslice(L, -1)))) != NULL) + pb_addvarint32(b, ev->number); + else if (type != LUA_TSTRING) + argcheck(L, 0, 2, "number/string expected at field '%s', got %s", + (const char*)f->name, luaL_typename(L, -1)); + else + argcheck(L, 0, 2, "can not encode unknown enum '%s' at field '%s'", + lua_tostring(L, -1), (const char*)f->name); +} + +static void lpbE_field(lpb_Env *e, const pb_Field *f, size_t *plen) { + lua_State *L = e->L; + pb_Buffer *b = e->b; + size_t len; + int ltype; + if (plen) *plen = 0; + switch (f->type_id) { + case PB_Tenum: + lpbE_enum(e, f); + break; + + case PB_Tmessage: + lpb_checktable(L, f); + len = pb_bufflen(b); + lpb_encode(e, f->type); + lpb_addlength(L, b, len); + break; + + default: + ltype = lpb_addtype(L, b, -1, f->type_id, plen); + argcheck(L, ltype == 0, + 2, "%s expected for field '%s', got %s", + lua_typename(L, ltype), + (const char*)f->name, luaL_typename(L, -1)); + } +} + +static void lpbE_tagfield(lpb_Env *e, const pb_Field *f, int ignorezero) { + size_t hlen = pb_addvarint32(e->b, + pb_pair(f->number, pb_wtypebytype(f->type_id))); + size_t ignoredlen; + lpbE_field(e, f, &ignoredlen); + if (ignoredlen != 0 && ignorezero) + e->b->size -= (unsigned)(ignoredlen + hlen); +} + +static void lpbE_map(lpb_Env *e, const pb_Field *f) { + lua_State *L = e->L; + const pb_Field *kf = pb_field(f->type, 1); + const pb_Field *vf = pb_field(f->type, 2); + if (kf == NULL || vf == NULL) return; + lpb_checktable(L, f); + lua_pushnil(L); + while (lua_next(L, -2)) { + size_t len; + pb_addvarint32(e->b, pb_pair(f->number, PB_TBYTES)); + len = pb_bufflen(e->b); + lua_pushvalue(L, -2); + lpbE_tagfield(e, kf, 1); + lua_pop(L, 1); + lpbE_tagfield(e, vf, 1); + lua_pop(L, 1); + lpb_addlength(L, e->b, len); + } +} + +static void lpbE_repeated(lpb_Env *e, const pb_Field *f) { + lua_State *L = e->L; + pb_Buffer *b = e->b; + int i; + lpb_checktable(L, f); + if (f->packed) { + size_t len; + pb_addvarint32(b, pb_pair(f->number, PB_TBYTES)); + len = pb_bufflen(b); + for (i = 1; lua53_rawgeti(L, -1, i) != LUA_TNIL; ++i) { + lpbE_field(e, f, NULL); + lua_pop(L, 1); + } + lpb_addlength(L, b, len); + } else { + for (i = 1; lua53_rawgeti(L, -1, i) != LUA_TNIL; ++i) { + lpbE_tagfield(e, f, 0); + lua_pop(L, 1); + } + } + lua_pop(L, 1); +} + +static void lpb_encode(lpb_Env *e, const pb_Type *t) { + lua_State *L = e->L; + luaL_checkstack(L, 3, "message too many levels"); + lua_pushnil(L); + while (lua_next(L, -2)) { + if (lua_type(L, -2) == LUA_TSTRING) { + const pb_Field *f = + pb_fname(t, lpb_name(e->LS, lpb_toslice(L, -2))); + if (f == NULL) + /* skip */; + else if (f->type && f->type->is_map) + lpbE_map(e, f); + else if (f->repeated) + lpbE_repeated(e, f); + else if (!f->type || !f->type->is_dead) + lpbE_tagfield(e, f, t->is_proto3 && !f->oneof_idx); + } + lua_pop(L, 1); + } +} + +static int Lpb_encode(lua_State *L) { + lpb_State *LS = default_lstate(L); + const pb_Type *t = lpb_type(LS, lpb_checkslice(L, 1)); + lpb_Env e; + argcheck(L, t!=NULL, 1, "type '%s' does not exists", lua_tostring(L, 1)); + luaL_checktype(L, 2, LUA_TTABLE); + e.L = L, e.LS = LS, e.b = test_buffer(L, 3); + if (e.b == NULL) pb_resetbuffer(e.b = &LS->buffer); + lua_pushvalue(L, 2); + lpb_encode(&e, t); + if (e.b != &LS->buffer) + lua_settop(L, 3); + else { + lua_pushlstring(L, pb_buffer(e.b), pb_bufflen(e.b)); + pb_resetbuffer(e.b); + } + return 1; +} + + +/* protobuf decode */ + +#define lpb_withinput(e,ns,stmt) ((e)->s = (ns), (stmt), (e)->s = s) + +static int lpbD_message(lpb_Env *e, const pb_Type *t); + +static void lpb_usehooks(lua_State *L, lpb_State *LS, const pb_Type *t) { + lpb_pushhooktable(L, LS); + if (lua53_rawgetp(L, -1, t) != LUA_TNIL) { + lua_pushvalue(L, -3); + lua_call(L, 1, 1); + if (!lua_isnil(L, -1)) { + lua_pushvalue(L, -1); + lua_replace(L, -4); + } + } + lua_pop(L, 2); +} + +static void lpb_pushtypetable(lua_State *L, lpb_State *LS, const pb_Type *t) { + const pb_Field *f = NULL; + int mode = LS->default_mode; + lua_createtable(L, 0, t->field_count); + switch (t->is_proto3 && mode == LPB_DEFDEF ? LPB_COPYDEF : mode) { + case LPB_COPYDEF: + while (pb_nextfield(t, &f)) + if (!f->oneof_idx && lpb_pushdefault(L, LS, f, t->is_proto3)) + lua_setfield(L, -2, (const char*)f->name); + break; + case LPB_METADEF: + while (pb_nextfield(t, &f)) + if (f->repeated && lpb_pushdefault(L, LS, f, t->is_proto3)) + lua_setfield(L, -2, (const char*)f->name); + lpb_pushdefaults(L, LS, t); + lua_setmetatable(L, -2); + break; + default: /* no default value */ + break; + } +} + +static void lpb_fetchtable(lpb_Env *e, const pb_Field *f) { + lua_State *L = e->L; + if (lua53_getfield(L, -1, (const char*)f->name) == LUA_TNIL) { + lua_pop(L, 1); + lua_newtable(L); + lua_pushvalue(L, -1); + lua_setfield(L, -3, (const char*)f->name); + } +} + +static void lpbD_rawfield(lpb_Env *e, const pb_Field *f) { + lua_State *L = e->L; + pb_Slice sv, *s = e->s; + const pb_Field *ev = NULL; + uint64_t u64; + switch (f->type_id) { + case PB_Tenum: + if (pb_readvarint64(s, &u64) == 0) + luaL_error(L, "invalid varint value at offset %d", pb_pos(*s)+1); + if (!default_lstate(L)->enum_as_value) + ev = pb_field(f->type, (int32_t)u64); + if (ev) lua_pushstring(L, (const char*)ev->name); + else lpb_pushinteger(L, (lua_Integer)u64, default_lstate(L)->int64_mode); + if (e->LS->use_hooks) lpb_usehooks(L, e->LS, f->type); + break; + + case PB_Tmessage: + lpb_readbytes(L, s, &sv); + if (f->type == NULL || f->type->is_dead) + lua_pushnil(L); + else { + lpb_pushtypetable(L, e->LS, f->type); + lpb_withinput(e, &sv, lpbD_message(e, f->type)); + } + break; + + default: + lpb_readtype(L, e->LS, f->type_id, s); + } +} + +static void lpbD_field(lpb_Env *e, const pb_Field *f, uint32_t tag) { + if (pb_wtypebytype(f->type_id) == (int)pb_gettype(tag)) { + lpbD_rawfield(e, f); + return; + } + luaL_error(e->L, + "type mismatch for %s%sfield '%s' at offset %d, " + "%s expected for type %s, got %s", + f->packed ? "packed " : "", f->repeated ? "repeated " : "", + (const char*)f->name, + pb_pos(*e->s)+1, + pb_wtypename(pb_wtypebytype(f->type_id), NULL), + pb_typename(f->type_id, NULL), + pb_wtypename(pb_gettype(tag), NULL)); +} + +static void lpbD_map(lpb_Env *e, const pb_Field *f) { + lua_State *L = e->L; + pb_Slice p, *s = e->s; + int mask = 0, top = lua_gettop(L) + 1; + uint32_t tag; + lpb_fetchtable(e, f); + lpb_readbytes(L, s, &p); + if (f->type == NULL) return; + lua_pushnil(L); + lua_pushnil(L); + while (pb_readvarint32(&p, &tag)) { + int n = pb_gettag(tag); + if (n == 1 || n == 2) { + mask |= n; + lpb_withinput(e, &p, lpbD_field(e, pb_field(f->type, n), tag)); + lua_replace(L, top+n); + } + } + if (!(mask & 1) && lpb_pushdefault(L, e->LS, pb_field(f->type, 1), 1)) + lua_replace(L, top + 1), mask |= 1; + if (!(mask & 2) && lpb_pushdefault(L, e->LS, pb_field(f->type, 2), 1)) + lua_replace(L, top + 2), mask |= 2; + if (mask == 3) lua_rawset(L, -3); + else lua_pop(L, 2); + lua_pop(L, 1); +} + +static void lpbD_repeated(lpb_Env *e, const pb_Field *f, uint32_t tag) { + lua_State *L = e->L; + lpb_fetchtable(e, f); + if (pb_gettype(tag) != PB_TBYTES + || (!f->packed && pb_wtypebytype(f->type_id) == PB_TBYTES)) { + lpbD_field(e, f, tag); + lua_rawseti(L, -2, (lua_Integer)lua_rawlen(L, -2) + 1); + } else { + int len = (int)lua_rawlen(L, -1); + pb_Slice p, *s = e->s; + lpb_readbytes(L, s, &p); + while (p.p < p.end) { + lpb_withinput(e, &p, lpbD_rawfield(e, f)); + lua_rawseti(L, -2, ++len); + } + } + lua_pop(L, 1); +} + +static int lpbD_message(lpb_Env *e, const pb_Type *t) { + lua_State *L = e->L; + pb_Slice *s = e->s; + uint32_t tag; + while (pb_readvarint32(s, &tag)) { + const pb_Field *f = pb_field(t, pb_gettag(tag)); + if (f == NULL) + pb_skipvalue(s, tag); + else if (f->type && f->type->is_map) + lpbD_map(e, f); + else if (f->repeated) + lpbD_repeated(e, f, tag); + else { + lua_pushstring(L, (const char*)f->name); + lpbD_field(e, f, tag); + lua_rawset(L, -3); + } + } + if (e->LS->use_hooks) lpb_usehooks(L, e->LS, t); + return 1; +} + +static int lpb_decode(lua_State *L, pb_Slice s, int start) { + lpb_State *LS = default_lstate(L); + const pb_Type *t = lpb_type(LS, lpb_checkslice(L, 1)); + lpb_Env e; + argcheck(L, t!=NULL, 1, "type '%s' does not exists", lua_tostring(L, 1)); + lua_settop(L, start); + if (!lua_istable(L, start)) { + lua_pop(L, 1); + lpb_pushtypetable(L, LS, t); + } + e.L = L, e.LS = LS, e.s = &s; + return lpbD_message(&e, t); +} + +static int Lpb_decode(lua_State *L) { + return lpb_decode(L, lua_isnoneornil(L, 2) ? + pb_lslice(NULL, 0) : + lpb_checkslice(L, 2), 3); +} + + +/* pb module interface */ + +static int Lpb_option(lua_State *L) { +#define OPTS(X) \ + X(0, enum_as_name, LS->enum_as_value = 0) \ + X(1, enum_as_value, LS->enum_as_value = 1) \ + X(2, int64_as_number, LS->int64_mode = LPB_NUMBER) \ + X(3, int64_as_string, LS->int64_mode = LPB_STRING) \ + X(4, int64_as_hexstring, LS->int64_mode = LPB_HEXSTRING) \ + X(5, auto_default_values, LS->default_mode = LPB_DEFDEF) \ + X(6, no_default_values, LS->default_mode = LPB_NODEF) \ + X(7, use_default_values, LS->default_mode = LPB_COPYDEF) \ + X(8, use_default_metatable, LS->default_mode = LPB_METADEF) \ + X(9, enable_hooks, LS->use_hooks = 1) \ + X(10, disable_hooks, LS->use_hooks = 0) \ + + static const char *opts[] = { +#define X(ID,NAME,CODE) #NAME, + OPTS(X) +#undef X + NULL + }; + lpb_State *LS = default_lstate(L); + switch (luaL_checkoption(L, 1, NULL, opts)) { +#define X(ID,NAME,CODE) case ID: CODE; break; + OPTS(X) +#undef X + } + return 0; +#undef OPTS +} + +LUALIB_API int luaopen_lprotobuf(lua_State *L) { + luaL_Reg libs[] = { + { "pack", Lbuf_pack }, + { "unpack", Lslice_unpack }, +#define ENTRY(name) { #name, Lpb_##name } + ENTRY(clear), + ENTRY(load), + ENTRY(loadfile), + ENTRY(encode), + ENTRY(decode), + ENTRY(types), + ENTRY(fields), + ENTRY(type), + ENTRY(field), + ENTRY(typefmt), + ENTRY(enum), + ENTRY(defaults), + ENTRY(hook), + ENTRY(tohex), + ENTRY(fromhex), + ENTRY(result), + ENTRY(option), + ENTRY(state), +#undef ENTRY + { NULL, NULL } + }; + luaL_Reg meta[] = { + { "__gc", Lpb_delete }, + { "setdefault", Lpb_state }, + { NULL, NULL } + }; + if (luaL_newmetatable(L, PB_STATE)) { + luaL_setfuncs(L, meta, 0); + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); + } + luaL_newlib(L, libs); + return 1; +} + +static int Lpb_decode_unsafe(lua_State *L) { + const char *data = (const char *)lua_touserdata(L, 2); + size_t size = (size_t)luaL_checkinteger(L, 3); + if (data == NULL) typeerror(L, 2, "userdata"); + return lpb_decode(L, pb_lslice(data, size), 4); +} + +static int Lpb_slice_unsafe(lua_State *L) { + const char *data = (const char *)lua_touserdata(L, 1); + size_t size = (size_t)luaL_checkinteger(L, 2); + if (data == NULL) typeerror(L, 1, "userdata"); + return lpb_newslice(L, data, size); +} + +static int Lpb_touserdata(lua_State *L) { + pb_Slice s = lpb_toslice(L, 1); + lua_pushlightuserdata(L, (void*)s.p); + lua_pushinteger(L, pb_len(s)); + return 2; +} + +static int Lpb_use(lua_State *L) { + const char *opts[] = { "global", "local", NULL }; + lpb_State *LS = default_lstate(L); + const pb_State *GS = global_state; + switch (luaL_checkoption(L, 1, NULL, opts)) { + case 0: if (GS) LS->state = GS; break; + case 1: LS->state = &LS->local; break; + } + lua_pushboolean(L, GS != NULL); + return 1; +} + +LUALIB_API int luaopen_lprotobuf_unsafe(lua_State *L) { + luaL_Reg libs[] = { + { "decode", Lpb_decode_unsafe }, + { "slice", Lpb_slice_unsafe }, + { "touserdata", Lpb_touserdata }, + { "use", Lpb_use }, + { NULL, NULL } + }; + luaL_newlib(L, libs); + return 1; +} + +PB_NS_END \ No newline at end of file diff --git a/luaclib/src/lpbc/lpb.h b/luaclib/src/lpbc/lpb.h new file mode 100644 index 00000000..de69c89e --- /dev/null +++ b/luaclib/src/lpbc/lpb.h @@ -0,0 +1,1702 @@ +#ifndef pb_h +#define pb_h + +#ifndef PB_NS_BEGIN +# ifdef __cplusplus +# define PB_NS_BEGIN extern "C" { +# define PB_NS_END } +# else +# define PB_NS_BEGIN +# define PB_NS_END +# endif +#endif /* PB_NS_BEGIN */ + +#ifndef PB_STATIC +# if __GNUC__ +# define PB_STATIC static __attribute((unused)) +# else +# define PB_STATIC static +# endif +#endif + +#ifdef PB_STATIC_API +# ifndef PB_IMPLEMENTATION +# define PB_IMPLEMENTATION +# endif +# define PB_API PB_STATIC +#endif + +#if !defined(PB_API) && defined(_WIN32) +# ifdef PB_IMPLEMENTATION +# define PB_API __declspec(dllexport) +# else +# define PB_API __declspec(dllimport) +# endif +#endif + +#ifndef PB_API +# define PB_API extern +#endif + +#if defined(_MSC_VER) || defined(__UNIXOS2__) || defined(__SOL64__) +typedef unsigned char uint8_t; +typedef signed char int8_t; +typedef unsigned short uint16_t; +typedef signed short int16_t; +typedef unsigned int uint32_t; +typedef signed int int32_t; +typedef unsigned long long uint64_t; +typedef signed long long int64_t; + +#ifndef INT64_MIN +# define INT64_MIN LLONG_MIN +#endif + +#ifndef INT64_MAX +# define INT64_MAX LLONG_MAX +#endif + +#elif defined(__SCO__) || defined(__USLC__) || defined(__MINGW32__) +# include +#else +# include +# if (defined(__sun__) || defined(__digital__)) +# if defined(__STDC__) && (defined(__arch64__) || defined(_LP64)) +typedef unsigned long int uint64_t; +typedef signed long int int64_t; +# else +typedef unsigned long long uint64_t; +typedef signed long long int64_t; +# endif /* LP64 */ +# endif /* __sun__ || __digital__ */ +#endif + +#include +#include + +PB_NS_BEGIN + + +/* types */ + +#define PB_WIRETYPES(X) /* X(id, name, fmt) */\ + X(VARINT, "varint", 'v') X(64BIT, "64bit", 'q') X(BYTES, "bytes", 's') \ + X(GSTART, "gstart", '!') X(GEND, "gend", '!') X(32BIT, "32bit", 'd') \ + +#define PB_TYPES(X) /* X(name, type, fmt) */\ + X(double, double, 'F') X(float, float, 'f') \ + X(int64, int64_t, 'I') X(uint64, uint64_t, 'U') \ + X(int32, int32_t, 'i') X(fixed64, uint64_t, 'X') \ + X(fixed32, uint32_t, 'x') X(bool, int, 'b') \ + X(string, pb_Slice, 't') X(group, pb_Slice, 'g') \ + X(message, pb_Slice, 'S') X(bytes, pb_Slice, 's') \ + X(uint32, uint32_t, 'u') X(enum, int32_t, 'v') \ + X(sfixed32, int32_t, 'y') X(sfixed64, int64_t, 'Y') \ + X(sint32, int32_t, 'j') X(sint64, int64_t, 'J') \ + +typedef enum pb_WireType { +#define X(id,name,fmt) PB_T##id, + PB_WIRETYPES(X) +#undef X + PB_TWIRECOUNT +} pb_WireType; + +typedef enum pb_FieldType { + PB_TNONE, +#define X(name,type,fmt) PB_T##name, + PB_TYPES(X) +#undef X + PB_TYPECOUNT +} pb_FieldType; + + +/* conversions */ + +PB_API uint64_t pb_expandsig (uint32_t v); +PB_API uint32_t pb_encode_sint32 ( int32_t v); +PB_API int32_t pb_decode_sint32 (uint32_t v); +PB_API uint64_t pb_encode_sint64 ( int64_t v); +PB_API int64_t pb_decode_sint64 (uint64_t v); +PB_API uint32_t pb_encode_float (float v); +PB_API float pb_decode_float (uint32_t v); +PB_API uint64_t pb_encode_double (double v); +PB_API double pb_decode_double (uint64_t v); + + +/* decode */ + +typedef struct pb_Slice { const char *p, *start, *end; } pb_Slice; +#define pb_gettype(v) ((v) & 7) +#define pb_gettag(v) ((v) >> 3) +#define pb_pair(tag,type) ((tag) << 3 | ((type) & 7)) + +PB_API pb_Slice pb_slice (const char *p); +PB_API pb_Slice pb_lslice (const char *p, size_t len); + +PB_API size_t pb_pos (const pb_Slice s); +PB_API size_t pb_len (const pb_Slice s); + +PB_API size_t pb_readvarint32 (pb_Slice *s, uint32_t *pv); +PB_API size_t pb_readvarint64 (pb_Slice *s, uint64_t *pv); +PB_API size_t pb_readfixed32 (pb_Slice *s, uint32_t *pv); +PB_API size_t pb_readfixed64 (pb_Slice *s, uint64_t *pv); + +PB_API size_t pb_readslice (pb_Slice *s, size_t len, pb_Slice *pv); +PB_API size_t pb_readbytes (pb_Slice *s, pb_Slice *pv); +PB_API size_t pb_readgroup (pb_Slice *s, uint32_t tag, pb_Slice *pv); + +PB_API size_t pb_skipvarint (pb_Slice *s); +PB_API size_t pb_skipbytes (pb_Slice *s); +PB_API size_t pb_skipslice (pb_Slice *s, size_t len); +PB_API size_t pb_skipvalue (pb_Slice *s, uint32_t tag); + +PB_API const char *pb_wtypename (int wiretype, const char *def); +PB_API const char *pb_typename (int type, const char *def); + +PB_API int pb_typebyname (const char *name, int def); +PB_API int pb_wtypebyname (const char *name, int def); +PB_API int pb_wtypebytype (int type); + + +/* encode */ + +#define PB_SSO_SIZE (sizeof(pb_HeapBuffer)) + +typedef struct pb_HeapBuffer { + unsigned capacity; + char *buff; +} pb_HeapBuffer; + +typedef struct pb_Buffer { + unsigned size : sizeof(unsigned)*CHAR_BIT - 1; + unsigned heap : 1; + union { + char buff[PB_SSO_SIZE]; + pb_HeapBuffer h; + } u; +} pb_Buffer; + +#define pb_onheap(b) ((b)->heap) +#define pb_bufflen(b) ((b)->size) +#define pb_buffer(b) (pb_onheap(b) ? (b)->u.h.buff : (b)->u.buff) +#define pb_addsize(b,sz) ((void)((b)->size += (unsigned)(sz))) + +PB_API void pb_initbuffer (pb_Buffer *b); +PB_API void pb_resetbuffer (pb_Buffer *b); +PB_API char *pb_prepbuffsize (pb_Buffer *b, size_t len); + +PB_API pb_Slice pb_result (const pb_Buffer *b); + +PB_API size_t pb_addvarint32 (pb_Buffer *b, uint32_t v); +PB_API size_t pb_addvarint64 (pb_Buffer *b, uint64_t v); +PB_API size_t pb_addfixed32 (pb_Buffer *b, uint32_t v); +PB_API size_t pb_addfixed64 (pb_Buffer *b, uint64_t v); + +PB_API size_t pb_addslice (pb_Buffer *b, pb_Slice s); +PB_API size_t pb_addbytes (pb_Buffer *b, pb_Slice s); +PB_API size_t pb_addlength (pb_Buffer *b, size_t len); + + +/* type info database state and name table */ + +typedef struct pb_State pb_State; +typedef struct pb_Name pb_Name; +typedef struct pb_Cache pb_Cache; + +PB_API void pb_init (pb_State *S); +PB_API void pb_free (pb_State *S); + +PB_API pb_Name *pb_newname (pb_State *S, pb_Slice s, pb_Cache *cache); +PB_API void pb_delname (pb_State *S, pb_Name *name); +PB_API pb_Name *pb_usename (pb_Name *name); + +PB_API const pb_Name *pb_name (const pb_State *S, pb_Slice s, pb_Cache *cache); + + +/* type info */ + +typedef struct pb_Type pb_Type; +typedef struct pb_Field pb_Field; + +#define PB_OK 0 +#define PB_ERROR 1 +#define PB_ENOMEM 2 + +PB_API int pb_load (pb_State *S, pb_Slice *s); + +PB_API pb_Type *pb_newtype (pb_State *S, pb_Name *tname); +PB_API void pb_deltype (pb_State *S, pb_Type *t); +PB_API pb_Field *pb_newfield (pb_State *S, pb_Type *t, pb_Name *fname, int32_t number); +PB_API void pb_delfield (pb_State *S, pb_Type *t, pb_Field *f); + +PB_API const pb_Type *pb_type (const pb_State *S, const pb_Name *tname); +PB_API const pb_Field *pb_fname (const pb_Type *t, const pb_Name *tname); +PB_API const pb_Field *pb_field (const pb_Type *t, int32_t number); + +PB_API const pb_Name *pb_oneofname (const pb_Type *t, int oneof_index); + +PB_API int pb_nexttype (const pb_State *S, const pb_Type **ptype); +PB_API int pb_nextfield (const pb_Type *t, const pb_Field **pfield); + + +/* util: memory pool */ + +#define PB_POOLSIZE 4096 + +typedef struct pb_Pool { + void *pages; + void *freed; + size_t obj_size; +} pb_Pool; + +PB_API void pb_initpool (pb_Pool *pool, size_t obj_size); +PB_API void pb_freepool (pb_Pool *pool); + +PB_API void *pb_poolalloc (pb_Pool *pool); +PB_API void pb_poolfree (pb_Pool *pool, void *obj); + +/* util: hash table */ + +typedef struct pb_Table pb_Table; +typedef struct pb_Entry pb_Entry; +typedef ptrdiff_t pb_Key; + +PB_API void pb_inittable (pb_Table *t, size_t entrysize); +PB_API void pb_freetable (pb_Table *t); + +PB_API size_t pb_resizetable (pb_Table *t, size_t size); + +PB_API pb_Entry *pb_gettable (const pb_Table *t, pb_Key key); +PB_API pb_Entry *pb_settable (pb_Table *t, pb_Key key); + +PB_API int pb_nextentry (const pb_Table *t, const pb_Entry **pentry); + +struct pb_Table { + unsigned size; + unsigned lastfree; + unsigned entry_size : sizeof(unsigned)*CHAR_BIT - 1; + unsigned has_zero : 1; + pb_Entry *hash; +}; + +struct pb_Entry { + ptrdiff_t next; + pb_Key key; +}; + + +/* fields */ + +#define PB_CACHE_SIZE (53) + +typedef struct pb_NameEntry { + struct pb_NameEntry *next; + unsigned hash : 32; + unsigned length : 16; + unsigned refcount : 16; +} pb_NameEntry; + +typedef struct pb_NameTable { + size_t size; + size_t count; + pb_NameEntry **hash; +} pb_NameTable; + +typedef struct pb_CacheSlot { + const char *name; + unsigned hash; +} pb_CacheSlot; + +struct pb_Cache { + pb_CacheSlot slots[PB_CACHE_SIZE][2]; + unsigned hash; +}; + +struct pb_State { + pb_NameTable nametable; + pb_Table types; + pb_Pool typepool; + pb_Pool fieldpool; +}; + +struct pb_Field { + pb_Name *name; + pb_Type *type; + pb_Name *default_value; + int32_t number; + unsigned oneof_idx : 24; + unsigned type_id : 5; /* PB_T* enum */ + unsigned repeated : 1; + unsigned packed : 1; + unsigned scalar : 1; +}; + +struct pb_Type { + pb_Name *name; + const char *basename; + pb_Table field_tags; + pb_Table field_names; + pb_Table oneof_index; + unsigned field_count : 28; + unsigned is_enum : 1; + unsigned is_map : 1; + unsigned is_proto3 : 1; + unsigned is_dead : 1; +}; + + +PB_NS_END + +#endif /* pb_h */ + + +#if defined(PB_IMPLEMENTATION) && !defined(pb_implemented) +#define pb_implemented + +#define PB_MAX_SIZET ((unsigned)~0 - 100) +#define PB_MAX_HASHSIZE ((unsigned)~0 - 100) +#define PB_MIN_STRTABLE_SIZE 16 +#define PB_MIN_HASHTABLE_SIZE 8 +#define PB_HASHLIMIT 5 + +#include +#include +#include + +PB_NS_BEGIN + + +/* conversions */ + +PB_API uint32_t pb_encode_sint32(int32_t value) +{ return ((uint32_t)value << 1) ^ -(value < 0); } + +PB_API int32_t pb_decode_sint32(uint32_t value) +{ return (value >> 1) ^ -(int32_t)(value & 1); } + +PB_API uint64_t pb_encode_sint64(int64_t value) +{ return ((uint64_t)value << 1) ^ -(value < 0); } + +PB_API int64_t pb_decode_sint64(uint64_t value) +{ return (value >> 1) ^ -(int64_t)(value & 1); } + +PB_API uint64_t pb_expandsig(uint32_t value) +{ uint64_t m = (uint64_t)1U << 31; return (value ^ m) - m; } + +PB_API uint32_t pb_encode_float(float value) +{ union { uint32_t u32; float f; } u; u.f = value; return u.u32; } + +PB_API float pb_decode_float(uint32_t value) +{ union { uint32_t u32; float f; } u; u.u32 = value; return u.f; } + +PB_API uint64_t pb_encode_double(double value) +{ union { uint64_t u64; double d; } u; u.d = value; return u.u64; } + +PB_API double pb_decode_double(uint64_t value) +{ union { uint64_t u64; double d; } u; u.u64 = value; return u.d; } + + +/* decode */ + +PB_API pb_Slice pb_slice(const char *s) +{ return s ? pb_lslice(s, strlen(s)) : pb_lslice(NULL, 0); } + +PB_API size_t pb_pos(const pb_Slice s) { return s.p - s.start; } +PB_API size_t pb_len(const pb_Slice s) { return s.end - s.p; } + +static size_t pb_readvarint_slow(pb_Slice *s, uint64_t *pv) { + const char *p = s->p; + uint64_t n = 0; + int i = 0; + while (s->p < s->end && i < 10) { + int b = *s->p++; + n |= ((uint64_t)b & 0x7F) << (7*i++); + if ((b & 0x80) == 0) { + *pv = n; + return i; + } + } + s->p = p; + return 0; +} + +static size_t pb_readvarint32_fallback(pb_Slice *s, uint32_t *pv) { + const uint8_t *p = (const uint8_t*)s->p, *o = p; + uint32_t b, n; + for (;;) { + n = *p++ - 0x80, n += (b = *p++) << 7; if (!(b & 0x80)) break; + n -= 0x80 << 7, n += (b = *p++) << 14; if (!(b & 0x80)) break; + n -= 0x80 << 14, n += (b = *p++) << 21; if (!(b & 0x80)) break; + n -= 0x80 << 21, n += (b = *p++) << 28; if (!(b & 0x80)) break; + /* n -= 0x80 << 28; */ + if (!(*p++ & 0x80)) break; + if (!(*p++ & 0x80)) break; + if (!(*p++ & 0x80)) break; + if (!(*p++ & 0x80)) break; + if (!(*p++ & 0x80)) break; + return 0; + } + *pv = n; + s->p = (const char*)p; + return p - o; +} + +static size_t pb_readvarint64_fallback(pb_Slice *s, uint64_t *pv) { + const uint8_t *p = (const uint8_t*)s->p, *o = p; + uint32_t b, n1, n2 = 0, n3 = 0; + for (;;) { + n1 = *p++ - 0x80, n1 += (b = *p++) << 7; if (!(b & 0x80)) break; + n1 -= 0x80 << 7, n1 += (b = *p++) << 14; if (!(b & 0x80)) break; + n1 -= 0x80 << 14, n1 += (b = *p++) << 21; if (!(b & 0x80)) break; + n1 -= 0x80 << 21, n2 += (b = *p++) ; if (!(b & 0x80)) break; + n2 -= 0x80 , n2 += (b = *p++) << 7; if (!(b & 0x80)) break; + n2 -= 0x80 << 7, n2 += (b = *p++) << 14; if (!(b & 0x80)) break; + n2 -= 0x80 << 14, n2 += (b = *p++) << 21; if (!(b & 0x80)) break; + n2 -= 0x80 << 21, n3 += (b = *p++) ; if (!(b & 0x80)) break; + n3 -= 0x80 , n3 += (b = *p++) << 7; if (!(b & 0x80)) break; + return 0; + } + *pv = n1 | ((uint64_t)n2 << 28) | ((uint64_t)n3 << 56); + s->p = (const char*)p; + return p - o; +} + +PB_API pb_Slice pb_lslice(const char *s, size_t len) { + pb_Slice slice; + slice.start = slice.p = s; + slice.end = s + len; + return slice; +} + +PB_API size_t pb_readvarint32(pb_Slice *s, uint32_t *pv) { + uint64_t u64; + size_t ret; + if (s->p >= s->end) return 0; + if (!(*s->p & 0x80)) { *pv = *s->p++; return 1; } + if (pb_len(*s) >= 10 || !(s->end[-1] & 0x80)) + return pb_readvarint32_fallback(s, pv); + if ((ret = pb_readvarint_slow(s, &u64)) != 0) + *pv = (uint32_t)u64; + return ret; +} + +PB_API size_t pb_readvarint64(pb_Slice *s, uint64_t *pv) { + if (s->p >= s->end) return 0; + if (!(*s->p & 0x80)) { *pv = *s->p++; return 1; } + if (pb_len(*s) >= 10 || !(s->end[-1] & 0x80)) + return pb_readvarint64_fallback(s, pv); + return pb_readvarint_slow(s, pv); +} + +PB_API size_t pb_readfixed32(pb_Slice *s, uint32_t *pv) { + int i; + uint32_t n = 0; + if (s->p + 4 > s->end) + return 0; + for (i = 3; i >= 0; --i) { + n <<= 8; + n |= s->p[i] & 0xFF; + } + s->p += 4; + *pv = n; + return 4; +} + +PB_API size_t pb_readfixed64(pb_Slice *s, uint64_t *pv) { + int i; + uint64_t n = 0; + if (s->p + 8 > s->end) + return 0; + for (i = 7; i >= 0; --i) { + n <<= 8; + n |= s->p[i] & 0xFF; + } + s->p += 8; + *pv = n; + return 8; +} + +PB_API size_t pb_readslice(pb_Slice *s, size_t len, pb_Slice *pv) { + if (pb_len(*s) < len) + return 0; + pv->start = s->start; + pv->p = s->p; + pv->end = s->p + len; + s->p = pv->end; + return len; +} + +PB_API size_t pb_readbytes(pb_Slice *s, pb_Slice *pv) { + const char *p = s->p; + uint64_t len; + if (pb_readvarint64(s, &len) == 0 || pb_len(*s) < len) { + s->p = p; + return 0; + } + pv->start = s->start; + pv->p = s->p; + pv->end = s->p + len; + s->p = pv->end; + return s->p - p; +} + +PB_API size_t pb_readgroup(pb_Slice *s, uint32_t tag, pb_Slice *pv) { + const char *p = s->p; + uint32_t newtag = 0; + size_t count; + assert(pb_gettype(tag) == PB_TGSTART); + while ((count = pb_readvarint32(s, &newtag)) != 0) { + if (pb_gettype(newtag) == PB_TGEND) { + if (pb_gettag(newtag) != pb_gettag(tag)) + break; + pv->start = s->start; + pv->p = p; + pv->end = s->p - count; + return s->p - p; + } + pb_skipvalue(s, newtag); + } + s->p = p; + return 0; +} + +PB_API size_t pb_skipvalue(pb_Slice *s, uint32_t tag) { + const char *p = s->p; + size_t ret = 0; + pb_Slice data; + switch (pb_gettype(tag)) { + default: break; + case PB_TVARINT: ret = pb_skipvarint(s); break; + case PB_T64BIT: ret = pb_skipslice(s, 8); break; + case PB_TBYTES: ret = pb_skipbytes(s); break; + case PB_T32BIT: ret = pb_skipslice(s, 4); break; + case PB_TGSTART: ret = pb_readgroup(s, tag, &data); break; + } + if (!ret) s->p = p; + return ret; +} + +PB_API size_t pb_skipvarint(pb_Slice *s) { + const char *p = s->p, *op = p; + while (p < s->end && (*p & 0x80) != 0) ++p; + if (p >= s->end) return 0; + s->p = ++p; + return p - op; +} + +PB_API size_t pb_skipbytes(pb_Slice *s) { + const char *p = s->p; + uint64_t var; + if (!pb_readvarint64(s, &var)) return 0; + if (pb_len(*s) < var) { + s->p = p; + return 0; + } + s->p += var; + return s->p - p; +} + +PB_API size_t pb_skipslice(pb_Slice *s, size_t len) { + if (s->p + len > s->end) return 0; + s->p += len; + return len; +} + +PB_API int pb_wtypebytype(int type) { + switch (type) { + case PB_Tdouble: return PB_T64BIT; + case PB_Tfloat: return PB_T32BIT; + case PB_Tint64: return PB_TVARINT; + case PB_Tuint64: return PB_TVARINT; + case PB_Tint32: return PB_TVARINT; + case PB_Tfixed64: return PB_T64BIT; + case PB_Tfixed32: return PB_T32BIT; + case PB_Tbool: return PB_TVARINT; + case PB_Tstring: return PB_TBYTES; + case PB_Tmessage: return PB_TBYTES; + case PB_Tbytes: return PB_TBYTES; + case PB_Tuint32: return PB_TVARINT; + case PB_Tenum: return PB_TVARINT; + case PB_Tsfixed32: return PB_T32BIT; + case PB_Tsfixed64: return PB_T64BIT; + case PB_Tsint32: return PB_TVARINT; + case PB_Tsint64: return PB_TVARINT; + default: return PB_TWIRECOUNT; + } +} + +PB_API const char *pb_wtypename(int wiretype, const char *def) { + switch (wiretype) { +#define X(id,name,fmt) case PB_T##id: return name; + PB_WIRETYPES(X) +#undef X + default: return def ? def : ""; + } +} + +PB_API const char *pb_typename(int type, const char *def) { + switch (type) { +#define X(name,type,fmt) case PB_T##name: return #name; + PB_TYPES(X) +#undef X + default: return def ? def : ""; + } +} + +PB_API int pb_typebyname(const char *name, int def) { + static struct entry { const char *name; int value; } names[] = { +#define X(name,type,fmt) { #name, PB_T##name }, + PB_TYPES(X) +#undef X + { NULL, 0 } + }; + struct entry *p; + for (p = names; p->name != NULL; ++p) + if (strcmp(p->name, name) == 0) + return p->value; + return def; +} + +PB_API int pb_wtypebyname(const char *name, int def) { + static struct entry { const char *name; int value; } names[] = { +#define X(id,name,fmt) { name, PB_T##id }, + PB_WIRETYPES(X) +#undef X + { NULL, 0 } + }; + struct entry *p; + for (p = names; p->name != NULL; ++p) + if (strcmp(p->name, name) == 0) + return p->value; + return def; +} + + +/* encode */ + +PB_API pb_Slice pb_result(const pb_Buffer *b) +{ pb_Slice slice = pb_lslice(pb_buffer(b), b->size); return slice; } + +PB_API void pb_initbuffer(pb_Buffer *b) +{ memset(b, 0, sizeof(pb_Buffer)); } + +PB_API void pb_resetbuffer(pb_Buffer *b) +{ if (pb_onheap(b)) free(b->u.h.buff); pb_initbuffer(b); } + +static int pb_write32(char *buff, uint32_t n) { + int p, c = 0; + do { + p = n & 0x7F; if ((n >>= 7) == 0) break; *buff++ = p | 0x80, ++c; + p = n & 0x7F; if ((n >>= 7) == 0) break; *buff++ = p | 0x80, ++c; + p = n & 0x7F; if ((n >>= 7) == 0) break; *buff++ = p | 0x80, ++c; + p = n & 0x7F; if ((n >>= 7) == 0) break; *buff++ = p | 0x80, ++c; + p = n; + } while (0); + return *buff++ = p, ++c; +} + +static int pb_write64(char *buff, uint64_t n) { + int p, c = 0; + do { + p = n & 0x7F; if ((n >>= 7) == 0) break; *buff++ = p | 0x80, ++c; + p = n & 0x7F; if ((n >>= 7) == 0) break; *buff++ = p | 0x80, ++c; + p = n & 0x7F; if ((n >>= 7) == 0) break; *buff++ = p | 0x80, ++c; + p = n & 0x7F; if ((n >>= 7) == 0) break; *buff++ = p | 0x80, ++c; + p = n & 0x7F; if ((n >>= 7) == 0) break; *buff++ = p | 0x80, ++c; + p = n & 0x7F; if ((n >>= 7) == 0) break; *buff++ = p | 0x80, ++c; + p = n & 0x7F; if ((n >>= 7) == 0) break; *buff++ = p | 0x80, ++c; + p = n & 0x7F; if ((n >>= 7) == 0) break; *buff++ = p | 0x80, ++c; + p = n & 0x7F; if ((n >>= 7) == 0) break; *buff++ = p | 0x80, ++c; + p = n & 0x7F; + } while (0); + return *buff++ = p, ++c; +} + +PB_API char *pb_prepbuffsize(pb_Buffer *b, size_t len) { + size_t capacity = pb_onheap(b) ? b->u.h.capacity : PB_SSO_SIZE; + if (b->size + len > capacity) { + char *newp, *oldp = pb_onheap(b) ? b->u.h.buff : NULL; + size_t expected = b->size + len; + size_t newsize = PB_SSO_SIZE; + while (newsize < PB_MAX_SIZET/2 && newsize < expected) + newsize += newsize >> 1; + if (newsize < expected) return NULL; + if ((newp = (char*)realloc(oldp, newsize)) == NULL) return NULL; + if (!pb_onheap(b)) memcpy(newp, pb_buffer(b), b->size); + b->heap = 1; + b->u.h.buff = newp; + b->u.h.capacity = (unsigned)newsize; + } + return &pb_buffer(b)[b->size]; +} + +PB_API size_t pb_addslice(pb_Buffer *b, pb_Slice s) { + size_t len = pb_len(s); + char *buff = pb_prepbuffsize(b, len); + if (buff == NULL) return 0; + memcpy(buff, s.p, len); + pb_addsize(b, len); + return len; +} + +PB_API size_t pb_addlength(pb_Buffer *b, size_t len) { + char buff[10], *s; + size_t bl, ml; + if ((bl = pb_bufflen(b)) < len) + return 0; + ml = pb_write64(buff, bl - len); + if (pb_prepbuffsize(b, ml) == NULL) return 0; + s = pb_buffer(b) + len; + memmove(s+ml, s, bl - len); + memcpy(s, buff, ml); + pb_addsize(b, ml); + return ml; +} + +PB_API size_t pb_addbytes(pb_Buffer *b, pb_Slice s) { + size_t ret, len = pb_len(s); + if (pb_prepbuffsize(b, len+5) == NULL) return 0; + ret = pb_addvarint32(b, (uint32_t)len); + return ret + pb_addslice(b, s); +} + +PB_API size_t pb_addvarint32(pb_Buffer *b, uint32_t n) { + char *buff = pb_prepbuffsize(b, 5); + size_t l; + if (buff == NULL) return 0; + pb_addsize(b, l = pb_write32(buff, n)); + return l; +} + +PB_API size_t pb_addvarint64(pb_Buffer *b, uint64_t n) { + char *buff = pb_prepbuffsize(b, 10); + size_t l; + if (buff == NULL) return 0; + pb_addsize(b, l = pb_write64(buff, n)); + return l; +} + +PB_API size_t pb_addfixed32(pb_Buffer *b, uint32_t n) { + char *ch = pb_prepbuffsize(b, 4); + if (ch == NULL) return 0; + *ch++ = n & 0xFF; n >>= 8; + *ch++ = n & 0xFF; n >>= 8; + *ch++ = n & 0xFF; n >>= 8; + *ch = n & 0xFF; + pb_addsize(b, 4); + return 4; +} + +PB_API size_t pb_addfixed64(pb_Buffer *b, uint64_t n) { + char *ch = pb_prepbuffsize(b, 8); + if (ch == NULL) return 0; + *ch++ = n & 0xFF; n >>= 8; + *ch++ = n & 0xFF; n >>= 8; + *ch++ = n & 0xFF; n >>= 8; + *ch++ = n & 0xFF; n >>= 8; + *ch++ = n & 0xFF; n >>= 8; + *ch++ = n & 0xFF; n >>= 8; + *ch++ = n & 0xFF; n >>= 8; + *ch = n & 0xFF; + pb_addsize(b, 8); + return 8; +} + + +/* memory pool */ + +PB_API void pb_initpool(pb_Pool *pool, size_t obj_size) { + memset(pool, 0, sizeof(pb_Pool)); + pool->obj_size = obj_size; + assert(obj_size > sizeof(void*) && obj_size < PB_POOLSIZE/4); +} + +PB_API void pb_freepool(pb_Pool *pool) { + void *page = pool->pages; + while (page) { + void *next = *(void**)((char*)page + PB_POOLSIZE - sizeof(void*)); + free(page); + page = next; + } + pb_initpool(pool, pool->obj_size); +} + +PB_API void *pb_poolalloc(pb_Pool *pool) { + void *obj = pool->freed; + if (obj == NULL) { + size_t objsize = pool->obj_size, offset; + void *newpage = malloc(PB_POOLSIZE); + if (newpage == NULL) return NULL; + offset = ((PB_POOLSIZE - sizeof(void*)) / objsize - 1) * objsize; + for (; offset > 0; offset -= objsize) { + void **entry = (void**)((char*)newpage + offset); + *entry = pool->freed, pool->freed = (void*)entry; + } + *(void**)((char*)newpage + PB_POOLSIZE - sizeof(void*)) = pool->pages; + pool->pages = newpage; + return newpage; + } + pool->freed = *(void**)obj; + return obj; +} + +PB_API void pb_poolfree(pb_Pool *pool, void *obj) +{ *(void**)obj = pool->freed, pool->freed = obj; } + + +/* hash table */ + +#define pbT_offset(a,b) ((char*)(a) - (char*)(b)) +#define pbT_index(a,b) ((pb_Entry*)((char*)(a) + (b))) + +PB_API void pb_inittable(pb_Table *t, size_t entrysize) +{ memset(t, 0, sizeof(pb_Table)), t->entry_size = (unsigned)entrysize; } + +PB_API void pb_freetable(pb_Table *t) +{ free(t->hash); pb_inittable(t, t->entry_size); } + +static pb_Entry *pbT_hash(const pb_Table *t, pb_Key key) { + size_t h = ((size_t)key*2654435761U)&(t->size-1); + if (key && h == 0) h = 1; + return pbT_index(t->hash, h*t->entry_size); +} + +static pb_Entry *pbT_newkey(pb_Table *t, pb_Key key) { + pb_Entry *mp, *on, *next, *f = NULL; + if (t->size == 0 && pb_resizetable(t, (size_t)t->size*2) == 0) return NULL; + if (key == 0) { + mp = t->hash; + t->has_zero = 1; + } else if ((mp = pbT_hash(t, key))->key != 0) { + while (t->lastfree > t->entry_size) { + pb_Entry *cur = pbT_index(t->hash, t->lastfree -= t->entry_size); + if (cur->key == 0 && cur->next == 0) { f = cur; break; } + } + if (f == NULL) return pb_resizetable(t, (size_t)t->size*2u) ? + pbT_newkey(t, key) : NULL; + if ((on = pbT_hash(t, mp->key)) != mp) { + while ((next = pbT_index(on, on->next)) != mp) on = next; + on->next = pbT_offset(f, on); + memcpy(f, mp, t->entry_size); + if (mp->next != 0) f->next += pbT_offset(mp, f), mp->next = 0; + } else { + if (mp->next != 0) f->next = pbT_offset(mp, f) + mp->next; + else assert(f->next == 0); + mp->next = pbT_offset(f, mp); + mp = f; + } + } + mp->key = key; + if (t->entry_size != sizeof(pb_Entry)) + memset(mp+1, 0, t->entry_size - sizeof(pb_Entry)); + return mp; +} + +PB_API size_t pb_resizetable(pb_Table *t, size_t size) { + pb_Table nt = *t; + unsigned i, rawsize = t->size*t->entry_size; + unsigned newsize = PB_MIN_HASHTABLE_SIZE; + while (newsize < PB_MAX_HASHSIZE/t->entry_size && newsize < size) + newsize <<= 1; + if (newsize < size) return 0; + nt.size = newsize; + nt.lastfree = nt.entry_size * newsize; + nt.hash = (pb_Entry*)malloc(nt.lastfree); + if (nt.hash == NULL) return 0; + memset(nt.hash, 0, nt.lastfree); + for (i = 0; i < rawsize; i += t->entry_size) { + pb_Entry *olde = (pb_Entry*)((char*)t->hash + i); + pb_Entry *newe = pbT_newkey(&nt, olde->key); + if (nt.entry_size > sizeof(pb_Entry)) + memcpy(newe+1, olde+1, nt.entry_size - sizeof(pb_Entry)); + } + free(t->hash); + *t = nt; + return newsize; +} + +PB_API pb_Entry *pb_gettable(const pb_Table *t, pb_Key key) { + pb_Entry *entry; + if (t == NULL || t->size == 0) + return NULL; + if (key == 0) + return t->has_zero ? t->hash : NULL; + for (entry = pbT_hash(t, key); + entry->key != key; + entry = pbT_index(entry, entry->next)) + if (entry->next == 0) return NULL; + return entry; +} + +PB_API pb_Entry *pb_settable(pb_Table *t, pb_Key key) { + pb_Entry *entry; + if ((entry = pb_gettable(t, key)) != NULL) + return entry; + return pbT_newkey(t, key); +} + +PB_API int pb_nextentry(const pb_Table *t, const pb_Entry **pentry) { + size_t i = *pentry ? pbT_offset(*pentry, t->hash) : 0; + size_t size = (size_t)t->size*t->entry_size; + if (*pentry == NULL && t->has_zero) { + *pentry = t->hash; + return 1; + } + while (i += t->entry_size, i < size) { + pb_Entry *entry = pbT_index(t->hash, i); + if (entry->key != 0) { + *pentry = entry; + return 1; + } + } + *pentry = NULL; + return 0; +} + + +/* name table */ + +static void pbN_init(pb_State *S) +{ memset(&S->nametable, 0, sizeof(pb_NameTable)); } + +PB_API pb_Name *pb_usename(pb_Name *name) +{ if (name != NULL) ++((pb_NameEntry*)name-1)->refcount; return name; } + +static void pbN_free(pb_State *S) { + pb_NameTable *nt = &S->nametable; + size_t i; + for (i = 0; i < nt->size; ++i) { + pb_NameEntry *ne = nt->hash[i]; + while (ne != NULL) { + pb_NameEntry *next = ne->next; + free(ne); + ne = next; + } + } + free(nt->hash); + pbN_init(S); +} + +static unsigned pbN_calchash(pb_Slice s) { + size_t len = pb_len(s); + unsigned h = (unsigned)len; + size_t step = (len >> PB_HASHLIMIT) + 1; + for (; len >= step; len -= step) + h ^= ((h<<5) + (h>>2) + (unsigned char)(s.p[len - 1])); + return h; +} + +static size_t pbN_resize(pb_State *S, size_t size) { + pb_NameTable *nt = &S->nametable; + pb_NameEntry **hash; + size_t i, newsize = PB_MIN_STRTABLE_SIZE; + while (newsize < PB_MAX_HASHSIZE/sizeof(pb_NameEntry*) && newsize < size) + newsize <<= 1; + if (newsize < size) return 0; + hash = (pb_NameEntry**)malloc(newsize * sizeof(pb_NameEntry*)); + if (hash == NULL) return 0; + memset(hash, 0, newsize * sizeof(pb_NameEntry*)); + for (i = 0; i < nt->size; ++i) { + pb_NameEntry *entry = nt->hash[i]; + while (entry != NULL) { + pb_NameEntry *next = entry->next; + pb_NameEntry **newh = &hash[entry->hash & (newsize - 1)]; + entry->next = *newh, *newh = entry; + entry = next; + } + } + free(nt->hash); + nt->hash = hash; + nt->size = newsize; + return newsize; +} + +static pb_NameEntry *pbN_newname(pb_State *S, pb_Slice s, unsigned hash) { + pb_NameTable *nt = &S->nametable; + pb_NameEntry **list, *newobj; + size_t len = pb_len(s); + if (nt->count >= nt->size && !pbN_resize(S, nt->size * 2)) return NULL; + list = &nt->hash[hash & (nt->size - 1)]; + newobj = (pb_NameEntry*)malloc(sizeof(pb_NameEntry) + len + 1); + if (newobj == NULL) return NULL; + newobj->next = *list; + newobj->length = (unsigned)len; + newobj->refcount = 0; + newobj->hash = hash; + memcpy(newobj+1, s.p, len); + ((char*)(newobj+1))[len] = '\0'; + *list = newobj; + ++nt->count; + return newobj; +} + +static void pbN_delname(pb_State *S, pb_NameEntry *name) { + pb_NameTable *nt = &S->nametable; + pb_NameEntry **list = &nt->hash[name->hash & (nt->size - 1)]; + while (*list != NULL) { + if (*list != name) + list = &(*list)->next; + else { + *list = (*list)->next; + --nt->count; + free(name); + break; + } + } +} + +static pb_NameEntry *pbN_getname(const pb_State *S, pb_Slice s, unsigned hash) { + const pb_NameTable *nt = &S->nametable; + size_t len = pb_len(s); + if (nt->hash) { + pb_NameEntry *entry = nt->hash[hash & (nt->size - 1)]; + for (; entry != NULL; entry = entry->next) + if (entry->hash == hash && entry->length == len + && memcmp(s.p, entry + 1, len) == 0) + return entry; + } + return NULL; +} + +PB_API void pb_delname(pb_State *S, pb_Name *name) { + if (name != NULL) { + pb_NameEntry *ne = (pb_NameEntry*)name - 1; + if (ne->refcount <= 1) { pbN_delname(S, ne); return; } + --ne->refcount; + } +} + +PB_API pb_Name *pb_newname(pb_State *S, pb_Slice s, pb_Cache *cache) { + pb_NameEntry *entry; + if (s.p == NULL) return NULL; + (void)cache; + assert(cache == NULL); + /* if (cache == NULL) */{ + unsigned hash = pbN_calchash(s); + entry = pbN_getname(S, s, hash); + if (entry == NULL) entry = pbN_newname(S, s, hash); + }/* else { + pb_Name *name = (pb_Name*)pb_name(S, s, cache); + if (name) return pb_usename(name); + entry = pbN_newname(S, s, cache->hash); + }*/ + return entry ? pb_usename((pb_Name*)(entry + 1)) : NULL; +} + +PB_API const pb_Name *pb_name(const pb_State *S, pb_Slice s, pb_Cache *cache) { + pb_NameEntry *entry = NULL; + pb_CacheSlot *slot; + if (s.p == NULL) return NULL; + if (cache == NULL) + entry = pbN_getname(S, s, pbN_calchash(s)); + else { + slot = cache->slots[((uintptr_t)s.p*2654435761U)%PB_CACHE_SIZE]; + if (slot[0].name == s.p) + entry = pbN_getname(S, s, cache->hash = slot[0].hash); + else if (slot[1].name == s.p) + entry = pbN_getname(S, s, cache->hash = (++slot)[0].hash); + else + slot[1] = slot[0], slot[0].name = s.p; + if (entry == NULL) { + cache->hash = slot[0].hash = pbN_calchash(s); + entry = pbN_getname(S, s, slot[0].hash); + } + } + return entry ? (pb_Name*)(entry + 1) : NULL; +} + + +/* state */ + +typedef struct pb_TypeEntry { pb_Entry entry; pb_Type *value; } pb_TypeEntry; +typedef struct pb_FieldEntry { pb_Entry entry; pb_Field *value; } pb_FieldEntry; + +typedef struct pb_OneofEntry { + pb_Entry entry; + pb_Name *name; + unsigned index; +} pb_OneofEntry; + +PB_API void pb_init(pb_State *S) { + memset(S, 0, sizeof(pb_State)); + S->types.entry_size = sizeof(pb_TypeEntry); + pb_initpool(&S->typepool, sizeof(pb_Type)); + pb_initpool(&S->fieldpool, sizeof(pb_Field)); +} + +PB_API void pb_free(pb_State *S) { + const pb_TypeEntry *te = NULL; + if (S == NULL) return; + while (pb_nextentry(&S->types, (const pb_Entry**)&te)) + if (te->value != NULL) pb_deltype(S, te->value); + pb_freetable(&S->types); + pb_freepool(&S->typepool); + pb_freepool(&S->fieldpool); + pbN_free(S); +} + +PB_API const pb_Type *pb_type(const pb_State *S, const pb_Name *tname) { + pb_TypeEntry *te = NULL; + if (S != NULL && tname != NULL) + te = (pb_TypeEntry*)pb_gettable(&S->types, (pb_Key)tname); + return te && !te->value->is_dead ? te->value : NULL; +} + +PB_API const pb_Field *pb_fname(const pb_Type *t, const pb_Name *name) { + pb_FieldEntry *fe = NULL; + if (t != NULL && name != NULL) + fe = (pb_FieldEntry*)pb_gettable(&t->field_names, (pb_Key)name); + return fe ? fe->value : NULL; +} + +PB_API const pb_Field *pb_field(const pb_Type *t, int32_t number) { + pb_FieldEntry *fe = NULL; + if (t != NULL) fe = (pb_FieldEntry*)pb_gettable(&t->field_tags, number); + return fe ? fe->value : NULL; +} + +PB_API const pb_Name *pb_oneofname(const pb_Type *t, int idx) { + pb_OneofEntry *oe = NULL; + if (t != NULL) oe = (pb_OneofEntry*)pb_gettable(&t->oneof_index, idx); + return oe ? oe->name : NULL; +} + +PB_API int pb_nexttype(const pb_State *S, const pb_Type **ptype) { + const pb_TypeEntry *e = NULL; + if (S != NULL) { + if (*ptype != NULL) + e = (pb_TypeEntry*)pb_gettable(&S->types, (pb_Key)(*ptype)->name); + while (pb_nextentry(&S->types, (const pb_Entry**)&e)) + if ((*ptype = e->value) != NULL && !(*ptype)->is_dead) + return 1; + } + *ptype = NULL; + return 0; +} + +PB_API int pb_nextfield(const pb_Type *t, const pb_Field **pfield) { + const pb_FieldEntry *e = NULL; + if (t != NULL) { + if (*pfield != NULL) + e = (pb_FieldEntry*)pb_gettable(&t->field_tags, (*pfield)->number); + while (pb_nextentry(&t->field_tags, (const pb_Entry**)&e)) + if ((*pfield = e->value) != NULL) + return 1; + } + *pfield = NULL; + return 0; +} + + +/* new type/field */ + +static const char *pbT_basename(const char *tname) { + const char *end = tname + strlen(tname); + while (tname < end && *--end != '.') + ; + return *end != '.' ? end : end + 1; +} + +static void pbT_inittype(pb_Type *t) { + memset(t, 0, sizeof(pb_Type)); + pb_inittable(&t->field_names, sizeof(pb_FieldEntry)); + pb_inittable(&t->field_tags, sizeof(pb_FieldEntry)); + pb_inittable(&t->oneof_index, sizeof(pb_OneofEntry)); +} + +static void pbT_freefield(pb_State *S, pb_Field *f) { + pb_delname(S, f->default_value); + pb_delname(S, f->name); + pb_poolfree(&S->fieldpool, f); +} + +PB_API pb_Type *pb_newtype(pb_State *S, pb_Name *tname) { + pb_TypeEntry *te; + pb_Type *t; + if (tname == NULL) return NULL; + te = (pb_TypeEntry*)pb_settable(&S->types, (pb_Key)tname); + if (te == NULL) return NULL; + if ((t = te->value) != NULL) { t->is_dead = 0; return t; } + if (!(t = (pb_Type*)pb_poolalloc(&S->typepool))) return NULL; + pbT_inittype(t); + t->name = tname; + t->basename = pbT_basename((const char*)tname); + return te->value = t; +} + +PB_API void pb_deltype(pb_State *S, pb_Type *t) { + pb_FieldEntry *nf = NULL; + pb_OneofEntry *ne = NULL; + if (S == NULL || t == NULL) return; + while (pb_nextentry(&t->field_names, (const pb_Entry**)&nf)) { + if (nf->value != NULL) { + pb_FieldEntry *of = (pb_FieldEntry*)pb_gettable( + &t->field_tags, nf->value->number); + if (of && of->value == nf->value) + of->entry.key = 0, of->value = NULL; + pbT_freefield(S, nf->value); + } + } + while (pb_nextentry(&t->field_tags, (const pb_Entry**)&nf)) + if (nf->value != NULL) pbT_freefield(S, nf->value); + while (pb_nextentry(&t->oneof_index, (const pb_Entry**)&ne)) + pb_delname(S, ne->name); + pb_freetable(&t->field_tags); + pb_freetable(&t->field_names); + pb_freetable(&t->oneof_index); + t->field_count = 0; + t->is_dead = 1; + /*pb_delname(S, t->name); */ + /*pb_poolfree(&S->typepool, t); */ +} + +PB_API pb_Field *pb_newfield(pb_State *S, pb_Type *t, pb_Name *fname, int32_t number) { + pb_FieldEntry *nf, *tf; + pb_Field *f; + if (fname == NULL) return NULL; + nf = (pb_FieldEntry*)pb_settable(&t->field_names, (pb_Key)fname); + tf = (pb_FieldEntry*)pb_settable(&t->field_tags, number); + if (nf == NULL || tf == NULL) return NULL; + if ((f = nf->value) != NULL && tf->value == f) { + pb_delname(S, f->default_value); + f->default_value = NULL; + return f; + } + if (!(f = (pb_Field*)pb_poolalloc(&S->fieldpool))) return NULL; + memset(f, 0, sizeof(pb_Field)); + f->name = fname; + f->type = t; + f->number = number; + if (nf->value && pb_field(t, nf->value->number) != nf->value) + pbT_freefield(S, nf->value), --t->field_count; + if (tf->value && pb_fname(t, tf->value->name) != tf->value) + pbT_freefield(S, tf->value), --t->field_count; + ++t->field_count; + return nf->value = tf->value = f; +} + +PB_API void pb_delfield(pb_State *S, pb_Type *t, pb_Field *f) { + pb_FieldEntry *nf, *tf; + int count = 0; + if (S == NULL || t == NULL || f == NULL) return; + nf = (pb_FieldEntry*)pb_gettable(&t->field_names, (pb_Key)f->name); + tf = (pb_FieldEntry*)pb_gettable(&t->field_tags, (pb_Key)f->number); + if (nf && nf->value == f) nf->entry.key = 0, nf->value = NULL, ++count; + if (tf && tf->value == f) tf->entry.key = 0, tf->value = NULL, ++count; + if (count) pbT_freefield(S, f), --t->field_count; +} + + +/* .pb proto loader */ + +typedef struct pb_Loader pb_Loader; +typedef struct pbL_FieldInfo pbL_FieldInfo; +typedef struct pbL_EnumValueInfo pbL_EnumValueInfo; +typedef struct pbL_EnumInfo pbL_EnumInfo; +typedef struct pbL_TypeInfo pbL_TypeInfo; +typedef struct pbL_FileInfo pbL_FileInfo; + +#define pbC(e) do { int r = (e); if (r != PB_OK) return r; } while (0) +#define pbCM(e) do { if ((e) == NULL) return PB_ENOMEM; } while (0) +#define pbCE(e) do { if ((e) == NULL) return PB_ERROR; } while (0) + +typedef struct pb_ArrayHeader { + unsigned count; + unsigned capacity; +} pb_ArrayHeader; + +#define pbL_rawh(A) ((pb_ArrayHeader*)(A) - 1) +#define pbL_delete(A) ((A) ? (void)free(pbL_rawh(A)) : (void)0) +#define pbL_count(A) ((A) ? pbL_rawh(A)->count : 0) +#define pbL_add(A) (pbL_grow((void**)&(A),sizeof(*(A)))==PB_OK ?\ + &(A)[pbL_rawh(A)->count++] : NULL) + +struct pb_Loader { + pb_Slice s; + pb_Buffer b; + int is_proto3; +}; + +/* parsers */ + +struct pbL_EnumValueInfo { + pb_Slice name; + int32_t number; +}; + +struct pbL_EnumInfo { + pb_Slice name; + pbL_EnumValueInfo *value; +}; + +struct pbL_FieldInfo { + pb_Slice name; + pb_Slice type_name; + pb_Slice extendee; + pb_Slice default_value; + int32_t number; + int32_t label; + int32_t type; + int32_t oneof_index; + int32_t packed; +}; + +struct pbL_TypeInfo { + pb_Slice name; + int32_t is_map; + pbL_FieldInfo *field; + pbL_FieldInfo *extension; + pbL_EnumInfo *enum_type; + pbL_TypeInfo *nested_type; + pb_Slice *oneof_decl; +}; + +struct pbL_FileInfo { + pb_Slice package; + pb_Slice syntax; + pbL_EnumInfo *enum_type; + pbL_TypeInfo *message_type; + pbL_FieldInfo *extension; +}; + +static int pbL_readbytes(pb_Loader *L, pb_Slice *pv) +{ return pb_readbytes(&L->s, pv) == 0 ? PB_ERROR : PB_OK; } + +static int pbL_beginmsg(pb_Loader *L, pb_Slice *pv) +{ pb_Slice v; pbC(pbL_readbytes(L, &v)); *pv = L->s, L->s = v; return PB_OK; } + +static void pbL_endmsg(pb_Loader *L, pb_Slice *pv) +{ L->s = *pv; } + +static int pbL_grow(void **pp, size_t objs) { + pb_ArrayHeader *nh, *h = *pp ? pbL_rawh(*pp) : NULL; + if (h == NULL || h->capacity <= h->count) { + size_t used = (h ? h->count : 0); + size_t size = used + 4, nsize = size + (size >> 1); + nh = nsize < size ? NULL : + (pb_ArrayHeader*)realloc(h, sizeof(pb_ArrayHeader)+nsize*objs); + if (nh == NULL) return PB_ENOMEM; + nh->count = (unsigned)used; + nh->capacity = (unsigned)nsize; + *pp = nh + 1; + memset((char*)*pp + used*objs, 0, (nsize - used)*objs); + } + return PB_OK; +} + +static int pbL_readint32(pb_Loader *L, int32_t *pv) { + uint32_t v; + if (pb_readvarint32(&L->s, &v) == 0) return PB_ERROR; + *pv = (int32_t)v; + return PB_OK; +} + +static int pbL_FieldOptions(pb_Loader *L, pbL_FieldInfo *info) { + pb_Slice s; + uint32_t tag; + pbC(pbL_beginmsg(L, &s)); + while (pb_readvarint32(&L->s, &tag)) { + switch (tag) { + case pb_pair(2, PB_TVARINT): /* bool packed */ + pbC(pbL_readint32(L, &info->packed)); break; + default: pb_skipvalue(&L->s, tag); + } + } + pbL_endmsg(L, &s); + return PB_OK; +} + +static int pbL_FieldDescriptorProto(pb_Loader *L, pbL_FieldInfo *info) { + pb_Slice s; + uint32_t tag; + pbCM(info); pbC(pbL_beginmsg(L, &s)); + info->packed = -1; + while (pb_readvarint32(&L->s, &tag)) { + switch (tag) { + case pb_pair(1, PB_TBYTES): /* string name */ + pbC(pbL_readbytes(L, &info->name)); break; + case pb_pair(3, PB_TVARINT): /* int32 number */ + pbC(pbL_readint32(L, &info->number)); break; + case pb_pair(4, PB_TVARINT): /* Label label */ + pbC(pbL_readint32(L, &info->label)); break; + case pb_pair(5, PB_TVARINT): /* Type type */ + pbC(pbL_readint32(L, &info->type)); break; + case pb_pair(6, PB_TBYTES): /* string type_name */ + pbC(pbL_readbytes(L, &info->type_name)); break; + case pb_pair(2, PB_TBYTES): /* string extendee */ + pbC(pbL_readbytes(L, &info->extendee)); break; + case pb_pair(7, PB_TBYTES): /* string default_value */ + pbC(pbL_readbytes(L, &info->default_value)); break; + case pb_pair(8, PB_TBYTES): /* FieldOptions options */ + pbC(pbL_FieldOptions(L, info)); break; + case pb_pair(9, PB_TVARINT): /* int32 oneof_index */ + pbC(pbL_readint32(L, &info->oneof_index)); + ++info->oneof_index; break; + default: pb_skipvalue(&L->s, tag); + } + } + pbL_endmsg(L, &s); + return PB_OK; +} + +static int pbL_EnumValueDescriptorProto(pb_Loader *L, pbL_EnumValueInfo *info) { + pb_Slice s; + uint32_t tag; + pbCM(info); pbC(pbL_beginmsg(L, &s)); + while (pb_readvarint32(&L->s, &tag)) { + switch (tag) { + case pb_pair(1, PB_TBYTES): /* string name */ + pbC(pbL_readbytes(L, &info->name)); break; + case pb_pair(2, PB_TVARINT): /* int32 number */ + pbC(pbL_readint32(L, &info->number)); break; + default: pb_skipvalue(&L->s, tag); + } + } + pbL_endmsg(L, &s); + return PB_OK; +} + +static int pbL_EnumDescriptorProto(pb_Loader *L, pbL_EnumInfo *info) { + pb_Slice s; + uint32_t tag; + pbCM(info); pbC(pbL_beginmsg(L, &s)); + while (pb_readvarint32(&L->s, &tag)) { + switch (tag) { + case pb_pair(1, PB_TBYTES): /* string name */ + pbC(pbL_readbytes(L, &info->name)); break; + case pb_pair(2, PB_TBYTES): /* EnumValueDescriptorProto value */ + pbC(pbL_EnumValueDescriptorProto(L, pbL_add(info->value))); break; + default: pb_skipvalue(&L->s, tag); + } + } + pbL_endmsg(L, &s); + return PB_OK; +} + +static int pbL_MessageOptions(pb_Loader *L, pbL_TypeInfo *info) { + pb_Slice s; + uint32_t tag; + pbCM(info); pbC(pbL_beginmsg(L, &s)); + while (pb_readvarint32(&L->s, &tag)) { + switch (tag) { + case pb_pair(7, PB_TVARINT): /* bool map_entry */ + pbC(pbL_readint32(L, &info->is_map)); break; + default: pb_skipvalue(&L->s, tag); + } + } + pbL_endmsg(L, &s); + return PB_OK; +} + +static int pbL_OneofDescriptorProto(pb_Loader *L, pbL_TypeInfo *info) { + pb_Slice s; + uint32_t tag; + pbCM(info); pbC(pbL_beginmsg(L, &s)); + while (pb_readvarint32(&L->s, &tag)) { + switch (tag) { + case pb_pair(1, PB_TBYTES): /* string name */ + pbC(pbL_readbytes(L, pbL_add(info->oneof_decl))); break; + default: pb_skipvalue(&L->s, tag); + } + } + pbL_endmsg(L, &s); + return PB_OK; +} + +static int pbL_DescriptorProto(pb_Loader *L, pbL_TypeInfo *info) { + pb_Slice s; + uint32_t tag; + pbCM(info); pbC(pbL_beginmsg(L, &s)); + while (pb_readvarint32(&L->s, &tag)) { + switch (tag) { + case pb_pair(1, PB_TBYTES): /* string name */ + pbC(pbL_readbytes(L, &info->name)); break; + case pb_pair(2, PB_TBYTES): /* FieldDescriptorProto field */ + pbC(pbL_FieldDescriptorProto(L, pbL_add(info->field))); break; + case pb_pair(6, PB_TBYTES): /* FieldDescriptorProto extension */ + pbC(pbL_FieldDescriptorProto(L, pbL_add(info->extension))); break; + case pb_pair(3, PB_TBYTES): /* DescriptorProto nested_type */ + pbC(pbL_DescriptorProto(L, pbL_add(info->nested_type))); break; + case pb_pair(4, PB_TBYTES): /* EnumDescriptorProto enum_type */ + pbC(pbL_EnumDescriptorProto(L, pbL_add(info->enum_type))); break; + case pb_pair(8, PB_TBYTES): /* OneofDescriptorProto oneof_decl */ + pbC(pbL_OneofDescriptorProto(L, info)); break; + case pb_pair(7, PB_TBYTES): /* MessageOptions options */ + pbC(pbL_MessageOptions(L, info)); break; + default: pb_skipvalue(&L->s, tag); + } + } + pbL_endmsg(L, &s); + return PB_OK; +} + +static int pbL_FileDescriptorProto(pb_Loader *L, pbL_FileInfo *info) { + pb_Slice s; + uint32_t tag; + pbCM(info); pbC(pbL_beginmsg(L, &s)); + while (pb_readvarint32(&L->s, &tag)) { + switch (tag) { + case pb_pair(2, PB_TBYTES): /* string package */ + pbC(pbL_readbytes(L, &info->package)); break; + case pb_pair(4, PB_TBYTES): /* DescriptorProto message_type */ + pbC(pbL_DescriptorProto(L, pbL_add(info->message_type))); break; + case pb_pair(5, PB_TBYTES): /* EnumDescriptorProto enum_type */ + pbC(pbL_EnumDescriptorProto(L, pbL_add(info->enum_type))); break; + case pb_pair(7, PB_TBYTES): /* FieldDescriptorProto extension */ + pbC(pbL_FieldDescriptorProto(L, pbL_add(info->extension))); break; + case pb_pair(12, PB_TBYTES): /* string syntax */ + pbC(pbL_readbytes(L, &info->syntax)); break; + default: pb_skipvalue(&L->s, tag); + } + } + pbL_endmsg(L, &s); + return PB_OK; +} + +static int pbL_FileDescriptorSet(pb_Loader *L, pbL_FileInfo **pfiles) { + uint32_t tag; + while (pb_readvarint32(&L->s, &tag)) { + switch (tag) { + case pb_pair(1, PB_TBYTES): /* FileDescriptorProto file */ + pbC(pbL_FileDescriptorProto(L, pbL_add(*pfiles))); break; + default: pb_skipvalue(&L->s, tag); + } + } + return PB_OK; +} + +/* loader */ + +static void pbL_delTypeInfo(pbL_TypeInfo *info) { + size_t i, count; + for (i = 0, count = pbL_count(info->nested_type); i < count; ++i) + pbL_delTypeInfo(&info->nested_type[i]); + for (i = 0, count = pbL_count(info->enum_type); i < count; ++i) + pbL_delete(info->enum_type[i].value); + pbL_delete(info->nested_type); + pbL_delete(info->enum_type); + pbL_delete(info->field); + pbL_delete(info->extension); +} + +static void pbL_delFileInfo(pbL_FileInfo *files) { + size_t i, count, j, jcount; + for (i = 0, count = pbL_count(files); i < count; ++i) { + for (j = 0, jcount = pbL_count(files[i].message_type); j < jcount; ++j) + pbL_delTypeInfo(&files[i].message_type[j]); + for (j = 0, jcount = pbL_count(files[i].enum_type); j < jcount; ++j) + pbL_delete(files[i].enum_type[j].value); + pbL_delete(files[i].message_type); + pbL_delete(files[i].enum_type); + pbL_delete(files[i].extension); + } + pbL_delete(files); +} + +static int pbL_prefixname(pb_State *S, pb_Slice s, size_t *ps, pb_Loader *L, pb_Name **out) { + char *buff; + *ps = pb_bufflen(&L->b); + pbCM(buff = pb_prepbuffsize(&L->b, pb_len(s) + 1)); + *buff = '.'; pb_addsize(&L->b, 1); + if (pb_addslice(&L->b, s) == 0) return PB_ENOMEM; + if (out) *out = pb_newname(S, pb_result(&L->b), NULL); + return PB_OK; +} + +static int pbL_loadEnum(pb_State *S, pbL_EnumInfo *info, pb_Loader *L) { + size_t i, count, curr; + pb_Name *name; + pb_Type *t; + pbC(pbL_prefixname(S, info->name, &curr, L, &name)); + pbCM(t = pb_newtype(S, name)); + t->is_enum = 1; + for (i = 0, count = pbL_count(info->value); i < count; ++i) { + pbL_EnumValueInfo *ev = &info->value[i]; + pbCE(pb_newfield(S, t, pb_newname(S, ev->name, NULL), ev->number)); + } + L->b.size = (unsigned)curr; + return PB_OK; +} + +static int pbL_loadField(pb_State *S, pbL_FieldInfo *info, pb_Loader *L, pb_Type *t) { + pb_Type *ft = NULL; + pb_Field *f; + if (info->type == PB_Tmessage || info->type == PB_Tenum) + pbCE(ft = pb_newtype(S, pb_newname(S, info->type_name, NULL))); + if (t == NULL) + pbCE(t = pb_newtype(S, pb_newname(S, info->extendee, NULL))); + pbCE(f = pb_newfield(S, t, pb_newname(S, info->name, NULL), info->number)); + f->default_value = pb_newname(S, info->default_value, NULL); + f->type = ft; + f->oneof_idx = info->oneof_index; + f->type_id = info->type; + f->repeated = info->label == 3; /* repeated */ + f->packed = info->packed >= 0 ? info->packed : L->is_proto3 && f->repeated; + if (f->type_id >= 9 && f->type_id <= 12) f->packed = 0; + f->scalar = (f->type == NULL); + return PB_OK; +} + +static int pbL_loadType(pb_State *S, pbL_TypeInfo *info, pb_Loader *L) { + size_t i, count, curr; + pb_Name *name; + pb_Type *t; + pbC(pbL_prefixname(S, info->name, &curr, L, &name)); + pbCM(t = pb_newtype(S, name)); + t->is_map = info->is_map; + t->is_proto3 = L->is_proto3; + for (i = 0, count = pbL_count(info->oneof_decl); i < count; ++i) { + pb_OneofEntry *e = (pb_OneofEntry*)pb_settable(&t->oneof_index, i+1); + pbCM(e); pbCE(e->name = pb_newname(S, info->oneof_decl[i], NULL)); + e->index = (int)i+1; + } + for (i = 0, count = pbL_count(info->field); i < count; ++i) + pbC(pbL_loadField(S, &info->field[i], L, t)); + for (i = 0, count = pbL_count(info->extension); i < count; ++i) + pbC(pbL_loadField(S, &info->extension[i], L, NULL)); + for (i = 0, count = pbL_count(info->enum_type); i < count; ++i) + pbC(pbL_loadEnum(S, &info->enum_type[i], L)); + for (i = 0, count = pbL_count(info->nested_type); i < count; ++i) + pbC(pbL_loadType(S, &info->nested_type[i], L)); + L->b.size = (unsigned)curr; + return PB_OK; +} + +static int pbL_loadFile(pb_State *S, pbL_FileInfo *info, pb_Loader *L) { + size_t i, count, j, jcount, curr = 0; + pb_Name *syntax; + pbCM(syntax = pb_newname(S, pb_slice("proto3"), NULL)); + for (i = 0, count = pbL_count(info); i < count; ++i) { + if (info[i].package.p) + pbC(pbL_prefixname(S, info[i].package, &curr, L, NULL)); + L->is_proto3 = (pb_name(S, info[i].syntax, NULL) == syntax); + for (j = 0, jcount = pbL_count(info[i].enum_type); j < jcount; ++j) + pbC(pbL_loadEnum(S, &info[i].enum_type[j], L)); + for (j = 0, jcount = pbL_count(info[i].message_type); j < jcount; ++j) + pbC(pbL_loadType(S, &info[i].message_type[j], L)); + for (j = 0, jcount = pbL_count(info[i].extension); j < jcount; ++j) + pbC(pbL_loadField(S, &info[i].extension[j], L, NULL)); + L->b.size = (unsigned)curr; + } + return PB_OK; +} + +PB_API int pb_load(pb_State *S, pb_Slice *s) { + pbL_FileInfo *files = NULL; + pb_Loader L; + int r; + pb_initbuffer(&L.b); + L.s = *s; + L.is_proto3 = 0; + if ((r = pbL_FileDescriptorSet(&L, &files)) == PB_OK) + r = pbL_loadFile(S, files, &L); + pbL_delFileInfo(files); + pb_resetbuffer(&L.b); + s->p = L.s.p; + return r; +} + + +PB_NS_END + +#endif /* PB_IMPLEMENTATION */ \ No newline at end of file diff --git a/luaclib/src/lpeg/HISTORY b/luaclib/src/lpeg/HISTORY new file mode 100644 index 00000000..66a8e14d --- /dev/null +++ b/luaclib/src/lpeg/HISTORY @@ -0,0 +1,100 @@ +HISTORY for LPeg 1.0.2 + +* Changes from version 1.0.1 to 1.0.2 + --------------------------------- + + some bugs fixed + +* Changes from version 0.12 to 1.0.1 + --------------------------------- + + group "names" can be any Lua value + + some bugs fixed + + other small improvements + +* Changes from version 0.11 to 0.12 + --------------------------------- + + no "unsigned short" limit for pattern sizes + + mathtime captures considered nullable + + some bugs fixed + +* Changes from version 0.10 to 0.11 + ------------------------------- + + complete reimplementation of the code generator + + new syntax for table captures + + new functions in module 're' + + other small improvements + +* Changes from version 0.9 to 0.10 + ------------------------------- + + backtrack stack has configurable size + + better error messages + + Notation for non-terminals in 're' back to A instead o + + experimental look-behind pattern + + support for external extensions + + works with Lua 5.2 + + consumes less C stack + + - "and" predicates do not keep captures + +* Changes from version 0.8 to 0.9 + ------------------------------- + + The accumulator capture was replaced by a fold capture; + programs that used the old 'lpeg.Ca' will need small changes. + + Some support for character classes from old C locales. + + A new named-group capture. + +* Changes from version 0.7 to 0.8 + ------------------------------- + + New "match-time" capture. + + New "argument capture" that allows passing arguments into the pattern. + + Better documentation for 're'. + + Several small improvements for 're'. + + The 're' module has an incompatibility with previous versions: + now, any use of a non-terminal must be enclosed in angle brackets + (like ). + +* Changes from version 0.6 to 0.7 + ------------------------------- + + Several improvements in module 're': + - better documentation; + - support for most captures (all but accumulator); + - limited repetitions p{n,m}. + + Small improvements in efficiency. + + Several small bugs corrected (special thanks to Hans Hagen + and Taco Hoekwater). + +* Changes from version 0.5 to 0.6 + ------------------------------- + + Support for non-numeric indices in grammars. + + Some bug fixes (thanks to the luatex team). + + Some new optimizations; (thanks to Mike Pall). + + A new page layout (thanks to Andre Carregal). + + Minimal documentation for module 're'. + +* Changes from version 0.4 to 0.5 + ------------------------------- + + Several optimizations. + + lpeg.P now accepts booleans. + + Some new examples. + + A proper license. + + Several small improvements. + +* Changes from version 0.3 to 0.4 + ------------------------------- + + Static check for loops in repetitions and grammars. + + Removed label option in captures. + + The implementation of captures uses less memory. + +* Changes from version 0.2 to 0.3 + ------------------------------- + + User-defined patterns in Lua. + + Several new captures. + +* Changes from version 0.1 to 0.2 + ------------------------------- + + Several small corrections. + + Handles embedded zeros like any other character. + + Capture "name" can be any Lua value. + + Unlimited number of captures. + + Match gets an optional initial position. + +(end of HISTORY) diff --git a/luaclib/src/lpeg/lpcap.c b/luaclib/src/lpeg/lpcap.c new file mode 100644 index 00000000..3202a1d7 --- /dev/null +++ b/luaclib/src/lpeg/lpcap.c @@ -0,0 +1,554 @@ +/* +** $Id: lpcap.c $ +** Copyright 2007, Lua.org & PUC-Rio (see 'lpeg.html' for license) +*/ + +#include + +#include "lpcap.h" +#include "lptypes.h" + + +#define captype(cap) ((cap)->kind) + +#define isclosecap(cap) (captype(cap) == Cclose) + +#define closeaddr(c) ((c)->s + (c)->siz - 1) + +#define isfullcap(cap) ((cap)->siz != 0) + +#define getfromktable(cs,v) lua_rawgeti((cs)->L, ktableidx((cs)->ptop), v) + +#define pushluaval(cs) getfromktable(cs, (cs)->cap->idx) + + + +/* +** Put at the cache for Lua values the value indexed by 'v' in ktable +** of the running pattern (if it is not there yet); returns its index. +*/ +static int updatecache (CapState *cs, int v) { + int idx = cs->ptop + 1; /* stack index of cache for Lua values */ + if (v != cs->valuecached) { /* not there? */ + getfromktable(cs, v); /* get value from 'ktable' */ + lua_replace(cs->L, idx); /* put it at reserved stack position */ + cs->valuecached = v; /* keep track of what is there */ + } + return idx; +} + + +static int pushcapture (CapState *cs); + + +/* +** Goes back in a list of captures looking for an open capture +** corresponding to a close +*/ +static Capture *findopen (Capture *cap) { + int n = 0; /* number of closes waiting an open */ + for (;;) { + cap--; + if (isclosecap(cap)) n++; /* one more open to skip */ + else if (!isfullcap(cap)) + if (n-- == 0) return cap; + } +} + + +/* +** Go to the next capture +*/ +static void nextcap (CapState *cs) { + Capture *cap = cs->cap; + if (!isfullcap(cap)) { /* not a single capture? */ + int n = 0; /* number of opens waiting a close */ + for (;;) { /* look for corresponding close */ + cap++; + if (isclosecap(cap)) { + if (n-- == 0) break; + } + else if (!isfullcap(cap)) n++; + } + } + cs->cap = cap + 1; /* + 1 to skip last close (or entire single capture) */ +} + + +/* +** Push on the Lua stack all values generated by nested captures inside +** the current capture. Returns number of values pushed. 'addextra' +** makes it push the entire match after all captured values. The +** entire match is pushed also if there are no other nested values, +** so the function never returns zero. +*/ +static int pushnestedvalues (CapState *cs, int addextra) { + Capture *co = cs->cap; + if (isfullcap(cs->cap++)) { /* no nested captures? */ + lua_pushlstring(cs->L, co->s, co->siz - 1); /* push whole match */ + return 1; /* that is it */ + } + else { + int n = 0; + while (!isclosecap(cs->cap)) /* repeat for all nested patterns */ + n += pushcapture(cs); + if (addextra || n == 0) { /* need extra? */ + lua_pushlstring(cs->L, co->s, cs->cap->s - co->s); /* push whole match */ + n++; + } + cs->cap++; /* skip close entry */ + return n; + } +} + + +/* +** Push only the first value generated by nested captures +*/ +static void pushonenestedvalue (CapState *cs) { + int n = pushnestedvalues(cs, 0); + if (n > 1) + lua_pop(cs->L, n - 1); /* pop extra values */ +} + + +/* +** Try to find a named group capture with the name given at the top of +** the stack; goes backward from 'cap'. +*/ +static Capture *findback (CapState *cs, Capture *cap) { + lua_State *L = cs->L; + while (cap-- > cs->ocap) { /* repeat until end of list */ + if (isclosecap(cap)) + cap = findopen(cap); /* skip nested captures */ + else if (!isfullcap(cap)) + continue; /* opening an enclosing capture: skip and get previous */ + if (captype(cap) == Cgroup) { + getfromktable(cs, cap->idx); /* get group name */ + if (lp_equal(L, -2, -1)) { /* right group? */ + lua_pop(L, 2); /* remove reference name and group name */ + return cap; + } + else lua_pop(L, 1); /* remove group name */ + } + } + luaL_error(L, "back reference '%s' not found", lua_tostring(L, -1)); + return NULL; /* to avoid warnings */ +} + + +/* +** Back-reference capture. Return number of values pushed. +*/ +static int backrefcap (CapState *cs) { + int n; + Capture *curr = cs->cap; + pushluaval(cs); /* reference name */ + cs->cap = findback(cs, curr); /* find corresponding group */ + n = pushnestedvalues(cs, 0); /* push group's values */ + cs->cap = curr + 1; + return n; +} + + +/* +** Table capture: creates a new table and populates it with nested +** captures. +*/ +static int tablecap (CapState *cs) { + lua_State *L = cs->L; + int n = 0; + lua_newtable(L); + if (isfullcap(cs->cap++)) + return 1; /* table is empty */ + while (!isclosecap(cs->cap)) { + if (captype(cs->cap) == Cgroup && cs->cap->idx != 0) { /* named group? */ + pushluaval(cs); /* push group name */ + pushonenestedvalue(cs); + lua_settable(L, -3); + } + else { /* not a named group */ + int i; + int k = pushcapture(cs); + for (i = k; i > 0; i--) /* store all values into table */ + lua_rawseti(L, -(i + 1), n + i); + n += k; + } + } + cs->cap++; /* skip close entry */ + return 1; /* number of values pushed (only the table) */ +} + + +/* +** Table-query capture +*/ +static int querycap (CapState *cs) { + int idx = cs->cap->idx; + pushonenestedvalue(cs); /* get nested capture */ + lua_gettable(cs->L, updatecache(cs, idx)); /* query cap. value at table */ + if (!lua_isnil(cs->L, -1)) + return 1; + else { /* no value */ + lua_pop(cs->L, 1); /* remove nil */ + return 0; + } +} + + +/* +** Fold capture +*/ +static int foldcap (CapState *cs) { + int n; + lua_State *L = cs->L; + int idx = cs->cap->idx; + if (isfullcap(cs->cap++) || /* no nested captures? */ + isclosecap(cs->cap) || /* no nested captures (large subject)? */ + (n = pushcapture(cs)) == 0) /* nested captures with no values? */ + return luaL_error(L, "no initial value for fold capture"); + if (n > 1) + lua_pop(L, n - 1); /* leave only one result for accumulator */ + while (!isclosecap(cs->cap)) { + lua_pushvalue(L, updatecache(cs, idx)); /* get folding function */ + lua_insert(L, -2); /* put it before accumulator */ + n = pushcapture(cs); /* get next capture's values */ + lua_call(L, n + 1, 1); /* call folding function */ + } + cs->cap++; /* skip close entry */ + return 1; /* only accumulator left on the stack */ +} + + +/* +** Function capture +*/ +static int functioncap (CapState *cs) { + int n; + int top = lua_gettop(cs->L); + pushluaval(cs); /* push function */ + n = pushnestedvalues(cs, 0); /* push nested captures */ + lua_call(cs->L, n, LUA_MULTRET); /* call function */ + return lua_gettop(cs->L) - top; /* return function's results */ +} + + +/* +** Select capture +*/ +static int numcap (CapState *cs) { + int idx = cs->cap->idx; /* value to select */ + if (idx == 0) { /* no values? */ + nextcap(cs); /* skip entire capture */ + return 0; /* no value produced */ + } + else { + int n = pushnestedvalues(cs, 0); + if (n < idx) /* invalid index? */ + return luaL_error(cs->L, "no capture '%d'", idx); + else { + lua_pushvalue(cs->L, -(n - idx + 1)); /* get selected capture */ + lua_replace(cs->L, -(n + 1)); /* put it in place of 1st capture */ + lua_pop(cs->L, n - 1); /* remove other captures */ + return 1; + } + } +} + + +/* +** Return the stack index of the first runtime capture in the given +** list of captures (or zero if no runtime captures) +*/ +int finddyncap (Capture *cap, Capture *last) { + for (; cap < last; cap++) { + if (cap->kind == Cruntime) + return cap->idx; /* stack position of first capture */ + } + return 0; /* no dynamic captures in this segment */ +} + + +/* +** Calls a runtime capture. Returns number of captures "removed" by the +** call, that is, those inside the group capture. Captures to be added +** are on the Lua stack. +*/ +int runtimecap (CapState *cs, Capture *close, const char *s, int *rem) { + int n, id; + lua_State *L = cs->L; + int otop = lua_gettop(L); + Capture *open = findopen(close); /* get open group capture */ + assert(captype(open) == Cgroup); + id = finddyncap(open, close); /* get first dynamic capture argument */ + close->kind = Cclose; /* closes the group */ + close->s = s; + cs->cap = open; cs->valuecached = 0; /* prepare capture state */ + luaL_checkstack(L, 4, "too many runtime captures"); + pushluaval(cs); /* push function to be called */ + lua_pushvalue(L, SUBJIDX); /* push original subject */ + lua_pushinteger(L, s - cs->s + 1); /* push current position */ + n = pushnestedvalues(cs, 0); /* push nested captures */ + lua_call(L, n + 2, LUA_MULTRET); /* call dynamic function */ + if (id > 0) { /* are there old dynamic captures to be removed? */ + int i; + for (i = id; i <= otop; i++) + lua_remove(L, id); /* remove old dynamic captures */ + *rem = otop - id + 1; /* total number of dynamic captures removed */ + } + else + *rem = 0; /* no dynamic captures removed */ + return close - open - 1; /* number of captures to be removed */ +} + + +/* +** Auxiliary structure for substitution and string captures: keep +** information about nested captures for future use, avoiding to push +** string results into Lua +*/ +typedef struct StrAux { + int isstring; /* whether capture is a string */ + union { + Capture *cp; /* if not a string, respective capture */ + struct { /* if it is a string... */ + const char *s; /* ... starts here */ + const char *e; /* ... ends here */ + } s; + } u; +} StrAux; + +#define MAXSTRCAPS 10 + +/* +** Collect values from current capture into array 'cps'. Current +** capture must be Cstring (first call) or Csimple (recursive calls). +** (In first call, fills %0 with whole match for Cstring.) +** Returns number of elements in the array that were filled. +*/ +static int getstrcaps (CapState *cs, StrAux *cps, int n) { + int k = n++; + cps[k].isstring = 1; /* get string value */ + cps[k].u.s.s = cs->cap->s; /* starts here */ + if (!isfullcap(cs->cap++)) { /* nested captures? */ + while (!isclosecap(cs->cap)) { /* traverse them */ + if (n >= MAXSTRCAPS) /* too many captures? */ + nextcap(cs); /* skip extra captures (will not need them) */ + else if (captype(cs->cap) == Csimple) /* string? */ + n = getstrcaps(cs, cps, n); /* put info. into array */ + else { + cps[n].isstring = 0; /* not a string */ + cps[n].u.cp = cs->cap; /* keep original capture */ + nextcap(cs); + n++; + } + } + cs->cap++; /* skip close */ + } + cps[k].u.s.e = closeaddr(cs->cap - 1); /* ends here */ + return n; +} + + +/* +** add next capture value (which should be a string) to buffer 'b' +*/ +static int addonestring (luaL_Buffer *b, CapState *cs, const char *what); + + +/* +** String capture: add result to buffer 'b' (instead of pushing +** it into the stack) +*/ +static void stringcap (luaL_Buffer *b, CapState *cs) { + StrAux cps[MAXSTRCAPS]; + int n; + size_t len, i; + const char *fmt; /* format string */ + fmt = lua_tolstring(cs->L, updatecache(cs, cs->cap->idx), &len); + n = getstrcaps(cs, cps, 0) - 1; /* collect nested captures */ + for (i = 0; i < len; i++) { /* traverse them */ + if (fmt[i] != '%') /* not an escape? */ + luaL_addchar(b, fmt[i]); /* add it to buffer */ + else if (fmt[++i] < '0' || fmt[i] > '9') /* not followed by a digit? */ + luaL_addchar(b, fmt[i]); /* add to buffer */ + else { + int l = fmt[i] - '0'; /* capture index */ + if (l > n) + luaL_error(cs->L, "invalid capture index (%d)", l); + else if (cps[l].isstring) + luaL_addlstring(b, cps[l].u.s.s, cps[l].u.s.e - cps[l].u.s.s); + else { + Capture *curr = cs->cap; + cs->cap = cps[l].u.cp; /* go back to evaluate that nested capture */ + if (!addonestring(b, cs, "capture")) + luaL_error(cs->L, "no values in capture index %d", l); + cs->cap = curr; /* continue from where it stopped */ + } + } + } +} + + +/* +** Substitution capture: add result to buffer 'b' +*/ +static void substcap (luaL_Buffer *b, CapState *cs) { + const char *curr = cs->cap->s; + if (isfullcap(cs->cap)) /* no nested captures? */ + luaL_addlstring(b, curr, cs->cap->siz - 1); /* keep original text */ + else { + cs->cap++; /* skip open entry */ + while (!isclosecap(cs->cap)) { /* traverse nested captures */ + const char *next = cs->cap->s; + luaL_addlstring(b, curr, next - curr); /* add text up to capture */ + if (addonestring(b, cs, "replacement")) + curr = closeaddr(cs->cap - 1); /* continue after match */ + else /* no capture value */ + curr = next; /* keep original text in final result */ + } + luaL_addlstring(b, curr, cs->cap->s - curr); /* add last piece of text */ + } + cs->cap++; /* go to next capture */ +} + + +/* +** Evaluates a capture and adds its first value to buffer 'b'; returns +** whether there was a value +*/ +static int addonestring (luaL_Buffer *b, CapState *cs, const char *what) { + switch (captype(cs->cap)) { + case Cstring: + stringcap(b, cs); /* add capture directly to buffer */ + return 1; + case Csubst: + substcap(b, cs); /* add capture directly to buffer */ + return 1; + default: { + lua_State *L = cs->L; + int n = pushcapture(cs); + if (n > 0) { + if (n > 1) lua_pop(L, n - 1); /* only one result */ + if (!lua_isstring(L, -1)) + luaL_error(L, "invalid %s value (a %s)", what, luaL_typename(L, -1)); + luaL_addvalue(b); + } + return n; + } + } +} + + +#if !defined(MAXRECLEVEL) +#define MAXRECLEVEL 200 +#endif + + +/* +** Push all values of the current capture into the stack; returns +** number of values pushed +*/ +static int pushcapture (CapState *cs) { + lua_State *L = cs->L; + int res; + luaL_checkstack(L, 4, "too many captures"); + if (cs->reclevel++ > MAXRECLEVEL) + return luaL_error(L, "subcapture nesting too deep"); + switch (captype(cs->cap)) { + case Cposition: { + lua_pushinteger(L, cs->cap->s - cs->s + 1); + cs->cap++; + res = 1; + break; + } + case Cconst: { + pushluaval(cs); + cs->cap++; + res = 1; + break; + } + case Carg: { + int arg = (cs->cap++)->idx; + if (arg + FIXEDARGS > cs->ptop) + return luaL_error(L, "reference to absent extra argument #%d", arg); + lua_pushvalue(L, arg + FIXEDARGS); + res = 1; + break; + } + case Csimple: { + int k = pushnestedvalues(cs, 1); + lua_insert(L, -k); /* make whole match be first result */ + res = k; + break; + } + case Cruntime: { + lua_pushvalue(L, (cs->cap++)->idx); /* value is in the stack */ + res = 1; + break; + } + case Cstring: { + luaL_Buffer b; + luaL_buffinit(L, &b); + stringcap(&b, cs); + luaL_pushresult(&b); + res = 1; + break; + } + case Csubst: { + luaL_Buffer b; + luaL_buffinit(L, &b); + substcap(&b, cs); + luaL_pushresult(&b); + res = 1; + break; + } + case Cgroup: { + if (cs->cap->idx == 0) /* anonymous group? */ + res = pushnestedvalues(cs, 0); /* add all nested values */ + else { /* named group: add no values */ + nextcap(cs); /* skip capture */ + res = 0; + } + break; + } + case Cbackref: res = backrefcap(cs); break; + case Ctable: res = tablecap(cs); break; + case Cfunction: res = functioncap(cs); break; + case Cnum: res = numcap(cs); break; + case Cquery: res = querycap(cs); break; + case Cfold: res = foldcap(cs); break; + default: assert(0); res = 0; + } + cs->reclevel--; + return res; +} + + +/* +** Prepare a CapState structure and traverse the entire list of +** captures in the stack pushing its results. 's' is the subject +** string, 'r' is the final position of the match, and 'ptop' +** the index in the stack where some useful values were pushed. +** Returns the number of results pushed. (If the list produces no +** results, push the final position of the match.) +*/ +int getcaptures (lua_State *L, const char *s, const char *r, int ptop) { + Capture *capture = (Capture *)lua_touserdata(L, caplistidx(ptop)); + int n = 0; + if (!isclosecap(capture)) { /* is there any capture? */ + CapState cs; + cs.ocap = cs.cap = capture; cs.L = L; cs.reclevel = 0; + cs.s = s; cs.valuecached = 0; cs.ptop = ptop; + do { /* collect their values */ + n += pushcapture(&cs); + } while (!isclosecap(cs.cap)); + } + if (n == 0) { /* no capture values? */ + lua_pushinteger(L, r - s + 1); /* return only end position */ + n = 1; + } + return n; +} + + diff --git a/luaclib/src/lpeg/lpcap.h b/luaclib/src/lpeg/lpcap.h new file mode 100644 index 00000000..dc10d696 --- /dev/null +++ b/luaclib/src/lpeg/lpcap.h @@ -0,0 +1,57 @@ +/* +** $Id: lpcap.h $ +*/ + +#if !defined(lpcap_h) +#define lpcap_h + + +#include "lptypes.h" + + +/* kinds of captures */ +typedef enum CapKind { + Cclose, /* not used in trees */ + Cposition, + Cconst, /* ktable[key] is Lua constant */ + Cbackref, /* ktable[key] is "name" of group to get capture */ + Carg, /* 'key' is arg's number */ + Csimple, /* next node is pattern */ + Ctable, /* next node is pattern */ + Cfunction, /* ktable[key] is function; next node is pattern */ + Cquery, /* ktable[key] is table; next node is pattern */ + Cstring, /* ktable[key] is string; next node is pattern */ + Cnum, /* numbered capture; 'key' is number of value to return */ + Csubst, /* substitution capture; next node is pattern */ + Cfold, /* ktable[key] is function; next node is pattern */ + Cruntime, /* not used in trees (is uses another type for tree) */ + Cgroup /* ktable[key] is group's "name" */ +} CapKind; + + +typedef struct Capture { + const char *s; /* subject position */ + unsigned short idx; /* extra info (group name, arg index, etc.) */ + byte kind; /* kind of capture */ + byte siz; /* size of full capture + 1 (0 = not a full capture) */ +} Capture; + + +typedef struct CapState { + Capture *cap; /* current capture */ + Capture *ocap; /* (original) capture list */ + lua_State *L; + int ptop; /* index of last argument to 'match' */ + const char *s; /* original string */ + int valuecached; /* value stored in cache slot */ + int reclevel; /* recursion level */ +} CapState; + + +int runtimecap (CapState *cs, Capture *close, const char *s, int *rem); +int getcaptures (lua_State *L, const char *s, const char *r, int ptop); +int finddyncap (Capture *cap, Capture *last); + +#endif + + diff --git a/luaclib/src/lpeg/lpcode.c b/luaclib/src/lpeg/lpcode.c new file mode 100644 index 00000000..29125b8f --- /dev/null +++ b/luaclib/src/lpeg/lpcode.c @@ -0,0 +1,1010 @@ +/* +** $Id: lpcode.c $ +** Copyright 2007, Lua.org & PUC-Rio (see 'lpeg.html' for license) +*/ + +#include + +#include "lptypes.h" +#include "lpcode.h" + + +/* signals a "no-instruction */ +#define NOINST -1 + + + +static const Charset fullset_ = + {{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; + +static const Charset *fullset = &fullset_; + +/* +** {====================================================== +** Analysis and some optimizations +** ======================================================= +*/ + +/* +** Check whether a charset is empty (returns IFail), singleton (IChar), +** full (IAny), or none of those (ISet). When singleton, '*c' returns +** which character it is. (When generic set, the set was the input, +** so there is no need to return it.) +*/ +static Opcode charsettype (const byte *cs, int *c) { + int count = 0; /* number of characters in the set */ + int i; + int candidate = -1; /* candidate position for the singleton char */ + for (i = 0; i < CHARSETSIZE; i++) { /* for each byte */ + int b = cs[i]; + if (b == 0) { /* is byte empty? */ + if (count > 1) /* was set neither empty nor singleton? */ + return ISet; /* neither full nor empty nor singleton */ + /* else set is still empty or singleton */ + } + else if (b == 0xFF) { /* is byte full? */ + if (count < (i * BITSPERCHAR)) /* was set not full? */ + return ISet; /* neither full nor empty nor singleton */ + else count += BITSPERCHAR; /* set is still full */ + } + else if ((b & (b - 1)) == 0) { /* has byte only one bit? */ + if (count > 0) /* was set not empty? */ + return ISet; /* neither full nor empty nor singleton */ + else { /* set has only one char till now; track it */ + count++; + candidate = i; + } + } + else return ISet; /* byte is neither empty, full, nor singleton */ + } + switch (count) { + case 0: return IFail; /* empty set */ + case 1: { /* singleton; find character bit inside byte */ + int b = cs[candidate]; + *c = candidate * BITSPERCHAR; + if ((b & 0xF0) != 0) { *c += 4; b >>= 4; } + if ((b & 0x0C) != 0) { *c += 2; b >>= 2; } + if ((b & 0x02) != 0) { *c += 1; } + return IChar; + } + default: { + assert(count == CHARSETSIZE * BITSPERCHAR); /* full set */ + return IAny; + } + } +} + + +/* +** A few basic operations on Charsets +*/ +static void cs_complement (Charset *cs) { + loopset(i, cs->cs[i] = ~cs->cs[i]); +} + +static int cs_equal (const byte *cs1, const byte *cs2) { + loopset(i, if (cs1[i] != cs2[i]) return 0); + return 1; +} + +static int cs_disjoint (const Charset *cs1, const Charset *cs2) { + loopset(i, if ((cs1->cs[i] & cs2->cs[i]) != 0) return 0;) + return 1; +} + + +/* +** If 'tree' is a 'char' pattern (TSet, TChar, TAny), convert it into a +** charset and return 1; else return 0. +*/ +int tocharset (TTree *tree, Charset *cs) { + switch (tree->tag) { + case TSet: { /* copy set */ + loopset(i, cs->cs[i] = treebuffer(tree)[i]); + return 1; + } + case TChar: { /* only one char */ + assert(0 <= tree->u.n && tree->u.n <= UCHAR_MAX); + loopset(i, cs->cs[i] = 0); /* erase all chars */ + setchar(cs->cs, tree->u.n); /* add that one */ + return 1; + } + case TAny: { + loopset(i, cs->cs[i] = 0xFF); /* add all characters to the set */ + return 1; + } + default: return 0; + } +} + + +/* +** Visit a TCall node taking care to stop recursion. If node not yet +** visited, return 'f(sib2(tree))', otherwise return 'def' (default +** value) +*/ +static int callrecursive (TTree *tree, int f (TTree *t), int def) { + int key = tree->key; + assert(tree->tag == TCall); + assert(sib2(tree)->tag == TRule); + if (key == 0) /* node already visited? */ + return def; /* return default value */ + else { /* first visit */ + int result; + tree->key = 0; /* mark call as already visited */ + result = f(sib2(tree)); /* go to called rule */ + tree->key = key; /* restore tree */ + return result; + } +} + + +/* +** Check whether a pattern tree has captures +*/ +int hascaptures (TTree *tree) { + tailcall: + switch (tree->tag) { + case TCapture: case TRunTime: + return 1; + case TCall: + return callrecursive(tree, hascaptures, 0); + case TRule: /* do not follow siblings */ + tree = sib1(tree); goto tailcall; + case TOpenCall: assert(0); + default: { + switch (numsiblings[tree->tag]) { + case 1: /* return hascaptures(sib1(tree)); */ + tree = sib1(tree); goto tailcall; + case 2: + if (hascaptures(sib1(tree))) + return 1; + /* else return hascaptures(sib2(tree)); */ + tree = sib2(tree); goto tailcall; + default: assert(numsiblings[tree->tag] == 0); return 0; + } + } + } +} + + +/* +** Checks how a pattern behaves regarding the empty string, +** in one of two different ways: +** A pattern is *nullable* if it can match without consuming any character; +** A pattern is *nofail* if it never fails for any string +** (including the empty string). +** The difference is only for predicates and run-time captures; +** for other patterns, the two properties are equivalent. +** (With predicates, &'a' is nullable but not nofail. Of course, +** nofail => nullable.) +** These functions are all convervative in the following way: +** p is nullable => nullable(p) +** nofail(p) => p cannot fail +** The function assumes that TOpenCall is not nullable; +** this will be checked again when the grammar is fixed. +** Run-time captures can do whatever they want, so the result +** is conservative. +*/ +int checkaux (TTree *tree, int pred) { + tailcall: + switch (tree->tag) { + case TChar: case TSet: case TAny: + case TFalse: case TOpenCall: + return 0; /* not nullable */ + case TRep: case TTrue: + return 1; /* no fail */ + case TNot: case TBehind: /* can match empty, but can fail */ + if (pred == PEnofail) return 0; + else return 1; /* PEnullable */ + case TAnd: /* can match empty; fail iff body does */ + if (pred == PEnullable) return 1; + /* else return checkaux(sib1(tree), pred); */ + tree = sib1(tree); goto tailcall; + case TRunTime: /* can fail; match empty iff body does */ + if (pred == PEnofail) return 0; + /* else return checkaux(sib1(tree), pred); */ + tree = sib1(tree); goto tailcall; + case TSeq: + if (!checkaux(sib1(tree), pred)) return 0; + /* else return checkaux(sib2(tree), pred); */ + tree = sib2(tree); goto tailcall; + case TChoice: + if (checkaux(sib2(tree), pred)) return 1; + /* else return checkaux(sib1(tree), pred); */ + tree = sib1(tree); goto tailcall; + case TCapture: case TGrammar: case TRule: + /* return checkaux(sib1(tree), pred); */ + tree = sib1(tree); goto tailcall; + case TCall: /* return checkaux(sib2(tree), pred); */ + tree = sib2(tree); goto tailcall; + default: assert(0); return 0; + } +} + + +/* +** number of characters to match a pattern (or -1 if variable) +*/ +int fixedlen (TTree *tree) { + int len = 0; /* to accumulate in tail calls */ + tailcall: + switch (tree->tag) { + case TChar: case TSet: case TAny: + return len + 1; + case TFalse: case TTrue: case TNot: case TAnd: case TBehind: + return len; + case TRep: case TRunTime: case TOpenCall: + return -1; + case TCapture: case TRule: case TGrammar: + /* return fixedlen(sib1(tree)); */ + tree = sib1(tree); goto tailcall; + case TCall: { + int n1 = callrecursive(tree, fixedlen, -1); + if (n1 < 0) + return -1; + else + return len + n1; + } + case TSeq: { + int n1 = fixedlen(sib1(tree)); + if (n1 < 0) + return -1; + /* else return fixedlen(sib2(tree)) + len; */ + len += n1; tree = sib2(tree); goto tailcall; + } + case TChoice: { + int n1 = fixedlen(sib1(tree)); + int n2 = fixedlen(sib2(tree)); + if (n1 != n2 || n1 < 0) + return -1; + else + return len + n1; + } + default: assert(0); return 0; + }; +} + + +/* +** Computes the 'first set' of a pattern. +** The result is a conservative aproximation: +** match p ax -> x (for some x) ==> a belongs to first(p) +** or +** a not in first(p) ==> match p ax -> fail (for all x) +** +** The set 'follow' is the first set of what follows the +** pattern (full set if nothing follows it). +** +** The function returns 0 when this resulting set can be used for +** test instructions that avoid the pattern altogether. +** A non-zero return can happen for two reasons: +** 1) match p '' -> '' ==> return has bit 1 set +** (tests cannot be used because they would always fail for an empty input); +** 2) there is a match-time capture ==> return has bit 2 set +** (optimizations should not bypass match-time captures). +*/ +static int getfirst (TTree *tree, const Charset *follow, Charset *firstset) { + tailcall: + switch (tree->tag) { + case TChar: case TSet: case TAny: { + tocharset(tree, firstset); + return 0; + } + case TTrue: { + loopset(i, firstset->cs[i] = follow->cs[i]); + return 1; /* accepts the empty string */ + } + case TFalse: { + loopset(i, firstset->cs[i] = 0); + return 0; + } + case TChoice: { + Charset csaux; + int e1 = getfirst(sib1(tree), follow, firstset); + int e2 = getfirst(sib2(tree), follow, &csaux); + loopset(i, firstset->cs[i] |= csaux.cs[i]); + return e1 | e2; + } + case TSeq: { + if (!nullable(sib1(tree))) { + /* when p1 is not nullable, p2 has nothing to contribute; + return getfirst(sib1(tree), fullset, firstset); */ + tree = sib1(tree); follow = fullset; goto tailcall; + } + else { /* FIRST(p1 p2, fl) = FIRST(p1, FIRST(p2, fl)) */ + Charset csaux; + int e2 = getfirst(sib2(tree), follow, &csaux); + int e1 = getfirst(sib1(tree), &csaux, firstset); + if (e1 == 0) return 0; /* 'e1' ensures that first can be used */ + else if ((e1 | e2) & 2) /* one of the children has a matchtime? */ + return 2; /* pattern has a matchtime capture */ + else return e2; /* else depends on 'e2' */ + } + } + case TRep: { + getfirst(sib1(tree), follow, firstset); + loopset(i, firstset->cs[i] |= follow->cs[i]); + return 1; /* accept the empty string */ + } + case TCapture: case TGrammar: case TRule: { + /* return getfirst(sib1(tree), follow, firstset); */ + tree = sib1(tree); goto tailcall; + } + case TRunTime: { /* function invalidates any follow info. */ + int e = getfirst(sib1(tree), fullset, firstset); + if (e) return 2; /* function is not "protected"? */ + else return 0; /* pattern inside capture ensures first can be used */ + } + case TCall: { + /* return getfirst(sib2(tree), follow, firstset); */ + tree = sib2(tree); goto tailcall; + } + case TAnd: { + int e = getfirst(sib1(tree), follow, firstset); + loopset(i, firstset->cs[i] &= follow->cs[i]); + return e; + } + case TNot: { + if (tocharset(sib1(tree), firstset)) { + cs_complement(firstset); + return 1; + } + /* else go through */ + } + case TBehind: { /* instruction gives no new information */ + /* call 'getfirst' only to check for math-time captures */ + int e = getfirst(sib1(tree), follow, firstset); + loopset(i, firstset->cs[i] = follow->cs[i]); /* uses follow */ + return e | 1; /* always can accept the empty string */ + } + default: assert(0); return 0; + } +} + + +/* +** If 'headfail(tree)' true, then 'tree' can fail only depending on the +** next character of the subject. +*/ +static int headfail (TTree *tree) { + tailcall: + switch (tree->tag) { + case TChar: case TSet: case TAny: case TFalse: + return 1; + case TTrue: case TRep: case TRunTime: case TNot: + case TBehind: + return 0; + case TCapture: case TGrammar: case TRule: case TAnd: + tree = sib1(tree); goto tailcall; /* return headfail(sib1(tree)); */ + case TCall: + tree = sib2(tree); goto tailcall; /* return headfail(sib2(tree)); */ + case TSeq: + if (!nofail(sib2(tree))) return 0; + /* else return headfail(sib1(tree)); */ + tree = sib1(tree); goto tailcall; + case TChoice: + if (!headfail(sib1(tree))) return 0; + /* else return headfail(sib2(tree)); */ + tree = sib2(tree); goto tailcall; + default: assert(0); return 0; + } +} + + +/* +** Check whether the code generation for the given tree can benefit +** from a follow set (to avoid computing the follow set when it is +** not needed) +*/ +static int needfollow (TTree *tree) { + tailcall: + switch (tree->tag) { + case TChar: case TSet: case TAny: + case TFalse: case TTrue: case TAnd: case TNot: + case TRunTime: case TGrammar: case TCall: case TBehind: + return 0; + case TChoice: case TRep: + return 1; + case TCapture: + tree = sib1(tree); goto tailcall; + case TSeq: + tree = sib2(tree); goto tailcall; + default: assert(0); return 0; + } +} + +/* }====================================================== */ + + + +/* +** {====================================================== +** Code generation +** ======================================================= +*/ + + +/* +** size of an instruction +*/ +int sizei (const Instruction *i) { + switch((Opcode)i->i.code) { + case ISet: case ISpan: return CHARSETINSTSIZE; + case ITestSet: return CHARSETINSTSIZE + 1; + case ITestChar: case ITestAny: case IChoice: case IJmp: case ICall: + case IOpenCall: case ICommit: case IPartialCommit: case IBackCommit: + return 2; + default: return 1; + } +} + + +/* +** state for the compiler +*/ +typedef struct CompileState { + Pattern *p; /* pattern being compiled */ + int ncode; /* next position in p->code to be filled */ + lua_State *L; +} CompileState; + + +/* +** code generation is recursive; 'opt' indicates that the code is being +** generated as the last thing inside an optional pattern (so, if that +** code is optional too, it can reuse the 'IChoice' already in place for +** the outer pattern). 'tt' points to a previous test protecting this +** code (or NOINST). 'fl' is the follow set of the pattern. +*/ +static void codegen (CompileState *compst, TTree *tree, int opt, int tt, + const Charset *fl); + + +void realloccode (lua_State *L, Pattern *p, int nsize) { + void *ud; + lua_Alloc f = lua_getallocf(L, &ud); + void *newblock = f(ud, p->code, p->codesize * sizeof(Instruction), + nsize * sizeof(Instruction)); + if (newblock == NULL && nsize > 0) + luaL_error(L, "not enough memory"); + p->code = (Instruction *)newblock; + p->codesize = nsize; +} + + +static int nextinstruction (CompileState *compst) { + int size = compst->p->codesize; + if (compst->ncode >= size) + realloccode(compst->L, compst->p, size * 2); + return compst->ncode++; +} + + +#define getinstr(cs,i) ((cs)->p->code[i]) + + +static int addinstruction (CompileState *compst, Opcode op, int aux) { + int i = nextinstruction(compst); + getinstr(compst, i).i.code = op; + getinstr(compst, i).i.aux = aux; + return i; +} + + +/* +** Add an instruction followed by space for an offset (to be set later) +*/ +static int addoffsetinst (CompileState *compst, Opcode op) { + int i = addinstruction(compst, op, 0); /* instruction */ + addinstruction(compst, (Opcode)0, 0); /* open space for offset */ + assert(op == ITestSet || sizei(&getinstr(compst, i)) == 2); + return i; +} + + +/* +** Set the offset of an instruction +*/ +static void setoffset (CompileState *compst, int instruction, int offset) { + getinstr(compst, instruction + 1).offset = offset; +} + + +/* +** Add a capture instruction: +** 'op' is the capture instruction; 'cap' the capture kind; +** 'key' the key into ktable; 'aux' is the optional capture offset +** +*/ +static int addinstcap (CompileState *compst, Opcode op, int cap, int key, + int aux) { + int i = addinstruction(compst, op, joinkindoff(cap, aux)); + getinstr(compst, i).i.key = key; + return i; +} + + +#define gethere(compst) ((compst)->ncode) + +#define target(code,i) ((i) + code[i + 1].offset) + + +/* +** Patch 'instruction' to jump to 'target' +*/ +static void jumptothere (CompileState *compst, int instruction, int target) { + if (instruction >= 0) + setoffset(compst, instruction, target - instruction); +} + + +/* +** Patch 'instruction' to jump to current position +*/ +static void jumptohere (CompileState *compst, int instruction) { + jumptothere(compst, instruction, gethere(compst)); +} + + +/* +** Code an IChar instruction, or IAny if there is an equivalent +** test dominating it +*/ +static void codechar (CompileState *compst, int c, int tt) { + if (tt >= 0 && getinstr(compst, tt).i.code == ITestChar && + getinstr(compst, tt).i.aux == c) + addinstruction(compst, IAny, 0); + else + addinstruction(compst, IChar, c); +} + + +/* +** Add a charset posfix to an instruction +*/ +static void addcharset (CompileState *compst, const byte *cs) { + int p = gethere(compst); + int i; + for (i = 0; i < (int)CHARSETINSTSIZE - 1; i++) + nextinstruction(compst); /* space for buffer */ + /* fill buffer with charset */ + loopset(j, getinstr(compst, p).buff[j] = cs[j]); +} + + +/* +** code a char set, optimizing unit sets for IChar, "complete" +** sets for IAny, and empty sets for IFail; also use an IAny +** when instruction is dominated by an equivalent test. +*/ +static void codecharset (CompileState *compst, const byte *cs, int tt) { + int c = 0; /* (=) to avoid warnings */ + Opcode op = charsettype(cs, &c); + switch (op) { + case IChar: codechar(compst, c, tt); break; + case ISet: { /* non-trivial set? */ + if (tt >= 0 && getinstr(compst, tt).i.code == ITestSet && + cs_equal(cs, getinstr(compst, tt + 2).buff)) + addinstruction(compst, IAny, 0); + else { + addinstruction(compst, ISet, 0); + addcharset(compst, cs); + } + break; + } + default: addinstruction(compst, op, c); break; + } +} + + +/* +** code a test set, optimizing unit sets for ITestChar, "complete" +** sets for ITestAny, and empty sets for IJmp (always fails). +** 'e' is true iff test should accept the empty string. (Test +** instructions in the current VM never accept the empty string.) +*/ +static int codetestset (CompileState *compst, Charset *cs, int e) { + if (e) return NOINST; /* no test */ + else { + int c = 0; + Opcode op = charsettype(cs->cs, &c); + switch (op) { + case IFail: return addoffsetinst(compst, IJmp); /* always jump */ + case IAny: return addoffsetinst(compst, ITestAny); + case IChar: { + int i = addoffsetinst(compst, ITestChar); + getinstr(compst, i).i.aux = c; + return i; + } + case ISet: { + int i = addoffsetinst(compst, ITestSet); + addcharset(compst, cs->cs); + return i; + } + default: assert(0); return 0; + } + } +} + + +/* +** Find the final destination of a sequence of jumps +*/ +static int finaltarget (Instruction *code, int i) { + while (code[i].i.code == IJmp) + i = target(code, i); + return i; +} + + +/* +** final label (after traversing any jumps) +*/ +static int finallabel (Instruction *code, int i) { + return finaltarget(code, target(code, i)); +} + + +/* +** == behind n;

(where n = fixedlen(p)) +*/ +static void codebehind (CompileState *compst, TTree *tree) { + if (tree->u.n > 0) + addinstruction(compst, IBehind, tree->u.n); + codegen(compst, sib1(tree), 0, NOINST, fullset); +} + + +/* +** Choice; optimizations: +** - when p1 is headfail or +** when first(p1) and first(p2) are disjoint, than +** a character not in first(p1) cannot go to p1, and a character +** in first(p1) cannot go to p2 (at it is not in first(p2)). +** (The optimization is not valid if p1 accepts the empty string, +** as then there is no character at all...) +** - when p2 is empty and opt is true; a IPartialCommit can reuse +** the Choice already active in the stack. +*/ +static void codechoice (CompileState *compst, TTree *p1, TTree *p2, int opt, + const Charset *fl) { + int emptyp2 = (p2->tag == TTrue); + Charset cs1, cs2; + int e1 = getfirst(p1, fullset, &cs1); + if (headfail(p1) || + (!e1 && (getfirst(p2, fl, &cs2), cs_disjoint(&cs1, &cs2)))) { + /* == test (fail(p1)) -> L1 ; p1 ; jmp L2; L1: p2; L2: */ + int test = codetestset(compst, &cs1, 0); + int jmp = NOINST; + codegen(compst, p1, 0, test, fl); + if (!emptyp2) + jmp = addoffsetinst(compst, IJmp); + jumptohere(compst, test); + codegen(compst, p2, opt, NOINST, fl); + jumptohere(compst, jmp); + } + else if (opt && emptyp2) { + /* p1? == IPartialCommit; p1 */ + jumptohere(compst, addoffsetinst(compst, IPartialCommit)); + codegen(compst, p1, 1, NOINST, fullset); + } + else { + /* == + test(first(p1)) -> L1; choice L1; ; commit L2; L1: ; L2: */ + int pcommit; + int test = codetestset(compst, &cs1, e1); + int pchoice = addoffsetinst(compst, IChoice); + codegen(compst, p1, emptyp2, test, fullset); + pcommit = addoffsetinst(compst, ICommit); + jumptohere(compst, pchoice); + jumptohere(compst, test); + codegen(compst, p2, opt, NOINST, fl); + jumptohere(compst, pcommit); + } +} + + +/* +** And predicate +** optimization: fixedlen(p) = n ==> <&p> ==

; behind n +** (valid only when 'p' has no captures) +*/ +static void codeand (CompileState *compst, TTree *tree, int tt) { + int n = fixedlen(tree); + if (n >= 0 && n <= MAXBEHIND && !hascaptures(tree)) { + codegen(compst, tree, 0, tt, fullset); + if (n > 0) + addinstruction(compst, IBehind, n); + } + else { /* default: Choice L1; p1; BackCommit L2; L1: Fail; L2: */ + int pcommit; + int pchoice = addoffsetinst(compst, IChoice); + codegen(compst, tree, 0, tt, fullset); + pcommit = addoffsetinst(compst, IBackCommit); + jumptohere(compst, pchoice); + addinstruction(compst, IFail, 0); + jumptohere(compst, pcommit); + } +} + + +/* +** Captures: if pattern has fixed (and not too big) length, and it +** has no nested captures, use a single IFullCapture instruction +** after the match; otherwise, enclose the pattern with OpenCapture - +** CloseCapture. +*/ +static void codecapture (CompileState *compst, TTree *tree, int tt, + const Charset *fl) { + int len = fixedlen(sib1(tree)); + if (len >= 0 && len <= MAXOFF && !hascaptures(sib1(tree))) { + codegen(compst, sib1(tree), 0, tt, fl); + addinstcap(compst, IFullCapture, tree->cap, tree->key, len); + } + else { + addinstcap(compst, IOpenCapture, tree->cap, tree->key, 0); + codegen(compst, sib1(tree), 0, tt, fl); + addinstcap(compst, ICloseCapture, Cclose, 0, 0); + } +} + + +static void coderuntime (CompileState *compst, TTree *tree, int tt) { + addinstcap(compst, IOpenCapture, Cgroup, tree->key, 0); + codegen(compst, sib1(tree), 0, tt, fullset); + addinstcap(compst, ICloseRunTime, Cclose, 0, 0); +} + + +/* +** Repetion; optimizations: +** When pattern is a charset, can use special instruction ISpan. +** When pattern is head fail, or if it starts with characters that +** are disjoint from what follows the repetions, a simple test +** is enough (a fail inside the repetition would backtrack to fail +** again in the following pattern, so there is no need for a choice). +** When 'opt' is true, the repetion can reuse the Choice already +** active in the stack. +*/ +static void coderep (CompileState *compst, TTree *tree, int opt, + const Charset *fl) { + Charset st; + if (tocharset(tree, &st)) { + addinstruction(compst, ISpan, 0); + addcharset(compst, st.cs); + } + else { + int e1 = getfirst(tree, fullset, &st); + if (headfail(tree) || (!e1 && cs_disjoint(&st, fl))) { + /* L1: test (fail(p1)) -> L2;

; jmp L1; L2: */ + int jmp; + int test = codetestset(compst, &st, 0); + codegen(compst, tree, 0, test, fullset); + jmp = addoffsetinst(compst, IJmp); + jumptohere(compst, test); + jumptothere(compst, jmp, test); + } + else { + /* test(fail(p1)) -> L2; choice L2; L1:

; partialcommit L1; L2: */ + /* or (if 'opt'): partialcommit L1; L1:

; partialcommit L1; */ + int commit, l2; + int test = codetestset(compst, &st, e1); + int pchoice = NOINST; + if (opt) + jumptohere(compst, addoffsetinst(compst, IPartialCommit)); + else + pchoice = addoffsetinst(compst, IChoice); + l2 = gethere(compst); + codegen(compst, tree, 0, NOINST, fullset); + commit = addoffsetinst(compst, IPartialCommit); + jumptothere(compst, commit, l2); + jumptohere(compst, pchoice); + jumptohere(compst, test); + } + } +} + + +/* +** Not predicate; optimizations: +** In any case, if first test fails, 'not' succeeds, so it can jump to +** the end. If pattern is headfail, that is all (it cannot fail +** in other parts); this case includes 'not' of simple sets. Otherwise, +** use the default code (a choice plus a failtwice). +*/ +static void codenot (CompileState *compst, TTree *tree) { + Charset st; + int e = getfirst(tree, fullset, &st); + int test = codetestset(compst, &st, e); + if (headfail(tree)) /* test (fail(p1)) -> L1; fail; L1: */ + addinstruction(compst, IFail, 0); + else { + /* test(fail(p))-> L1; choice L1;

; failtwice; L1: */ + int pchoice = addoffsetinst(compst, IChoice); + codegen(compst, tree, 0, NOINST, fullset); + addinstruction(compst, IFailTwice, 0); + jumptohere(compst, pchoice); + } + jumptohere(compst, test); +} + + +/* +** change open calls to calls, using list 'positions' to find +** correct offsets; also optimize tail calls +*/ +static void correctcalls (CompileState *compst, int *positions, + int from, int to) { + int i; + Instruction *code = compst->p->code; + for (i = from; i < to; i += sizei(&code[i])) { + if (code[i].i.code == IOpenCall) { + int n = code[i].i.key; /* rule number */ + int rule = positions[n]; /* rule position */ + assert(rule == from || code[rule - 1].i.code == IRet); + if (code[finaltarget(code, i + 2)].i.code == IRet) /* call; ret ? */ + code[i].i.code = IJmp; /* tail call */ + else + code[i].i.code = ICall; + jumptothere(compst, i, rule); /* call jumps to respective rule */ + } + } + assert(i == to); +} + + +/* +** Code for a grammar: +** call L1; jmp L2; L1: rule 1; ret; rule 2; ret; ...; L2: +*/ +static void codegrammar (CompileState *compst, TTree *grammar) { + int positions[MAXRULES]; + int rulenumber = 0; + TTree *rule; + int firstcall = addoffsetinst(compst, ICall); /* call initial rule */ + int jumptoend = addoffsetinst(compst, IJmp); /* jump to the end */ + int start = gethere(compst); /* here starts the initial rule */ + jumptohere(compst, firstcall); + for (rule = sib1(grammar); rule->tag == TRule; rule = sib2(rule)) { + positions[rulenumber++] = gethere(compst); /* save rule position */ + codegen(compst, sib1(rule), 0, NOINST, fullset); /* code rule */ + addinstruction(compst, IRet, 0); + } + assert(rule->tag == TTrue); + jumptohere(compst, jumptoend); + correctcalls(compst, positions, start, gethere(compst)); +} + + +static void codecall (CompileState *compst, TTree *call) { + int c = addoffsetinst(compst, IOpenCall); /* to be corrected later */ + getinstr(compst, c).i.key = sib2(call)->cap; /* rule number */ + assert(sib2(call)->tag == TRule); +} + + +/* +** Code first child of a sequence +** (second child is called in-place to allow tail call) +** Return 'tt' for second child +*/ +static int codeseq1 (CompileState *compst, TTree *p1, TTree *p2, + int tt, const Charset *fl) { + if (needfollow(p1)) { + Charset fl1; + getfirst(p2, fl, &fl1); /* p1 follow is p2 first */ + codegen(compst, p1, 0, tt, &fl1); + } + else /* use 'fullset' as follow */ + codegen(compst, p1, 0, tt, fullset); + if (fixedlen(p1) != 0) /* can 'p1' consume anything? */ + return NOINST; /* invalidate test */ + else return tt; /* else 'tt' still protects sib2 */ +} + + +/* +** Main code-generation function: dispatch to auxiliar functions +** according to kind of tree. ('needfollow' should return true +** only for consructions that use 'fl'.) +*/ +static void codegen (CompileState *compst, TTree *tree, int opt, int tt, + const Charset *fl) { + tailcall: + switch (tree->tag) { + case TChar: codechar(compst, tree->u.n, tt); break; + case TAny: addinstruction(compst, IAny, 0); break; + case TSet: codecharset(compst, treebuffer(tree), tt); break; + case TTrue: break; + case TFalse: addinstruction(compst, IFail, 0); break; + case TChoice: codechoice(compst, sib1(tree), sib2(tree), opt, fl); break; + case TRep: coderep(compst, sib1(tree), opt, fl); break; + case TBehind: codebehind(compst, tree); break; + case TNot: codenot(compst, sib1(tree)); break; + case TAnd: codeand(compst, sib1(tree), tt); break; + case TCapture: codecapture(compst, tree, tt, fl); break; + case TRunTime: coderuntime(compst, tree, tt); break; + case TGrammar: codegrammar(compst, tree); break; + case TCall: codecall(compst, tree); break; + case TSeq: { + tt = codeseq1(compst, sib1(tree), sib2(tree), tt, fl); /* code 'p1' */ + /* codegen(compst, p2, opt, tt, fl); */ + tree = sib2(tree); goto tailcall; + } + default: assert(0); + } +} + + +/* +** Optimize jumps and other jump-like instructions. +** * Update labels of instructions with labels to their final +** destinations (e.g., choice L1; ... L1: jmp L2: becomes +** choice L2) +** * Jumps to other instructions that do jumps become those +** instructions (e.g., jump to return becomes a return; jump +** to commit becomes a commit) +*/ +static void peephole (CompileState *compst) { + Instruction *code = compst->p->code; + int i; + for (i = 0; i < compst->ncode; i += sizei(&code[i])) { + redo: + switch (code[i].i.code) { + case IChoice: case ICall: case ICommit: case IPartialCommit: + case IBackCommit: case ITestChar: case ITestSet: + case ITestAny: { /* instructions with labels */ + jumptothere(compst, i, finallabel(code, i)); /* optimize label */ + break; + } + case IJmp: { + int ft = finaltarget(code, i); + switch (code[ft].i.code) { /* jumping to what? */ + case IRet: case IFail: case IFailTwice: + case IEnd: { /* instructions with unconditional implicit jumps */ + code[i] = code[ft]; /* jump becomes that instruction */ + code[i + 1].i.code = IAny; /* 'no-op' for target position */ + break; + } + case ICommit: case IPartialCommit: + case IBackCommit: { /* inst. with unconditional explicit jumps */ + int fft = finallabel(code, ft); + code[i] = code[ft]; /* jump becomes that instruction... */ + jumptothere(compst, i, fft); /* but must correct its offset */ + goto redo; /* reoptimize its label */ + } + default: { + jumptothere(compst, i, ft); /* optimize label */ + break; + } + } + break; + } + default: break; + } + } + assert(code[i - 1].i.code == IEnd); +} + + +/* +** Compile a pattern +*/ +Instruction *compile (lua_State *L, Pattern *p) { + CompileState compst; + compst.p = p; compst.ncode = 0; compst.L = L; + realloccode(L, p, 2); /* minimum initial size */ + codegen(&compst, p->tree, 0, NOINST, fullset); + addinstruction(&compst, IEnd, 0); + realloccode(L, p, compst.ncode); /* set final size */ + peephole(&compst); + return p->code; +} + + +/* }====================================================== */ + diff --git a/luaclib/src/lpeg/lpcode.h b/luaclib/src/lpeg/lpcode.h new file mode 100644 index 00000000..f4f98a0a --- /dev/null +++ b/luaclib/src/lpeg/lpcode.h @@ -0,0 +1,40 @@ +/* +** $Id: lpcode.h $ +*/ + +#if !defined(lpcode_h) +#define lpcode_h + +#include + +#include "lptypes.h" +#include "lptree.h" +#include "lpvm.h" + +int tocharset (TTree *tree, Charset *cs); +int checkaux (TTree *tree, int pred); +int fixedlen (TTree *tree); +int hascaptures (TTree *tree); +int lp_gc (lua_State *L); +Instruction *compile (lua_State *L, Pattern *p); +void realloccode (lua_State *L, Pattern *p, int nsize); +int sizei (const Instruction *i); + + +#define PEnullable 0 +#define PEnofail 1 + +/* +** nofail(t) implies that 't' cannot fail with any input +*/ +#define nofail(t) checkaux(t, PEnofail) + +/* +** (not nullable(t)) implies 't' cannot match without consuming +** something +*/ +#define nullable(t) checkaux(t, PEnullable) + + + +#endif diff --git a/luaclib/src/lpeg/lpeg-128.gif b/luaclib/src/lpeg/lpeg-128.gif new file mode 100644 index 00000000..bbf5e78b Binary files /dev/null and b/luaclib/src/lpeg/lpeg-128.gif differ diff --git a/luaclib/src/lpeg/lpeg.html b/luaclib/src/lpeg/lpeg.html new file mode 100644 index 00000000..8b9f59c2 --- /dev/null +++ b/luaclib/src/lpeg/lpeg.html @@ -0,0 +1,1439 @@ + + + + LPeg - Parsing Expression Grammars For Lua + + + + + + + +

+ + + diff --git a/luaclib/src/lpeg/lpprint.c b/luaclib/src/lpeg/lpprint.c new file mode 100644 index 00000000..ce48672a --- /dev/null +++ b/luaclib/src/lpeg/lpprint.c @@ -0,0 +1,244 @@ +/* +** $Id: lpprint.c $ +** Copyright 2007, Lua.org & PUC-Rio (see 'lpeg.html' for license) +*/ + +#include + +#include + + +#include "lptypes.h" +#include "lpprint.h" +#include "lpcode.h" + + +#if defined(LPEG_DEBUG) + +/* +** {====================================================== +** Printing patterns (for debugging) +** ======================================================= +*/ + + +void printcharset (const byte *st) { + int i; + printf("["); + for (i = 0; i <= UCHAR_MAX; i++) { + int first = i; + while (testchar(st, i) && i <= UCHAR_MAX) i++; + if (i - 1 == first) /* unary range? */ + printf("(%02x)", first); + else if (i - 1 > first) /* non-empty range? */ + printf("(%02x-%02x)", first, i - 1); + } + printf("]"); +} + + +static const char *capkind (int kind) { + const char *const modes[] = { + "close", "position", "constant", "backref", + "argument", "simple", "table", "function", + "query", "string", "num", "substitution", "fold", + "runtime", "group"}; + return modes[kind]; +} + + +static void printjmp (const Instruction *op, const Instruction *p) { + printf("-> %d", (int)(p + (p + 1)->offset - op)); +} + + +void printinst (const Instruction *op, const Instruction *p) { + const char *const names[] = { + "any", "char", "set", + "testany", "testchar", "testset", + "span", "behind", + "ret", "end", + "choice", "jmp", "call", "open_call", + "commit", "partial_commit", "back_commit", "failtwice", "fail", "giveup", + "fullcapture", "opencapture", "closecapture", "closeruntime" + }; + printf("%02ld: %s ", (long)(p - op), names[p->i.code]); + switch ((Opcode)p->i.code) { + case IChar: { + printf("'%c'", p->i.aux); + break; + } + case ITestChar: { + printf("'%c'", p->i.aux); printjmp(op, p); + break; + } + case IFullCapture: { + printf("%s (size = %d) (idx = %d)", + capkind(getkind(p)), getoff(p), p->i.key); + break; + } + case IOpenCapture: { + printf("%s (idx = %d)", capkind(getkind(p)), p->i.key); + break; + } + case ISet: { + printcharset((p+1)->buff); + break; + } + case ITestSet: { + printcharset((p+2)->buff); printjmp(op, p); + break; + } + case ISpan: { + printcharset((p+1)->buff); + break; + } + case IOpenCall: { + printf("-> %d", (p + 1)->offset); + break; + } + case IBehind: { + printf("%d", p->i.aux); + break; + } + case IJmp: case ICall: case ICommit: case IChoice: + case IPartialCommit: case IBackCommit: case ITestAny: { + printjmp(op, p); + break; + } + default: break; + } + printf("\n"); +} + + +void printpatt (Instruction *p, int n) { + Instruction *op = p; + while (p < op + n) { + printinst(op, p); + p += sizei(p); + } +} + + +#if defined(LPEG_DEBUG) +static void printcap (Capture *cap) { + printf("%s (idx: %d - size: %d) -> %p\n", + capkind(cap->kind), cap->idx, cap->siz, cap->s); +} + + +void printcaplist (Capture *cap, Capture *limit) { + printf(">======\n"); + for (; cap->s && (limit == NULL || cap < limit); cap++) + printcap(cap); + printf("=======\n"); +} +#endif + +/* }====================================================== */ + + +/* +** {====================================================== +** Printing trees (for debugging) +** ======================================================= +*/ + +static const char *tagnames[] = { + "char", "set", "any", + "true", "false", + "rep", + "seq", "choice", + "not", "and", + "call", "opencall", "rule", "grammar", + "behind", + "capture", "run-time" +}; + + +void printtree (TTree *tree, int ident) { + int i; + for (i = 0; i < ident; i++) printf(" "); + printf("%s", tagnames[tree->tag]); + switch (tree->tag) { + case TChar: { + int c = tree->u.n; + if (isprint(c)) + printf(" '%c'\n", c); + else + printf(" (%02X)\n", c); + break; + } + case TSet: { + printcharset(treebuffer(tree)); + printf("\n"); + break; + } + case TOpenCall: case TCall: { + assert(sib2(tree)->tag == TRule); + printf(" key: %d (rule: %d)\n", tree->key, sib2(tree)->cap); + break; + } + case TBehind: { + printf(" %d\n", tree->u.n); + printtree(sib1(tree), ident + 2); + break; + } + case TCapture: { + printf(" kind: '%s' key: %d\n", capkind(tree->cap), tree->key); + printtree(sib1(tree), ident + 2); + break; + } + case TRule: { + printf(" n: %d key: %d\n", tree->cap, tree->key); + printtree(sib1(tree), ident + 2); + break; /* do not print next rule as a sibling */ + } + case TGrammar: { + TTree *rule = sib1(tree); + printf(" %d\n", tree->u.n); /* number of rules */ + for (i = 0; i < tree->u.n; i++) { + printtree(rule, ident + 2); + rule = sib2(rule); + } + assert(rule->tag == TTrue); /* sentinel */ + break; + } + default: { + int sibs = numsiblings[tree->tag]; + printf("\n"); + if (sibs >= 1) { + printtree(sib1(tree), ident + 2); + if (sibs >= 2) + printtree(sib2(tree), ident + 2); + } + break; + } + } +} + + +void printktable (lua_State *L, int idx) { + int n, i; + lua_getuservalue(L, idx); + if (lua_isnil(L, -1)) /* no ktable? */ + return; + n = lua_rawlen(L, -1); + printf("["); + for (i = 1; i <= n; i++) { + printf("%d = ", i); + lua_rawgeti(L, -1, i); + if (lua_isstring(L, -1)) + printf("%s ", lua_tostring(L, -1)); + else + printf("%s ", lua_typename(L, lua_type(L, -1))); + lua_pop(L, 1); + } + printf("]\n"); + /* leave ktable at the stack */ +} + +/* }====================================================== */ + +#endif diff --git a/luaclib/src/lpeg/lpprint.h b/luaclib/src/lpeg/lpprint.h new file mode 100644 index 00000000..15ef121d --- /dev/null +++ b/luaclib/src/lpeg/lpprint.h @@ -0,0 +1,36 @@ +/* +** $Id: lpprint.h $ +*/ + + +#if !defined(lpprint_h) +#define lpprint_h + + +#include "lptree.h" +#include "lpvm.h" + + +#if defined(LPEG_DEBUG) + +void printpatt (Instruction *p, int n); +void printtree (TTree *tree, int ident); +void printktable (lua_State *L, int idx); +void printcharset (const byte *st); +void printcaplist (Capture *cap, Capture *limit); +void printinst (const Instruction *op, const Instruction *p); + +#else + +#define printktable(L,idx) \ + luaL_error(L, "function only implemented in debug mode") +#define printtree(tree,i) \ + luaL_error(L, "function only implemented in debug mode") +#define printpatt(p,n) \ + luaL_error(L, "function only implemented in debug mode") + +#endif + + +#endif + diff --git a/luaclib/src/lpeg/lptree.c b/luaclib/src/lpeg/lptree.c new file mode 100644 index 00000000..7cd8c546 --- /dev/null +++ b/luaclib/src/lpeg/lptree.c @@ -0,0 +1,1301 @@ +/* +** $Id: lptree.c $ +** Copyright 2013, Lua.org & PUC-Rio (see 'lpeg.html' for license) +*/ + +#include + +#include + +#include "lptypes.h" +#include "lpcap.h" +#include "lpcode.h" +#include "lpprint.h" +#include "lptree.h" + + +/* number of siblings for each tree */ +const byte numsiblings[] = { + 0, 0, 0, /* char, set, any */ + 0, 0, /* true, false */ + 1, /* rep */ + 2, 2, /* seq, choice */ + 1, 1, /* not, and */ + 0, 0, 2, 1, /* call, opencall, rule, grammar */ + 1, /* behind */ + 1, 1 /* capture, runtime capture */ +}; + + +static TTree *newgrammar (lua_State *L, int arg); + + +/* +** returns a reasonable name for value at index 'idx' on the stack +*/ +static const char *val2str (lua_State *L, int idx) { + const char *k = lua_tostring(L, idx); + if (k != NULL) + return lua_pushfstring(L, "%s", k); + else + return lua_pushfstring(L, "(a %s)", luaL_typename(L, idx)); +} + + +/* +** Fix a TOpenCall into a TCall node, using table 'postable' to +** translate a key to its rule address in the tree. Raises an +** error if key does not exist. +*/ +static void fixonecall (lua_State *L, int postable, TTree *g, TTree *t) { + int n; + lua_rawgeti(L, -1, t->key); /* get rule's name */ + lua_gettable(L, postable); /* query name in position table */ + n = lua_tonumber(L, -1); /* get (absolute) position */ + lua_pop(L, 1); /* remove position */ + if (n == 0) { /* no position? */ + lua_rawgeti(L, -1, t->key); /* get rule's name again */ + luaL_error(L, "rule '%s' undefined in given grammar", val2str(L, -1)); + } + t->tag = TCall; + t->u.ps = n - (t - g); /* position relative to node */ + assert(sib2(t)->tag == TRule); + sib2(t)->key = t->key; /* fix rule's key */ +} + + +/* +** Transform left associative constructions into right +** associative ones, for sequence and choice; that is: +** (t11 + t12) + t2 => t11 + (t12 + t2) +** (t11 * t12) * t2 => t11 * (t12 * t2) +** (that is, Op (Op t11 t12) t2 => Op t11 (Op t12 t2)) +*/ +static void correctassociativity (TTree *tree) { + TTree *t1 = sib1(tree); + assert(tree->tag == TChoice || tree->tag == TSeq); + while (t1->tag == tree->tag) { + int n1size = tree->u.ps - 1; /* t1 == Op t11 t12 */ + int n11size = t1->u.ps - 1; + int n12size = n1size - n11size - 1; + memmove(sib1(tree), sib1(t1), n11size * sizeof(TTree)); /* move t11 */ + tree->u.ps = n11size + 1; + sib2(tree)->tag = tree->tag; + sib2(tree)->u.ps = n12size + 1; + } +} + + +/* +** Make final adjustments in a tree. Fix open calls in tree 't', +** making them refer to their respective rules or raising appropriate +** errors (if not inside a grammar). Correct associativity of associative +** constructions (making them right associative). Assume that tree's +** ktable is at the top of the stack (for error messages). +*/ +static void finalfix (lua_State *L, int postable, TTree *g, TTree *t) { + tailcall: + switch (t->tag) { + case TGrammar: /* subgrammars were already fixed */ + return; + case TOpenCall: { + if (g != NULL) /* inside a grammar? */ + fixonecall(L, postable, g, t); + else { /* open call outside grammar */ + lua_rawgeti(L, -1, t->key); + luaL_error(L, "rule '%s' used outside a grammar", val2str(L, -1)); + } + break; + } + case TSeq: case TChoice: + correctassociativity(t); + break; + } + switch (numsiblings[t->tag]) { + case 1: /* finalfix(L, postable, g, sib1(t)); */ + t = sib1(t); goto tailcall; + case 2: + finalfix(L, postable, g, sib1(t)); + t = sib2(t); goto tailcall; /* finalfix(L, postable, g, sib2(t)); */ + default: assert(numsiblings[t->tag] == 0); break; + } +} + + + +/* +** {=================================================================== +** KTable manipulation +** +** - The ktable of a pattern 'p' can be shared by other patterns that +** contain 'p' and no other constants. Because of this sharing, we +** should not add elements to a 'ktable' unless it was freshly created +** for the new pattern. +** +** - The maximum index in a ktable is USHRT_MAX, because trees and +** patterns use unsigned shorts to store those indices. +** ==================================================================== +*/ + +/* +** Create a new 'ktable' to the pattern at the top of the stack. +*/ +static void newktable (lua_State *L, int n) { + lua_createtable(L, n, 0); /* create a fresh table */ + lua_setuservalue(L, -2); /* set it as 'ktable' for pattern */ +} + + +/* +** Add element 'idx' to 'ktable' of pattern at the top of the stack; +** Return index of new element. +** If new element is nil, does not add it to table (as it would be +** useless) and returns 0, as ktable[0] is always nil. +*/ +static int addtoktable (lua_State *L, int idx) { + if (lua_isnil(L, idx)) /* nil value? */ + return 0; + else { + int n; + lua_getuservalue(L, -1); /* get ktable from pattern */ + n = lua_rawlen(L, -1); + if (n >= USHRT_MAX) + luaL_error(L, "too many Lua values in pattern"); + lua_pushvalue(L, idx); /* element to be added */ + lua_rawseti(L, -2, ++n); + lua_pop(L, 1); /* remove 'ktable' */ + return n; + } +} + + +/* +** Return the number of elements in the ktable at 'idx'. +** In Lua 5.2/5.3, default "environment" for patterns is nil, not +** a table. Treat it as an empty table. In Lua 5.1, assumes that +** the environment has no numeric indices (len == 0) +*/ +static int ktablelen (lua_State *L, int idx) { + if (!lua_istable(L, idx)) return 0; + else return lua_rawlen(L, idx); +} + + +/* +** Concatentate the contents of table 'idx1' into table 'idx2'. +** (Assume that both indices are negative.) +** Return the original length of table 'idx2' (or 0, if no +** element was added, as there is no need to correct any index). +*/ +static int concattable (lua_State *L, int idx1, int idx2) { + int i; + int n1 = ktablelen(L, idx1); + int n2 = ktablelen(L, idx2); + if (n1 + n2 > USHRT_MAX) + luaL_error(L, "too many Lua values in pattern"); + if (n1 == 0) return 0; /* nothing to correct */ + for (i = 1; i <= n1; i++) { + lua_rawgeti(L, idx1, i); + lua_rawseti(L, idx2 - 1, n2 + i); /* correct 'idx2' */ + } + return n2; +} + + +/* +** When joining 'ktables', constants from one of the subpatterns must +** be renumbered; 'correctkeys' corrects their indices (adding 'n' +** to each of them) +*/ +static void correctkeys (TTree *tree, int n) { + if (n == 0) return; /* no correction? */ + tailcall: + switch (tree->tag) { + case TOpenCall: case TCall: case TRunTime: case TRule: { + if (tree->key > 0) + tree->key += n; + break; + } + case TCapture: { + if (tree->key > 0 && tree->cap != Carg && tree->cap != Cnum) + tree->key += n; + break; + } + default: break; + } + switch (numsiblings[tree->tag]) { + case 1: /* correctkeys(sib1(tree), n); */ + tree = sib1(tree); goto tailcall; + case 2: + correctkeys(sib1(tree), n); + tree = sib2(tree); goto tailcall; /* correctkeys(sib2(tree), n); */ + default: assert(numsiblings[tree->tag] == 0); break; + } +} + + +/* +** Join the ktables from p1 and p2 the ktable for the new pattern at the +** top of the stack, reusing them when possible. +*/ +static void joinktables (lua_State *L, int p1, TTree *t2, int p2) { + int n1, n2; + lua_getuservalue(L, p1); /* get ktables */ + lua_getuservalue(L, p2); + n1 = ktablelen(L, -2); + n2 = ktablelen(L, -1); + if (n1 == 0 && n2 == 0) /* are both tables empty? */ + lua_pop(L, 2); /* nothing to be done; pop tables */ + else if (n2 == 0 || lp_equal(L, -2, -1)) { /* 2nd table empty or equal? */ + lua_pop(L, 1); /* pop 2nd table */ + lua_setuservalue(L, -2); /* set 1st ktable into new pattern */ + } + else if (n1 == 0) { /* first table is empty? */ + lua_setuservalue(L, -3); /* set 2nd table into new pattern */ + lua_pop(L, 1); /* pop 1st table */ + } + else { + lua_createtable(L, n1 + n2, 0); /* create ktable for new pattern */ + /* stack: new p; ktable p1; ktable p2; new ktable */ + concattable(L, -3, -1); /* from p1 into new ktable */ + concattable(L, -2, -1); /* from p2 into new ktable */ + lua_setuservalue(L, -4); /* new ktable becomes 'p' environment */ + lua_pop(L, 2); /* pop other ktables */ + correctkeys(t2, n1); /* correction for indices from p2 */ + } +} + + +/* +** copy 'ktable' of element 'idx' to new tree (on top of stack) +*/ +static void copyktable (lua_State *L, int idx) { + lua_getuservalue(L, idx); + lua_setuservalue(L, -2); +} + + +/* +** merge 'ktable' from 'stree' at stack index 'idx' into 'ktable' +** from tree at the top of the stack, and correct corresponding +** tree. +*/ +static void mergektable (lua_State *L, int idx, TTree *stree) { + int n; + lua_getuservalue(L, -1); /* get ktables */ + lua_getuservalue(L, idx); + n = concattable(L, -1, -2); + lua_pop(L, 2); /* remove both ktables */ + correctkeys(stree, n); +} + + +/* +** Create a new 'ktable' to the pattern at the top of the stack, adding +** all elements from pattern 'p' (if not 0) plus element 'idx' to it. +** Return index of new element. +*/ +static int addtonewktable (lua_State *L, int p, int idx) { + newktable(L, 1); + if (p) + mergektable(L, p, NULL); + return addtoktable(L, idx); +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Tree generation +** ======================================================= +*/ + +/* +** In 5.2, could use 'luaL_testudata'... +*/ +static int testpattern (lua_State *L, int idx) { + if (lua_touserdata(L, idx)) { /* value is a userdata? */ + if (lua_getmetatable(L, idx)) { /* does it have a metatable? */ + luaL_getmetatable(L, PATTERN_T); + if (lua_rawequal(L, -1, -2)) { /* does it have the correct mt? */ + lua_pop(L, 2); /* remove both metatables */ + return 1; + } + } + } + return 0; +} + + +static Pattern *getpattern (lua_State *L, int idx) { + return (Pattern *)luaL_checkudata(L, idx, PATTERN_T); +} + + +static int getsize (lua_State *L, int idx) { + return (lua_rawlen(L, idx) - sizeof(Pattern)) / sizeof(TTree) + 1; +} + + +static TTree *gettree (lua_State *L, int idx, int *len) { + Pattern *p = getpattern(L, idx); + if (len) + *len = getsize(L, idx); + return p->tree; +} + + +/* +** create a pattern. Set its uservalue (the 'ktable') equal to its +** metatable. (It could be any empty sequence; the metatable is at +** hand here, so we use it.) +*/ +static TTree *newtree (lua_State *L, int len) { + size_t size = (len - 1) * sizeof(TTree) + sizeof(Pattern); + Pattern *p = (Pattern *)lua_newuserdata(L, size); + luaL_getmetatable(L, PATTERN_T); + lua_pushvalue(L, -1); + lua_setuservalue(L, -3); + lua_setmetatable(L, -2); + p->code = NULL; p->codesize = 0; + return p->tree; +} + + +static TTree *newleaf (lua_State *L, int tag) { + TTree *tree = newtree(L, 1); + tree->tag = tag; + return tree; +} + + +static TTree *newcharset (lua_State *L) { + TTree *tree = newtree(L, bytes2slots(CHARSETSIZE) + 1); + tree->tag = TSet; + loopset(i, treebuffer(tree)[i] = 0); + return tree; +} + + +/* +** add to tree a sequence where first sibling is 'sib' (with size +** 'sibsize'); returns position for second sibling +*/ +static TTree *seqaux (TTree *tree, TTree *sib, int sibsize) { + tree->tag = TSeq; tree->u.ps = sibsize + 1; + memcpy(sib1(tree), sib, sibsize * sizeof(TTree)); + return sib2(tree); +} + + +/* +** Build a sequence of 'n' nodes, each with tag 'tag' and 'u.n' got +** from the array 's' (or 0 if array is NULL). (TSeq is binary, so it +** must build a sequence of sequence of sequence...) +*/ +static void fillseq (TTree *tree, int tag, int n, const char *s) { + int i; + for (i = 0; i < n - 1; i++) { /* initial n-1 copies of Seq tag; Seq ... */ + tree->tag = TSeq; tree->u.ps = 2; + sib1(tree)->tag = tag; + sib1(tree)->u.n = s ? (byte)s[i] : 0; + tree = sib2(tree); + } + tree->tag = tag; /* last one does not need TSeq */ + tree->u.n = s ? (byte)s[i] : 0; +} + + +/* +** Numbers as patterns: +** 0 == true (always match); n == TAny repeated 'n' times; +** -n == not (TAny repeated 'n' times) +*/ +static TTree *numtree (lua_State *L, int n) { + if (n == 0) + return newleaf(L, TTrue); + else { + TTree *tree, *nd; + if (n > 0) + tree = nd = newtree(L, 2 * n - 1); + else { /* negative: code it as !(-n) */ + n = -n; + tree = newtree(L, 2 * n); + tree->tag = TNot; + nd = sib1(tree); + } + fillseq(nd, TAny, n, NULL); /* sequence of 'n' any's */ + return tree; + } +} + + +/* +** Convert value at index 'idx' to a pattern +*/ +static TTree *getpatt (lua_State *L, int idx, int *len) { + TTree *tree; + switch (lua_type(L, idx)) { + case LUA_TSTRING: { + size_t slen; + const char *s = lua_tolstring(L, idx, &slen); /* get string */ + if (slen == 0) /* empty? */ + tree = newleaf(L, TTrue); /* always match */ + else { + tree = newtree(L, 2 * (slen - 1) + 1); + fillseq(tree, TChar, slen, s); /* sequence of 'slen' chars */ + } + break; + } + case LUA_TNUMBER: { + int n = lua_tointeger(L, idx); + tree = numtree(L, n); + break; + } + case LUA_TBOOLEAN: { + tree = (lua_toboolean(L, idx) ? newleaf(L, TTrue) : newleaf(L, TFalse)); + break; + } + case LUA_TTABLE: { + tree = newgrammar(L, idx); + break; + } + case LUA_TFUNCTION: { + tree = newtree(L, 2); + tree->tag = TRunTime; + tree->key = addtonewktable(L, 0, idx); + sib1(tree)->tag = TTrue; + break; + } + default: { + return gettree(L, idx, len); + } + } + lua_replace(L, idx); /* put new tree into 'idx' slot */ + if (len) + *len = getsize(L, idx); + return tree; +} + + +/* +** create a new tree, whith a new root and one sibling. +** Sibling must be on the Lua stack, at index 1. +*/ +static TTree *newroot1sib (lua_State *L, int tag) { + int s1; + TTree *tree1 = getpatt(L, 1, &s1); + TTree *tree = newtree(L, 1 + s1); /* create new tree */ + tree->tag = tag; + memcpy(sib1(tree), tree1, s1 * sizeof(TTree)); + copyktable(L, 1); + return tree; +} + + +/* +** create a new tree, whith a new root and 2 siblings. +** Siblings must be on the Lua stack, first one at index 1. +*/ +static TTree *newroot2sib (lua_State *L, int tag) { + int s1, s2; + TTree *tree1 = getpatt(L, 1, &s1); + TTree *tree2 = getpatt(L, 2, &s2); + TTree *tree = newtree(L, 1 + s1 + s2); /* create new tree */ + tree->tag = tag; + tree->u.ps = 1 + s1; + memcpy(sib1(tree), tree1, s1 * sizeof(TTree)); + memcpy(sib2(tree), tree2, s2 * sizeof(TTree)); + joinktables(L, 1, sib2(tree), 2); + return tree; +} + + +static int lp_P (lua_State *L) { + luaL_checkany(L, 1); + getpatt(L, 1, NULL); + lua_settop(L, 1); + return 1; +} + + +/* +** sequence operator; optimizations: +** false x => false, x true => x, true x => x +** (cannot do x . false => false because x may have runtime captures) +*/ +static int lp_seq (lua_State *L) { + TTree *tree1 = getpatt(L, 1, NULL); + TTree *tree2 = getpatt(L, 2, NULL); + if (tree1->tag == TFalse || tree2->tag == TTrue) + lua_pushvalue(L, 1); /* false . x == false, x . true = x */ + else if (tree1->tag == TTrue) + lua_pushvalue(L, 2); /* true . x = x */ + else + newroot2sib(L, TSeq); + return 1; +} + + +/* +** choice operator; optimizations: +** charset / charset => charset +** true / x => true, x / false => x, false / x => x +** (x / true is not equivalent to true) +*/ +static int lp_choice (lua_State *L) { + Charset st1, st2; + TTree *t1 = getpatt(L, 1, NULL); + TTree *t2 = getpatt(L, 2, NULL); + if (tocharset(t1, &st1) && tocharset(t2, &st2)) { + TTree *t = newcharset(L); + loopset(i, treebuffer(t)[i] = st1.cs[i] | st2.cs[i]); + } + else if (nofail(t1) || t2->tag == TFalse) + lua_pushvalue(L, 1); /* true / x => true, x / false => x */ + else if (t1->tag == TFalse) + lua_pushvalue(L, 2); /* false / x => x */ + else + newroot2sib(L, TChoice); + return 1; +} + + +/* +** p^n +*/ +static int lp_star (lua_State *L) { + int size1; + int n = (int)luaL_checkinteger(L, 2); + TTree *tree1 = getpatt(L, 1, &size1); + if (n >= 0) { /* seq tree1 (seq tree1 ... (seq tree1 (rep tree1))) */ + TTree *tree = newtree(L, (n + 1) * (size1 + 1)); + if (nullable(tree1)) + luaL_error(L, "loop body may accept empty string"); + while (n--) /* repeat 'n' times */ + tree = seqaux(tree, tree1, size1); + tree->tag = TRep; + memcpy(sib1(tree), tree1, size1 * sizeof(TTree)); + } + else { /* choice (seq tree1 ... choice tree1 true ...) true */ + TTree *tree; + n = -n; + /* size = (choice + seq + tree1 + true) * n, but the last has no seq */ + tree = newtree(L, n * (size1 + 3) - 1); + for (; n > 1; n--) { /* repeat (n - 1) times */ + tree->tag = TChoice; tree->u.ps = n * (size1 + 3) - 2; + sib2(tree)->tag = TTrue; + tree = sib1(tree); + tree = seqaux(tree, tree1, size1); + } + tree->tag = TChoice; tree->u.ps = size1 + 1; + sib2(tree)->tag = TTrue; + memcpy(sib1(tree), tree1, size1 * sizeof(TTree)); + } + copyktable(L, 1); + return 1; +} + + +/* +** #p == &p +*/ +static int lp_and (lua_State *L) { + newroot1sib(L, TAnd); + return 1; +} + + +/* +** -p == !p +*/ +static int lp_not (lua_State *L) { + newroot1sib(L, TNot); + return 1; +} + + +/* +** [t1 - t2] == Seq (Not t2) t1 +** If t1 and t2 are charsets, make their difference. +*/ +static int lp_sub (lua_State *L) { + Charset st1, st2; + int s1, s2; + TTree *t1 = getpatt(L, 1, &s1); + TTree *t2 = getpatt(L, 2, &s2); + if (tocharset(t1, &st1) && tocharset(t2, &st2)) { + TTree *t = newcharset(L); + loopset(i, treebuffer(t)[i] = st1.cs[i] & ~st2.cs[i]); + } + else { + TTree *tree = newtree(L, 2 + s1 + s2); + tree->tag = TSeq; /* sequence of... */ + tree->u.ps = 2 + s2; + sib1(tree)->tag = TNot; /* ...not... */ + memcpy(sib1(sib1(tree)), t2, s2 * sizeof(TTree)); /* ...t2 */ + memcpy(sib2(tree), t1, s1 * sizeof(TTree)); /* ... and t1 */ + joinktables(L, 1, sib1(tree), 2); + } + return 1; +} + + +static int lp_set (lua_State *L) { + size_t l; + const char *s = luaL_checklstring(L, 1, &l); + TTree *tree = newcharset(L); + while (l--) { + setchar(treebuffer(tree), (byte)(*s)); + s++; + } + return 1; +} + + +static int lp_range (lua_State *L) { + int arg; + int top = lua_gettop(L); + TTree *tree = newcharset(L); + for (arg = 1; arg <= top; arg++) { + int c; + size_t l; + const char *r = luaL_checklstring(L, arg, &l); + luaL_argcheck(L, l == 2, arg, "range must have two characters"); + for (c = (byte)r[0]; c <= (byte)r[1]; c++) + setchar(treebuffer(tree), c); + } + return 1; +} + + +/* +** Look-behind predicate +*/ +static int lp_behind (lua_State *L) { + TTree *tree; + TTree *tree1 = getpatt(L, 1, NULL); + int n = fixedlen(tree1); + luaL_argcheck(L, n >= 0, 1, "pattern may not have fixed length"); + luaL_argcheck(L, !hascaptures(tree1), 1, "pattern have captures"); + luaL_argcheck(L, n <= MAXBEHIND, 1, "pattern too long to look behind"); + tree = newroot1sib(L, TBehind); + tree->u.n = n; + return 1; +} + + +/* +** Create a non-terminal +*/ +static int lp_V (lua_State *L) { + TTree *tree = newleaf(L, TOpenCall); + luaL_argcheck(L, !lua_isnoneornil(L, 1), 1, "non-nil value expected"); + tree->key = addtonewktable(L, 0, 1); + return 1; +} + + +/* +** Create a tree for a non-empty capture, with a body and +** optionally with an associated Lua value (at index 'labelidx' in the +** stack) +*/ +static int capture_aux (lua_State *L, int cap, int labelidx) { + TTree *tree = newroot1sib(L, TCapture); + tree->cap = cap; + tree->key = (labelidx == 0) ? 0 : addtonewktable(L, 1, labelidx); + return 1; +} + + +/* +** Fill a tree with an empty capture, using an empty (TTrue) sibling. +** (The 'key' field must be filled by the caller to finish the tree.) +*/ +static TTree *auxemptycap (TTree *tree, int cap) { + tree->tag = TCapture; + tree->cap = cap; + sib1(tree)->tag = TTrue; + return tree; +} + + +/* +** Create a tree for an empty capture. +*/ +static TTree *newemptycap (lua_State *L, int cap, int key) { + TTree *tree = auxemptycap(newtree(L, 2), cap); + tree->key = key; + return tree; +} + + +/* +** Create a tree for an empty capture with an associated Lua value. +*/ +static TTree *newemptycapkey (lua_State *L, int cap, int idx) { + TTree *tree = auxemptycap(newtree(L, 2), cap); + tree->key = addtonewktable(L, 0, idx); + return tree; +} + + +/* +** Captures with syntax p / v +** (function capture, query capture, string capture, or number capture) +*/ +static int lp_divcapture (lua_State *L) { + switch (lua_type(L, 2)) { + case LUA_TFUNCTION: return capture_aux(L, Cfunction, 2); + case LUA_TTABLE: return capture_aux(L, Cquery, 2); + case LUA_TSTRING: return capture_aux(L, Cstring, 2); + case LUA_TNUMBER: { + int n = lua_tointeger(L, 2); + TTree *tree = newroot1sib(L, TCapture); + luaL_argcheck(L, 0 <= n && n <= SHRT_MAX, 1, "invalid number"); + tree->cap = Cnum; + tree->key = n; + return 1; + } + default: return luaL_argerror(L, 2, "invalid replacement value"); + } +} + + +static int lp_substcapture (lua_State *L) { + return capture_aux(L, Csubst, 0); +} + + +static int lp_tablecapture (lua_State *L) { + return capture_aux(L, Ctable, 0); +} + + +static int lp_groupcapture (lua_State *L) { + if (lua_isnoneornil(L, 2)) + return capture_aux(L, Cgroup, 0); + else + return capture_aux(L, Cgroup, 2); +} + + +static int lp_foldcapture (lua_State *L) { + luaL_checktype(L, 2, LUA_TFUNCTION); + return capture_aux(L, Cfold, 2); +} + + +static int lp_simplecapture (lua_State *L) { + return capture_aux(L, Csimple, 0); +} + + +static int lp_poscapture (lua_State *L) { + newemptycap(L, Cposition, 0); + return 1; +} + + +static int lp_argcapture (lua_State *L) { + int n = (int)luaL_checkinteger(L, 1); + luaL_argcheck(L, 0 < n && n <= SHRT_MAX, 1, "invalid argument index"); + newemptycap(L, Carg, n); + return 1; +} + + +static int lp_backref (lua_State *L) { + luaL_checkany(L, 1); + newemptycapkey(L, Cbackref, 1); + return 1; +} + + +/* +** Constant capture +*/ +static int lp_constcapture (lua_State *L) { + int i; + int n = lua_gettop(L); /* number of values */ + if (n == 0) /* no values? */ + newleaf(L, TTrue); /* no capture */ + else if (n == 1) + newemptycapkey(L, Cconst, 1); /* single constant capture */ + else { /* create a group capture with all values */ + TTree *tree = newtree(L, 1 + 3 * (n - 1) + 2); + newktable(L, n); /* create a 'ktable' for new tree */ + tree->tag = TCapture; + tree->cap = Cgroup; + tree->key = 0; + tree = sib1(tree); + for (i = 1; i <= n - 1; i++) { + tree->tag = TSeq; + tree->u.ps = 3; /* skip TCapture and its sibling */ + auxemptycap(sib1(tree), Cconst); + sib1(tree)->key = addtoktable(L, i); + tree = sib2(tree); + } + auxemptycap(tree, Cconst); + tree->key = addtoktable(L, i); + } + return 1; +} + + +static int lp_matchtime (lua_State *L) { + TTree *tree; + luaL_checktype(L, 2, LUA_TFUNCTION); + tree = newroot1sib(L, TRunTime); + tree->key = addtonewktable(L, 1, 2); + return 1; +} + +/* }====================================================== */ + + +/* +** {====================================================== +** Grammar - Tree generation +** ======================================================= +*/ + +/* +** push on the stack the index and the pattern for the +** initial rule of grammar at index 'arg' in the stack; +** also add that index into position table. +*/ +static void getfirstrule (lua_State *L, int arg, int postab) { + lua_rawgeti(L, arg, 1); /* access first element */ + if (lua_isstring(L, -1)) { /* is it the name of initial rule? */ + lua_pushvalue(L, -1); /* duplicate it to use as key */ + lua_gettable(L, arg); /* get associated rule */ + } + else { + lua_pushinteger(L, 1); /* key for initial rule */ + lua_insert(L, -2); /* put it before rule */ + } + if (!testpattern(L, -1)) { /* initial rule not a pattern? */ + if (lua_isnil(L, -1)) + luaL_error(L, "grammar has no initial rule"); + else + luaL_error(L, "initial rule '%s' is not a pattern", lua_tostring(L, -2)); + } + lua_pushvalue(L, -2); /* push key */ + lua_pushinteger(L, 1); /* push rule position (after TGrammar) */ + lua_settable(L, postab); /* insert pair at position table */ +} + +/* +** traverse grammar at index 'arg', pushing all its keys and patterns +** into the stack. Create a new table (before all pairs key-pattern) to +** collect all keys and their associated positions in the final tree +** (the "position table"). +** Return the number of rules and (in 'totalsize') the total size +** for the new tree. +*/ +static int collectrules (lua_State *L, int arg, int *totalsize) { + int n = 1; /* to count number of rules */ + int postab = lua_gettop(L) + 1; /* index of position table */ + int size; /* accumulator for total size */ + lua_newtable(L); /* create position table */ + getfirstrule(L, arg, postab); + size = 2 + getsize(L, postab + 2); /* TGrammar + TRule + rule */ + lua_pushnil(L); /* prepare to traverse grammar table */ + while (lua_next(L, arg) != 0) { + if (lua_tonumber(L, -2) == 1 || + lp_equal(L, -2, postab + 1)) { /* initial rule? */ + lua_pop(L, 1); /* remove value (keep key for lua_next) */ + continue; + } + if (!testpattern(L, -1)) /* value is not a pattern? */ + luaL_error(L, "rule '%s' is not a pattern", val2str(L, -2)); + luaL_checkstack(L, LUA_MINSTACK, "grammar has too many rules"); + lua_pushvalue(L, -2); /* push key (to insert into position table) */ + lua_pushinteger(L, size); + lua_settable(L, postab); + size += 1 + getsize(L, -1); /* update size */ + lua_pushvalue(L, -2); /* push key (for next lua_next) */ + n++; + } + *totalsize = size + 1; /* TTrue to finish list of rules */ + return n; +} + + +static void buildgrammar (lua_State *L, TTree *grammar, int frule, int n) { + int i; + TTree *nd = sib1(grammar); /* auxiliary pointer to traverse the tree */ + for (i = 0; i < n; i++) { /* add each rule into new tree */ + int ridx = frule + 2*i + 1; /* index of i-th rule */ + int rulesize; + TTree *rn = gettree(L, ridx, &rulesize); + nd->tag = TRule; + nd->key = 0; /* will be fixed when rule is used */ + nd->cap = i; /* rule number */ + nd->u.ps = rulesize + 1; /* point to next rule */ + memcpy(sib1(nd), rn, rulesize * sizeof(TTree)); /* copy rule */ + mergektable(L, ridx, sib1(nd)); /* merge its ktable into new one */ + nd = sib2(nd); /* move to next rule */ + } + nd->tag = TTrue; /* finish list of rules */ +} + + +/* +** Check whether a tree has potential infinite loops +*/ +static int checkloops (TTree *tree) { + tailcall: + if (tree->tag == TRep && nullable(sib1(tree))) + return 1; + else if (tree->tag == TGrammar) + return 0; /* sub-grammars already checked */ + else { + switch (numsiblings[tree->tag]) { + case 1: /* return checkloops(sib1(tree)); */ + tree = sib1(tree); goto tailcall; + case 2: + if (checkloops(sib1(tree))) return 1; + /* else return checkloops(sib2(tree)); */ + tree = sib2(tree); goto tailcall; + default: assert(numsiblings[tree->tag] == 0); return 0; + } + } +} + + +/* +** Give appropriate error message for 'verifyrule'. If a rule appears +** twice in 'passed', there is path from it back to itself without +** advancing the subject. +*/ +static int verifyerror (lua_State *L, int *passed, int npassed) { + int i, j; + for (i = npassed - 1; i >= 0; i--) { /* search for a repetition */ + for (j = i - 1; j >= 0; j--) { + if (passed[i] == passed[j]) { + lua_rawgeti(L, -1, passed[i]); /* get rule's key */ + return luaL_error(L, "rule '%s' may be left recursive", val2str(L, -1)); + } + } + } + return luaL_error(L, "too many left calls in grammar"); +} + + +/* +** Check whether a rule can be left recursive; raise an error in that +** case; otherwise return 1 iff pattern is nullable. +** The return value is used to check sequences, where the second pattern +** is only relevant if the first is nullable. +** Parameter 'nb' works as an accumulator, to allow tail calls in +** choices. ('nb' true makes function returns true.) +** Parameter 'passed' is a list of already visited rules, 'npassed' +** counts the elements in 'passed'. +** Assume ktable at the top of the stack. +*/ +static int verifyrule (lua_State *L, TTree *tree, int *passed, int npassed, + int nb) { + tailcall: + switch (tree->tag) { + case TChar: case TSet: case TAny: + case TFalse: + return nb; /* cannot pass from here */ + case TTrue: + case TBehind: /* look-behind cannot have calls */ + return 1; + case TNot: case TAnd: case TRep: + /* return verifyrule(L, sib1(tree), passed, npassed, 1); */ + tree = sib1(tree); nb = 1; goto tailcall; + case TCapture: case TRunTime: + /* return verifyrule(L, sib1(tree), passed, npassed, nb); */ + tree = sib1(tree); goto tailcall; + case TCall: + /* return verifyrule(L, sib2(tree), passed, npassed, nb); */ + tree = sib2(tree); goto tailcall; + case TSeq: /* only check 2nd child if first is nb */ + if (!verifyrule(L, sib1(tree), passed, npassed, 0)) + return nb; + /* else return verifyrule(L, sib2(tree), passed, npassed, nb); */ + tree = sib2(tree); goto tailcall; + case TChoice: /* must check both children */ + nb = verifyrule(L, sib1(tree), passed, npassed, nb); + /* return verifyrule(L, sib2(tree), passed, npassed, nb); */ + tree = sib2(tree); goto tailcall; + case TRule: + if (npassed >= MAXRULES) + return verifyerror(L, passed, npassed); + else { + passed[npassed++] = tree->key; + /* return verifyrule(L, sib1(tree), passed, npassed); */ + tree = sib1(tree); goto tailcall; + } + case TGrammar: + return nullable(tree); /* sub-grammar cannot be left recursive */ + default: assert(0); return 0; + } +} + + +static void verifygrammar (lua_State *L, TTree *grammar) { + int passed[MAXRULES]; + TTree *rule; + /* check left-recursive rules */ + for (rule = sib1(grammar); rule->tag == TRule; rule = sib2(rule)) { + if (rule->key == 0) continue; /* unused rule */ + verifyrule(L, sib1(rule), passed, 0, 0); + } + assert(rule->tag == TTrue); + /* check infinite loops inside rules */ + for (rule = sib1(grammar); rule->tag == TRule; rule = sib2(rule)) { + if (rule->key == 0) continue; /* unused rule */ + if (checkloops(sib1(rule))) { + lua_rawgeti(L, -1, rule->key); /* get rule's key */ + luaL_error(L, "empty loop in rule '%s'", val2str(L, -1)); + } + } + assert(rule->tag == TTrue); +} + + +/* +** Give a name for the initial rule if it is not referenced +*/ +static void initialrulename (lua_State *L, TTree *grammar, int frule) { + if (sib1(grammar)->key == 0) { /* initial rule is not referenced? */ + int n = lua_rawlen(L, -1) + 1; /* index for name */ + lua_pushvalue(L, frule); /* rule's name */ + lua_rawseti(L, -2, n); /* ktable was on the top of the stack */ + sib1(grammar)->key = n; + } +} + + +static TTree *newgrammar (lua_State *L, int arg) { + int treesize; + int frule = lua_gettop(L) + 2; /* position of first rule's key */ + int n = collectrules(L, arg, &treesize); + TTree *g = newtree(L, treesize); + luaL_argcheck(L, n <= MAXRULES, arg, "grammar has too many rules"); + g->tag = TGrammar; g->u.n = n; + lua_newtable(L); /* create 'ktable' */ + lua_setuservalue(L, -2); + buildgrammar(L, g, frule, n); + lua_getuservalue(L, -1); /* get 'ktable' for new tree */ + finalfix(L, frule - 1, g, sib1(g)); + initialrulename(L, g, frule); + verifygrammar(L, g); + lua_pop(L, 1); /* remove 'ktable' */ + lua_insert(L, -(n * 2 + 2)); /* move new table to proper position */ + lua_pop(L, n * 2 + 1); /* remove position table + rule pairs */ + return g; /* new table at the top of the stack */ +} + +/* }====================================================== */ + + +static Instruction *prepcompile (lua_State *L, Pattern *p, int idx) { + lua_getuservalue(L, idx); /* push 'ktable' (may be used by 'finalfix') */ + finalfix(L, 0, NULL, p->tree); + lua_pop(L, 1); /* remove 'ktable' */ + return compile(L, p); +} + + +static int lp_printtree (lua_State *L) { + TTree *tree = getpatt(L, 1, NULL); + int c = lua_toboolean(L, 2); + if (c) { + lua_getuservalue(L, 1); /* push 'ktable' (may be used by 'finalfix') */ + finalfix(L, 0, NULL, tree); + lua_pop(L, 1); /* remove 'ktable' */ + } + printktable(L, 1); + printtree(tree, 0); + return 0; +} + + +static int lp_printcode (lua_State *L) { + Pattern *p = getpattern(L, 1); + printktable(L, 1); + if (p->code == NULL) /* not compiled yet? */ + prepcompile(L, p, 1); + printpatt(p->code, p->codesize); + return 0; +} + + +/* +** Get the initial position for the match, interpreting negative +** values from the end of the subject +*/ +static size_t initposition (lua_State *L, size_t len) { + lua_Integer ii = luaL_optinteger(L, 3, 1); + if (ii > 0) { /* positive index? */ + if ((size_t)ii <= len) /* inside the string? */ + return (size_t)ii - 1; /* return it (corrected to 0-base) */ + else return len; /* crop at the end */ + } + else { /* negative index */ + if ((size_t)(-ii) <= len) /* inside the string? */ + return len - ((size_t)(-ii)); /* return position from the end */ + else return 0; /* crop at the beginning */ + } +} + + +/* +** Main match function +*/ +static int lp_match (lua_State *L) { + Capture capture[INITCAPSIZE]; + const char *r; + size_t l; + Pattern *p = (getpatt(L, 1, NULL), getpattern(L, 1)); + Instruction *code = (p->code != NULL) ? p->code : prepcompile(L, p, 1); + const char *s = luaL_checklstring(L, SUBJIDX, &l); + size_t i = initposition(L, l); + int ptop = lua_gettop(L); + lua_pushnil(L); /* initialize subscache */ + lua_pushlightuserdata(L, capture); /* initialize caplistidx */ + lua_getuservalue(L, 1); /* initialize penvidx */ + r = match(L, s, s + i, s + l, code, capture, ptop); + if (r == NULL) { + lua_pushnil(L); + return 1; + } + return getcaptures(L, s, r, ptop); +} + + + +/* +** {====================================================== +** Library creation and functions not related to matching +** ======================================================= +*/ + +/* maximum limit for stack size */ +#define MAXLIM (INT_MAX / 100) + +static int lp_setmax (lua_State *L) { + lua_Integer lim = luaL_checkinteger(L, 1); + luaL_argcheck(L, 0 < lim && lim <= MAXLIM, 1, "out of range"); + lua_settop(L, 1); + lua_setfield(L, LUA_REGISTRYINDEX, MAXSTACKIDX); + return 0; +} + + +static int lp_version (lua_State *L) { + lua_pushstring(L, VERSION); + return 1; +} + + +static int lp_type (lua_State *L) { + if (testpattern(L, 1)) + lua_pushliteral(L, "pattern"); + else + lua_pushnil(L); + return 1; +} + + +int lp_gc (lua_State *L) { + Pattern *p = getpattern(L, 1); + realloccode(L, p, 0); /* delete code block */ + return 0; +} + + +static void createcat (lua_State *L, const char *catname, int (catf) (int)) { + TTree *t = newcharset(L); + int i; + for (i = 0; i <= UCHAR_MAX; i++) + if (catf(i)) setchar(treebuffer(t), i); + lua_setfield(L, -2, catname); +} + + +static int lp_locale (lua_State *L) { + if (lua_isnoneornil(L, 1)) { + lua_settop(L, 0); + lua_createtable(L, 0, 12); + } + else { + luaL_checktype(L, 1, LUA_TTABLE); + lua_settop(L, 1); + } + createcat(L, "alnum", isalnum); + createcat(L, "alpha", isalpha); + createcat(L, "cntrl", iscntrl); + createcat(L, "digit", isdigit); + createcat(L, "graph", isgraph); + createcat(L, "lower", islower); + createcat(L, "print", isprint); + createcat(L, "punct", ispunct); + createcat(L, "space", isspace); + createcat(L, "upper", isupper); + createcat(L, "xdigit", isxdigit); + return 1; +} + + +static struct luaL_Reg pattreg[] = { + {"ptree", lp_printtree}, + {"pcode", lp_printcode}, + {"match", lp_match}, + {"B", lp_behind}, + {"V", lp_V}, + {"C", lp_simplecapture}, + {"Cc", lp_constcapture}, + {"Cmt", lp_matchtime}, + {"Cb", lp_backref}, + {"Carg", lp_argcapture}, + {"Cp", lp_poscapture}, + {"Cs", lp_substcapture}, + {"Ct", lp_tablecapture}, + {"Cf", lp_foldcapture}, + {"Cg", lp_groupcapture}, + {"P", lp_P}, + {"S", lp_set}, + {"R", lp_range}, + {"locale", lp_locale}, + {"version", lp_version}, + {"setmaxstack", lp_setmax}, + {"type", lp_type}, + {NULL, NULL} +}; + + +static struct luaL_Reg metareg[] = { + {"__mul", lp_seq}, + {"__add", lp_choice}, + {"__pow", lp_star}, + {"__gc", lp_gc}, + {"__len", lp_and}, + {"__div", lp_divcapture}, + {"__unm", lp_not}, + {"__sub", lp_sub}, + {NULL, NULL} +}; + + +int luaopen_lpeg (lua_State *L); +int luaopen_lpeg (lua_State *L) { + luaL_newmetatable(L, PATTERN_T); + lua_pushnumber(L, MAXBACK); /* initialize maximum backtracking */ + lua_setfield(L, LUA_REGISTRYINDEX, MAXSTACKIDX); + luaL_setfuncs(L, metareg, 0); + luaL_newlib(L, pattreg); + lua_pushvalue(L, -1); + lua_setfield(L, -3, "__index"); + return 1; +} + +/* }====================================================== */ diff --git a/luaclib/src/lpeg/lptree.h b/luaclib/src/lpeg/lptree.h new file mode 100644 index 00000000..25906d5f --- /dev/null +++ b/luaclib/src/lpeg/lptree.h @@ -0,0 +1,82 @@ +/* +** $Id: lptree.h $ +*/ + +#if !defined(lptree_h) +#define lptree_h + + +#include "lptypes.h" + + +/* +** types of trees +*/ +typedef enum TTag { + TChar = 0, /* 'n' = char */ + TSet, /* the set is stored in next CHARSETSIZE bytes */ + TAny, + TTrue, + TFalse, + TRep, /* 'sib1'* */ + TSeq, /* 'sib1' 'sib2' */ + TChoice, /* 'sib1' / 'sib2' */ + TNot, /* !'sib1' */ + TAnd, /* &'sib1' */ + TCall, /* ktable[key] is rule's key; 'sib2' is rule being called */ + TOpenCall, /* ktable[key] is rule's key */ + TRule, /* ktable[key] is rule's key (but key == 0 for unused rules); + 'sib1' is rule's pattern; + 'sib2' is next rule; 'cap' is rule's sequential number */ + TGrammar, /* 'sib1' is initial (and first) rule */ + TBehind, /* 'sib1' is pattern, 'n' is how much to go back */ + TCapture, /* captures: 'cap' is kind of capture (enum 'CapKind'); + ktable[key] is Lua value associated with capture; + 'sib1' is capture body */ + TRunTime /* run-time capture: 'key' is Lua function; + 'sib1' is capture body */ +} TTag; + + +/* +** Tree trees +** The first child of a tree (if there is one) is immediately after +** the tree. A reference to a second child (ps) is its position +** relative to the position of the tree itself. +*/ +typedef struct TTree { + byte tag; + byte cap; /* kind of capture (if it is a capture) */ + unsigned short key; /* key in ktable for Lua data (0 if no key) */ + union { + int ps; /* occasional second child */ + int n; /* occasional counter */ + } u; +} TTree; + + +/* +** A complete pattern has its tree plus, if already compiled, +** its corresponding code +*/ +typedef struct Pattern { + union Instruction *code; + int codesize; + TTree tree[1]; +} Pattern; + + +/* number of children for each tree */ +extern const byte numsiblings[]; + +/* access to children */ +#define sib1(t) ((t) + 1) +#define sib2(t) ((t) + (t)->u.ps) + + + + + + +#endif + diff --git a/luaclib/src/lpeg/lptypes.h b/luaclib/src/lpeg/lptypes.h new file mode 100644 index 00000000..1d9d59f6 --- /dev/null +++ b/luaclib/src/lpeg/lptypes.h @@ -0,0 +1,145 @@ +/* +** $Id: lptypes.h $ +** LPeg - PEG pattern matching for Lua +** Copyright 2007-2019, Lua.org & PUC-Rio (see 'lpeg.html' for license) +** written by Roberto Ierusalimschy +*/ + +#if !defined(lptypes_h) +#define lptypes_h + + +#include +#include + +#include "lua.h" + + +#define VERSION "1.0.2" + + +#define PATTERN_T "lpeg-pattern" +#define MAXSTACKIDX "lpeg-maxstack" + + +/* +** compatibility with Lua 5.1 +*/ +#if (LUA_VERSION_NUM == 501) + +#define lp_equal lua_equal + +#define lua_getuservalue lua_getfenv +#define lua_setuservalue lua_setfenv + +#define lua_rawlen lua_objlen + +#define luaL_setfuncs(L,f,n) luaL_register(L,NULL,f) +#define luaL_newlib(L,f) luaL_register(L,"lpeg",f) + +#endif + + +#if !defined(lp_equal) +#define lp_equal(L,idx1,idx2) lua_compare(L,(idx1),(idx2),LUA_OPEQ) +#endif + + +/* default maximum size for call/backtrack stack */ +#if !defined(MAXBACK) +#define MAXBACK 400 +#endif + + +/* maximum number of rules in a grammar (limited by 'unsigned char') */ +#if !defined(MAXRULES) +#define MAXRULES 250 +#endif + + + +/* initial size for capture's list */ +#define INITCAPSIZE 32 + + +/* index, on Lua stack, for subject */ +#define SUBJIDX 2 + +/* number of fixed arguments to 'match' (before capture arguments) */ +#define FIXEDARGS 3 + +/* index, on Lua stack, for capture list */ +#define caplistidx(ptop) ((ptop) + 2) + +/* index, on Lua stack, for pattern's ktable */ +#define ktableidx(ptop) ((ptop) + 3) + +/* index, on Lua stack, for backtracking stack */ +#define stackidx(ptop) ((ptop) + 4) + + + +typedef unsigned char byte; + + +#define BITSPERCHAR 8 + +#define CHARSETSIZE ((UCHAR_MAX/BITSPERCHAR) + 1) + + + +typedef struct Charset { + byte cs[CHARSETSIZE]; +} Charset; + + + +#define loopset(v,b) { int v; for (v = 0; v < CHARSETSIZE; v++) {b;} } + +/* access to charset */ +#define treebuffer(t) ((byte *)((t) + 1)) + +/* number of slots needed for 'n' bytes */ +#define bytes2slots(n) (((n) - 1) / sizeof(TTree) + 1) + +/* set 'b' bit in charset 'cs' */ +#define setchar(cs,b) ((cs)[(b) >> 3] |= (1 << ((b) & 7))) + + +/* +** in capture instructions, 'kind' of capture and its offset are +** packed in field 'aux', 4 bits for each +*/ +#define getkind(op) ((op)->i.aux & 0xF) +#define getoff(op) (((op)->i.aux >> 4) & 0xF) +#define joinkindoff(k,o) ((k) | ((o) << 4)) + +#define MAXOFF 0xF +#define MAXAUX 0xFF + + +/* maximum number of bytes to look behind */ +#define MAXBEHIND MAXAUX + + +/* maximum size (in elements) for a pattern */ +#define MAXPATTSIZE (SHRT_MAX - 10) + + +/* size (in elements) for an instruction plus extra l bytes */ +#define instsize(l) (((l) + sizeof(Instruction) - 1)/sizeof(Instruction) + 1) + + +/* size (in elements) for a ISet instruction */ +#define CHARSETINSTSIZE instsize(CHARSETSIZE) + +/* size (in elements) for a IFunc instruction */ +#define funcinstsize(p) ((p)->i.aux + 2) + + + +#define testchar(st,c) (((int)(st)[((c) >> 3)] & (1 << ((c) & 7)))) + + +#endif + diff --git a/luaclib/src/lpeg/lpvm.c b/luaclib/src/lpeg/lpvm.c new file mode 100644 index 00000000..516f2cca --- /dev/null +++ b/luaclib/src/lpeg/lpvm.c @@ -0,0 +1,369 @@ +/* +** $Id: lpvm.c $ +** Copyright 2007, Lua.org & PUC-Rio (see 'lpeg.html' for license) +*/ + +#include + +#include "lpcap.h" +#include "lptypes.h" +#include "lpvm.h" +#include "lpprint.h" + + +/* initial size for call/backtrack stack */ +#if !defined(INITBACK) +#define INITBACK MAXBACK +#endif + + +#define getoffset(p) (((p) + 1)->offset) + +static const Instruction giveup = {{IGiveup, 0, 0}}; + + +/* +** {====================================================== +** Virtual Machine +** ======================================================= +*/ + + +typedef struct Stack { + const char *s; /* saved position (or NULL for calls) */ + const Instruction *p; /* next instruction */ + int caplevel; +} Stack; + + +#define getstackbase(L, ptop) ((Stack *)lua_touserdata(L, stackidx(ptop))) + + +/* +** Ensures the size of array 'capture' (with size '*capsize' and +** 'captop' elements being used) is enough to accomodate 'n' extra +** elements plus one. (Because several opcodes add stuff to the capture +** array, it is simpler to ensure the array always has at least one free +** slot upfront and check its size later.) +*/ +static Capture *growcap (lua_State *L, Capture *capture, int *capsize, + int captop, int n, int ptop) { + if (*capsize - captop > n) + return capture; /* no need to grow array */ + else { /* must grow */ + Capture *newc; + int newsize = captop + n + 1; /* minimum size needed */ + if (newsize < INT_MAX/((int)sizeof(Capture) * 2)) + newsize *= 2; /* twice that size, if not too big */ + else if (newsize >= INT_MAX/((int)sizeof(Capture))) + luaL_error(L, "too many captures"); + newc = (Capture *)lua_newuserdata(L, newsize * sizeof(Capture)); + memcpy(newc, capture, captop * sizeof(Capture)); + *capsize = newsize; + lua_replace(L, caplistidx(ptop)); + return newc; + } +} + + +/* +** Double the size of the stack +*/ +static Stack *doublestack (lua_State *L, Stack **stacklimit, int ptop) { + Stack *stack = getstackbase(L, ptop); + Stack *newstack; + int n = *stacklimit - stack; /* current stack size */ + int max, newn; + lua_getfield(L, LUA_REGISTRYINDEX, MAXSTACKIDX); + max = lua_tointeger(L, -1); /* maximum allowed size */ + lua_pop(L, 1); + if (n >= max) /* already at maximum size? */ + luaL_error(L, "backtrack stack overflow (current limit is %d)", max); + newn = 2 * n; /* new size */ + if (newn > max) newn = max; + newstack = (Stack *)lua_newuserdata(L, newn * sizeof(Stack)); + memcpy(newstack, stack, n * sizeof(Stack)); + lua_replace(L, stackidx(ptop)); + *stacklimit = newstack + newn; + return newstack + n; /* return next position */ +} + + +/* +** Interpret the result of a dynamic capture: false -> fail; +** true -> keep current position; number -> next position. +** Return new subject position. 'fr' is stack index where +** is the result; 'curr' is current subject position; 'limit' +** is subject's size. +*/ +static int resdyncaptures (lua_State *L, int fr, int curr, int limit) { + lua_Integer res; + if (!lua_toboolean(L, fr)) { /* false value? */ + lua_settop(L, fr - 1); /* remove results */ + return -1; /* and fail */ + } + else if (lua_isboolean(L, fr)) /* true? */ + res = curr; /* keep current position */ + else { + res = lua_tointeger(L, fr) - 1; /* new position */ + if (res < curr || res > limit) + luaL_error(L, "invalid position returned by match-time capture"); + } + lua_remove(L, fr); /* remove first result (offset) */ + return res; +} + + +/* +** Add capture values returned by a dynamic capture to the list +** 'capture', nested inside a group. 'fd' indexes the first capture +** value, 'n' is the number of values (at least 1). The open group +** capture is already in 'capture', before the place for the new entries. +*/ +static void adddyncaptures (const char *s, Capture *capture, int n, int fd) { + int i; + assert(capture[-1].kind == Cgroup && capture[-1].siz == 0); + capture[-1].idx = 0; /* make group capture an anonymous group */ + for (i = 0; i < n; i++) { /* add runtime captures */ + capture[i].kind = Cruntime; + capture[i].siz = 1; /* mark it as closed */ + capture[i].idx = fd + i; /* stack index of capture value */ + capture[i].s = s; + } + capture[n].kind = Cclose; /* close group */ + capture[n].siz = 1; + capture[n].s = s; +} + + +/* +** Remove dynamic captures from the Lua stack (called in case of failure) +*/ +static int removedyncap (lua_State *L, Capture *capture, + int level, int last) { + int id = finddyncap(capture + level, capture + last); /* index of 1st cap. */ + int top = lua_gettop(L); + if (id == 0) return 0; /* no dynamic captures? */ + lua_settop(L, id - 1); /* remove captures */ + return top - id + 1; /* number of values removed */ +} + + +/* +** Opcode interpreter +*/ +const char *match (lua_State *L, const char *o, const char *s, const char *e, + Instruction *op, Capture *capture, int ptop) { + Stack stackbase[INITBACK]; + Stack *stacklimit = stackbase + INITBACK; + Stack *stack = stackbase; /* point to first empty slot in stack */ + int capsize = INITCAPSIZE; + int captop = 0; /* point to first empty slot in captures */ + int ndyncap = 0; /* number of dynamic captures (in Lua stack) */ + const Instruction *p = op; /* current instruction */ + stack->p = &giveup; stack->s = s; stack->caplevel = 0; stack++; + lua_pushlightuserdata(L, stackbase); + for (;;) { +// #if defined(DEBUG) +// printf("-------------------------------------\n"); +// printcaplist(capture, capture + captop); +// printf("s: |%s| stck:%d, dyncaps:%d, caps:%d ", +// s, (int)(stack - getstackbase(L, ptop)), ndyncap, captop); +// printinst(op, p); +// #endif + assert(stackidx(ptop) + ndyncap == lua_gettop(L) && ndyncap <= captop); + switch ((Opcode)p->i.code) { + case IEnd: { + assert(stack == getstackbase(L, ptop) + 1); + capture[captop].kind = Cclose; + capture[captop].s = NULL; + return s; + } + case IGiveup: { + assert(stack == getstackbase(L, ptop)); + return NULL; + } + case IRet: { + assert(stack > getstackbase(L, ptop) && (stack - 1)->s == NULL); + p = (--stack)->p; + continue; + } + case IAny: { + if (s < e) { p++; s++; } + else goto fail; + continue; + } + case ITestAny: { + if (s < e) p += 2; + else p += getoffset(p); + continue; + } + case IChar: { + if ((byte)*s == p->i.aux && s < e) { p++; s++; } + else goto fail; + continue; + } + case ITestChar: { + if ((byte)*s == p->i.aux && s < e) p += 2; + else p += getoffset(p); + continue; + } + case ISet: { + int c = (byte)*s; + if (testchar((p+1)->buff, c) && s < e) + { p += CHARSETINSTSIZE; s++; } + else goto fail; + continue; + } + case ITestSet: { + int c = (byte)*s; + if (testchar((p + 2)->buff, c) && s < e) + p += 1 + CHARSETINSTSIZE; + else p += getoffset(p); + continue; + } + case IBehind: { + int n = p->i.aux; + if (n > s - o) goto fail; + s -= n; p++; + continue; + } + case ISpan: { + for (; s < e; s++) { + int c = (byte)*s; + if (!testchar((p+1)->buff, c)) break; + } + p += CHARSETINSTSIZE; + continue; + } + case IJmp: { + p += getoffset(p); + continue; + } + case IChoice: { + if (stack == stacklimit) + stack = doublestack(L, &stacklimit, ptop); + stack->p = p + getoffset(p); + stack->s = s; + stack->caplevel = captop; + stack++; + p += 2; + continue; + } + case ICall: { + if (stack == stacklimit) + stack = doublestack(L, &stacklimit, ptop); + stack->s = NULL; + stack->p = p + 2; /* save return address */ + stack++; + p += getoffset(p); + continue; + } + case ICommit: { + assert(stack > getstackbase(L, ptop) && (stack - 1)->s != NULL); + stack--; + p += getoffset(p); + continue; + } + case IPartialCommit: { + assert(stack > getstackbase(L, ptop) && (stack - 1)->s != NULL); + (stack - 1)->s = s; + (stack - 1)->caplevel = captop; + p += getoffset(p); + continue; + } + case IBackCommit: { + assert(stack > getstackbase(L, ptop) && (stack - 1)->s != NULL); + s = (--stack)->s; + captop = stack->caplevel; + p += getoffset(p); + continue; + } + case IFailTwice: + assert(stack > getstackbase(L, ptop)); + stack--; + /* go through */ + case IFail: + fail: { /* pattern failed: try to backtrack */ + do { /* remove pending calls */ + assert(stack > getstackbase(L, ptop)); + s = (--stack)->s; + } while (s == NULL); + if (ndyncap > 0) /* is there matchtime captures? */ + ndyncap -= removedyncap(L, capture, stack->caplevel, captop); + captop = stack->caplevel; + p = stack->p; +#if defined(DEBUG) + printf("**FAIL**\n"); +#endif + continue; + } + case ICloseRunTime: { + CapState cs; + int rem, res, n; + int fr = lua_gettop(L) + 1; /* stack index of first result */ + cs.reclevel = 0; cs.L = L; + cs.s = o; cs.ocap = capture; cs.ptop = ptop; + n = runtimecap(&cs, capture + captop, s, &rem); /* call function */ + captop -= n; /* remove nested captures */ + ndyncap -= rem; /* update number of dynamic captures */ + fr -= rem; /* 'rem' items were popped from Lua stack */ + res = resdyncaptures(L, fr, s - o, e - o); /* get result */ + if (res == -1) /* fail? */ + goto fail; + s = o + res; /* else update current position */ + n = lua_gettop(L) - fr + 1; /* number of new captures */ + ndyncap += n; /* update number of dynamic captures */ + if (n == 0) /* no new captures? */ + captop--; /* remove open group */ + else { /* new captures; keep original open group */ + if (fr + n >= SHRT_MAX) + luaL_error(L, "too many results in match-time capture"); + /* add new captures + close group to 'capture' list */ + capture = growcap(L, capture, &capsize, captop, n + 1, ptop); + adddyncaptures(s, capture + captop, n, fr); + captop += n + 1; /* new captures + close group */ + } + p++; + continue; + } + case ICloseCapture: { + const char *s1 = s; + assert(captop > 0); + /* if possible, turn capture into a full capture */ + if (capture[captop - 1].siz == 0 && + s1 - capture[captop - 1].s < UCHAR_MAX) { + capture[captop - 1].siz = s1 - capture[captop - 1].s + 1; + p++; + continue; + } + else { + capture[captop].siz = 1; /* mark entry as closed */ + capture[captop].s = s; + goto pushcapture; + } + } + case IOpenCapture: + capture[captop].siz = 0; /* mark entry as open */ + capture[captop].s = s; + goto pushcapture; + case IFullCapture: + capture[captop].siz = getoff(p) + 1; /* save capture size */ + capture[captop].s = s - getoff(p); + /* goto pushcapture; */ + pushcapture: { + capture[captop].idx = p->i.key; + capture[captop].kind = getkind(p); + captop++; + capture = growcap(L, capture, &capsize, captop, 0, ptop); + p++; + continue; + } + default: assert(0); return NULL; + } + } +} + +/* }====================================================== */ + + diff --git a/luaclib/src/lpeg/lpvm.h b/luaclib/src/lpeg/lpvm.h new file mode 100644 index 00000000..69ec33dc --- /dev/null +++ b/luaclib/src/lpeg/lpvm.h @@ -0,0 +1,58 @@ +/* +** $Id: lpvm.h $ +*/ + +#if !defined(lpvm_h) +#define lpvm_h + +#include "lpcap.h" + + +/* Virtual Machine's instructions */ +typedef enum Opcode { + IAny, /* if no char, fail */ + IChar, /* if char != aux, fail */ + ISet, /* if char not in buff, fail */ + ITestAny, /* in no char, jump to 'offset' */ + ITestChar, /* if char != aux, jump to 'offset' */ + ITestSet, /* if char not in buff, jump to 'offset' */ + ISpan, /* read a span of chars in buff */ + IBehind, /* walk back 'aux' characters (fail if not possible) */ + IRet, /* return from a rule */ + IEnd, /* end of pattern */ + IChoice, /* stack a choice; next fail will jump to 'offset' */ + IJmp, /* jump to 'offset' */ + ICall, /* call rule at 'offset' */ + IOpenCall, /* call rule number 'key' (must be closed to a ICall) */ + ICommit, /* pop choice and jump to 'offset' */ + IPartialCommit, /* update top choice to current position and jump */ + IBackCommit, /* "fails" but jump to its own 'offset' */ + IFailTwice, /* pop one choice and then fail */ + IFail, /* go back to saved state on choice and jump to saved offset */ + IGiveup, /* internal use */ + IFullCapture, /* complete capture of last 'off' chars */ + IOpenCapture, /* start a capture */ + ICloseCapture, + ICloseRunTime +} Opcode; + + + +typedef union Instruction { + struct Inst { + byte code; + byte aux; + short key; + } i; + int offset; + byte buff[1]; +} Instruction; + + +void printpatt (Instruction *p, int n); +const char *match (lua_State *L, const char *o, const char *s, const char *e, + Instruction *op, Capture *capture, int ptop); + + +#endif + diff --git a/luaclib/src/lpeg/makefile b/luaclib/src/lpeg/makefile new file mode 100644 index 00000000..8584447a --- /dev/null +++ b/luaclib/src/lpeg/makefile @@ -0,0 +1,18 @@ +.PHONY : build rebuild clean + +default : + @echo "=======================================" + @echo "Please use 'make build' command to build it.." + @echo "Please use 'make rebuild' command to build it.." + @echo "Please use 'make clean' command to clean all." + @echo "=======================================" + +CC = cc +INCLUDES += -I../../../src -I/usr/local/include +LIBS = -L../ -L../../ -L../../../ -L/usr/local/lib +CFLAGS = -O3 -shared -fPIC +DLL = -lcore + +build: + @$(CC) -o lpeg.so lpcap.c lpcode.c lpprint.c lptree.c lpvm.c $(CFLAGS) $(INCLUDES) $(LIBS) $(DLL) + @mv *.so ../../ diff --git a/luaclib/src/lpeg/re.html b/luaclib/src/lpeg/re.html new file mode 100644 index 00000000..ad60d509 --- /dev/null +++ b/luaclib/src/lpeg/re.html @@ -0,0 +1,494 @@ + + + + LPeg.re - Regex syntax for LPEG + + + + + + + +
+ +
+ +
LPeg.re
+
+ Regex syntax for LPEG +
+
+ +
+ + + +
+ +

The re Module

+ +

+The re module +(provided by file re.lua in the distribution) +supports a somewhat conventional regex syntax +for pattern usage within LPeg. +

+ +

+The next table summarizes re's syntax. +A p represents an arbitrary pattern; +num represents a number ([0-9]+); +name represents an identifier +([a-zA-Z][a-zA-Z0-9_]*). +Constructions are listed in order of decreasing precedence. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SyntaxDescription
( p ) grouping
'string' literal string
"string" literal string
[class] character class
. any character
%namepattern defs[name] or a pre-defined pattern
namenon terminal
<name>non terminal
{} position capture
{ p } simple capture
{: p :} anonymous group capture
{:name: p :} named group capture
{~ p ~} substitution capture
{| p |} table capture
=name back reference +
p ? optional match
p * zero or more repetitions
p + one or more repetitions
p^num exactly n repetitions
p^+numat least n repetitions
p^-numat most n repetitions
p -> 'string' string capture
p -> "string" string capture
p -> num numbered capture
p -> name function/query/string capture +equivalent to p / defs[name]
p => name match-time capture +equivalent to lpeg.Cmt(p, defs[name])
p ~> name fold capture +equivalent to lpeg.Cf(p, defs[name])
& p and predicate
! p not predicate
p1 p2 concatenation
p1 / p2 ordered choice
(name <- p)+ grammar
+

+Any space appearing in a syntax description can be +replaced by zero or more space characters and Lua-style comments +(-- until end of line). +

+ +

+Character classes define sets of characters. +An initial ^ complements the resulting set. +A range x-y includes in the set +all characters with codes between the codes of x and y. +A pre-defined class %name includes all +characters of that class. +A simple character includes itself in the set. +The only special characters inside a class are ^ +(special only if it is the first character); +] +(can be included in the set as the first character, +after the optional ^); +% (special only if followed by a letter); +and - +(can be included in the set as the first or the last character). +

+ +

+Currently the pre-defined classes are similar to those from the +Lua's string library +(%a for letters, +%A for non letters, etc.). +There is also a class %nl +containing only the newline character, +which is particularly handy for grammars written inside long strings, +as long strings do not interpret escape sequences like \n. +

+ + +

Functions

+ +

re.compile (string, [, defs])

+

+Compiles the given string and +returns an equivalent LPeg pattern. +The given string may define either an expression or a grammar. +The optional defs table provides extra Lua values +to be used by the pattern. +

+ +

re.find (subject, pattern [, init])

+

+Searches the given pattern in the given subject. +If it finds a match, +returns the index where this occurrence starts and +the index where it ends. +Otherwise, returns nil. +

+ +

+An optional numeric argument init makes the search +starts at that position in the subject string. +As usual in Lua libraries, +a negative value counts from the end. +

+ +

re.gsub (subject, pattern, replacement)

+

+Does a global substitution, +replacing all occurrences of pattern +in the given subject by replacement. + +

re.match (subject, pattern)

+

+Matches the given pattern against the given subject, +returning all captures. +

+ +

re.updatelocale ()

+

+Updates the pre-defined character classes to the current locale. +

+ + +

Some Examples

+ +

A complete simple program

+

+The next code shows a simple complete Lua program using +the re module: +

+
+local re = require"re"
+
+-- find the position of the first numeral in a string
+print(re.find("the number 423 is odd", "[0-9]+"))  --> 12    14
+
+-- returns all words in a string
+print(re.match("the number 423 is odd", "({%a+} / .)*"))
+--> the    number    is    odd
+
+-- returns the first numeral in a string
+print(re.match("the number 423 is odd", "s <- {%d+} / . s"))
+--> 423
+
+print(re.gsub("hello World", "[aeiou]", "."))
+--> h.ll. W.rld
+
+ + +

Balanced parentheses

+

+The following call will produce the same pattern produced by the +Lua expression in the +balanced parentheses example: +

+
+b = re.compile[[  balanced <- "(" ([^()] / balanced)* ")"  ]]
+
+ +

String reversal

+

+The next example reverses a string: +

+
+rev = re.compile[[ R <- (!.) -> '' / ({.} R) -> '%2%1']]
+print(rev:match"0123456789")   --> 9876543210
+
+ +

CSV decoder

+

+The next example replicates the CSV decoder: +

+
+record = re.compile[[
+  record <- {| field (',' field)* |} (%nl / !.)
+  field <- escaped / nonescaped
+  nonescaped <- { [^,"%nl]* }
+  escaped <- '"' {~ ([^"] / '""' -> '"')* ~} '"'
+]]
+
+ +

Lua's long strings

+

+The next example matches Lua long strings: +

+
+c = re.compile([[
+  longstring <- ('[' {:eq: '='* :} '[' close)
+  close <- ']' =eq ']' / . close
+]])
+
+print(c:match'[==[]]===]]]]==]===[]')   --> 17
+
+ +

Abstract Syntax Trees

+

+This example shows a simple way to build an +abstract syntax tree (AST) for a given grammar. +To keep our example simple, +let us consider the following grammar +for lists of names: +

+
+p = re.compile[[
+      listname <- (name s)*
+      name <- [a-z][a-z]*
+      s <- %s*
+]]
+
+

+Now, we will add captures to build a corresponding AST. +As a first step, the pattern will build a table to +represent each non terminal; +terminals will be represented by their corresponding strings: +

+
+c = re.compile[[
+      listname <- {| (name s)* |}
+      name <- {| {[a-z][a-z]*} |}
+      s <- %s*
+]]
+
+

+Now, a match against "hi hello bye" +results in the table +{{"hi"}, {"hello"}, {"bye"}}. +

+

+For such a simple grammar, +this AST is more than enough; +actually, the tables around each single name +are already overkilling. +More complex grammars, +however, may need some more structure. +Specifically, +it would be useful if each table had +a tag field telling what non terminal +that table represents. +We can add such a tag using +named group captures: +

+
+x = re.compile[[
+      listname <- {| {:tag: '' -> 'list':} (name s)* |}
+      name <- {| {:tag: '' -> 'id':} {[a-z][a-z]*} |}
+      s <- ' '*
+]]
+
+

+With these group captures, +a match against "hi hello bye" +results in the following table: +

+
+{tag="list",
+  {tag="id", "hi"},
+  {tag="id", "hello"},
+  {tag="id", "bye"}
+}
+
+ + +

Indented blocks

+

+This example breaks indented blocks into tables, +respecting the indentation: +

+
+p = re.compile[[
+  block <- {| {:ident:' '*:} line
+           ((=ident !' ' line) / &(=ident ' ') block)* |}
+  line <- {[^%nl]*} %nl
+]]
+
+

+As an example, +consider the following text: +

+
+t = p:match[[
+first line
+  subline 1
+  subline 2
+second line
+third line
+  subline 3.1
+    subline 3.1.1
+  subline 3.2
+]]
+
+

+The resulting table t will be like this: +

+
+   {'first line'; {'subline 1'; 'subline 2'; ident = '  '};
+    'second line';
+    'third line'; { 'subline 3.1'; {'subline 3.1.1'; ident = '    '};
+                    'subline 3.2'; ident = '  '};
+    ident = ''}
+
+ +

Macro expander

+

+This example implements a simple macro expander. +Macros must be defined as part of the pattern, +following some simple rules: +

+
+p = re.compile[[
+      text <- {~ item* ~}
+      item <- macro / [^()] / '(' item* ')'
+      arg <- ' '* {~ (!',' item)* ~}
+      args <- '(' arg (',' arg)* ')'
+      -- now we define some macros
+      macro <- ('apply' args) -> '%1(%2)'
+             / ('add' args) -> '%1 + %2'
+             / ('mul' args) -> '%1 * %2'
+]]
+
+print(p:match"add(mul(a,b), apply(f,x))")   --> a * b + f(x)
+
+

+A text is a sequence of items, +wherein we apply a substitution capture to expand any macros. +An item is either a macro, +any character different from parentheses, +or a parenthesized expression. +A macro argument (arg) is a sequence +of items different from a comma. +(Note that a comma may appear inside an item, +e.g., inside a parenthesized expression.) +Again we do a substitution capture to expand any macro +in the argument before expanding the outer macro. +args is a list of arguments separated by commas. +Finally we define the macros. +Each macro is a string substitution; +it replaces the macro name and its arguments by its corresponding string, +with each %n replaced by the n-th argument. +

+ +

Patterns

+

+This example shows the complete syntax +of patterns accepted by re. +

+
+p = [=[
+
+pattern         <- exp !.
+exp             <- S (grammar / alternative)
+
+alternative     <- seq ('/' S seq)*
+seq             <- prefix*
+prefix          <- '&' S prefix / '!' S prefix / suffix
+suffix          <- primary S (([+*?]
+                            / '^' [+-]? num
+                            / '->' S (string / '{}' / name)
+                            / '=>' S name) S)*
+
+primary         <- '(' exp ')' / string / class / defined
+                 / '{:' (name ':')? exp ':}'
+                 / '=' name
+                 / '{}'
+                 / '{~' exp '~}'
+                 / '{' exp '}'
+                 / '.'
+                 / name S !arrow
+                 / '<' name '>'          -- old-style non terminals
+
+grammar         <- definition+
+definition      <- name S arrow exp
+
+class           <- '[' '^'? item (!']' item)* ']'
+item            <- defined / range / .
+range           <- . '-' [^]]
+
+S               <- (%s / '--' [^%nl]*)*   -- spaces and comments
+name            <- [A-Za-z][A-Za-z0-9_]*
+arrow           <- '<-'
+num             <- [0-9]+
+string          <- '"' [^"]* '"' / "'" [^']* "'"
+defined         <- '%' name
+
+]=]
+
+print(re.match(p, p))   -- a self description must match itself
+
+ + + +

License

+ +

+Copyright © 2008-2015 Lua.org, PUC-Rio. +

+

+Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), +to deal in the Software without restriction, +including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, +and to permit persons to whom the Software is +furnished to do so, +subject to the following conditions: +

+ +

+The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. +

+ +

+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +

+ +
+ +
+ +
+ + + diff --git a/luaclib/src/lpeg/re.lua b/luaclib/src/lpeg/re.lua new file mode 100644 index 00000000..40cdbf09 --- /dev/null +++ b/luaclib/src/lpeg/re.lua @@ -0,0 +1,265 @@ +-- $Id: re.lua $ + +-- imported functions and modules +local tonumber, type, print, error = tonumber, type, print, error +local setmetatable = setmetatable +local m = require"lpeg" + +-- 'm' will be used to parse expressions, and 'mm' will be used to +-- create expressions; that is, 're' runs on 'm', creating patterns +-- on 'mm' +local mm = m + +-- pattern's metatable +local mt = getmetatable(mm.P(0)) + + + +-- No more global accesses after this point +local version = _VERSION +if version == "Lua 5.2" then _ENV = nil end + + +local any = m.P(1) + + +-- Pre-defined names +local Predef = { nl = m.P"\n" } + + +local mem +local fmem +local gmem + + +local function updatelocale () + mm.locale(Predef) + Predef.a = Predef.alpha + Predef.c = Predef.cntrl + Predef.d = Predef.digit + Predef.g = Predef.graph + Predef.l = Predef.lower + Predef.p = Predef.punct + Predef.s = Predef.space + Predef.u = Predef.upper + Predef.w = Predef.alnum + Predef.x = Predef.xdigit + Predef.A = any - Predef.a + Predef.C = any - Predef.c + Predef.D = any - Predef.d + Predef.G = any - Predef.g + Predef.L = any - Predef.l + Predef.P = any - Predef.p + Predef.S = any - Predef.s + Predef.U = any - Predef.u + Predef.W = any - Predef.w + Predef.X = any - Predef.x + mem = {} -- restart memoization + fmem = {} + gmem = {} + local mt = {__mode = "v"} + setmetatable(mem, mt) + setmetatable(fmem, mt) + setmetatable(gmem, mt) +end + + +updatelocale() + + + +local I = m.P(function (s,i) print(i, s:sub(1, i-1)); return i end) + + +local function patt_error (s, i) + local msg = (#s < i + 20) and s:sub(i) + or s:sub(i,i+20) .. "..." + msg = ("pattern error near '%s'"):format(msg) + error(msg, 2) +end + +local function mult (p, n) + local np = mm.P(true) + while n >= 1 do + if n%2 >= 1 then np = np * p end + p = p * p + n = n/2 + end + return np +end + +local function equalcap (s, i, c) + if type(c) ~= "string" then return nil end + local e = #c + i + if s:sub(i, e - 1) == c then return e else return nil end +end + + +local S = (Predef.space + "--" * (any - Predef.nl)^0)^0 + +local name = m.R("AZ", "az", "__") * m.R("AZ", "az", "__", "09")^0 + +local arrow = S * "<-" + +local seq_follow = m.P"/" + ")" + "}" + ":}" + "~}" + "|}" + (name * arrow) + -1 + +name = m.C(name) + + +-- a defined name only have meaning in a given environment +local Def = name * m.Carg(1) + + +local function getdef (id, defs) + local c = defs and defs[id] + if not c then error("undefined name: " .. id) end + return c +end + +-- match a name and return a group of its corresponding definition +-- and 'f' (to be folded in 'Suffix') +local function defwithfunc (f) + return m.Cg(Def / getdef * m.Cc(f)) +end + + +local num = m.C(m.R"09"^1) * S / tonumber + +local String = "'" * m.C((any - "'")^0) * "'" + + '"' * m.C((any - '"')^0) * '"' + + +local defined = "%" * Def / function (c,Defs) + local cat = Defs and Defs[c] or Predef[c] + if not cat then error ("name '" .. c .. "' undefined") end + return cat +end + +local Range = m.Cs(any * (m.P"-"/"") * (any - "]")) / mm.R + +local item = (defined + Range + m.C(any)) / m.P + +local Class = + "[" + * (m.C(m.P"^"^-1)) -- optional complement symbol + * m.Cf(item * (item - "]")^0, mt.__add) / + function (c, p) return c == "^" and any - p or p end + * "]" + +local function adddef (t, k, exp) + if t[k] then + error("'"..k.."' already defined as a rule") + else + t[k] = exp + end + return t +end + +local function firstdef (n, r) return adddef({n}, n, r) end + + +local function NT (n, b) + if not b then + error("rule '"..n.."' used outside a grammar") + else return mm.V(n) + end +end + + +local exp = m.P{ "Exp", + Exp = S * ( m.V"Grammar" + + m.Cf(m.V"Seq" * ("/" * S * m.V"Seq")^0, mt.__add) ); + Seq = m.Cf(m.Cc(m.P"") * m.V"Prefix"^0 , mt.__mul) + * (#seq_follow + patt_error); + Prefix = "&" * S * m.V"Prefix" / mt.__len + + "!" * S * m.V"Prefix" / mt.__unm + + m.V"Suffix"; + Suffix = m.Cf(m.V"Primary" * S * + ( ( m.P"+" * m.Cc(1, mt.__pow) + + m.P"*" * m.Cc(0, mt.__pow) + + m.P"?" * m.Cc(-1, mt.__pow) + + "^" * ( m.Cg(num * m.Cc(mult)) + + m.Cg(m.C(m.S"+-" * m.R"09"^1) * m.Cc(mt.__pow)) + ) + + "->" * S * ( m.Cg((String + num) * m.Cc(mt.__div)) + + m.P"{}" * m.Cc(nil, m.Ct) + + defwithfunc(mt.__div) + ) + + "=>" * S * defwithfunc(m.Cmt) + + "~>" * S * defwithfunc(m.Cf) + ) * S + )^0, function (a,b,f) return f(a,b) end ); + Primary = "(" * m.V"Exp" * ")" + + String / mm.P + + Class + + defined + + "{:" * (name * ":" + m.Cc(nil)) * m.V"Exp" * ":}" / + function (n, p) return mm.Cg(p, n) end + + "=" * name / function (n) return mm.Cmt(mm.Cb(n), equalcap) end + + m.P"{}" / mm.Cp + + "{~" * m.V"Exp" * "~}" / mm.Cs + + "{|" * m.V"Exp" * "|}" / mm.Ct + + "{" * m.V"Exp" * "}" / mm.C + + m.P"." * m.Cc(any) + + (name * -arrow + "<" * name * ">") * m.Cb("G") / NT; + Definition = name * arrow * m.V"Exp"; + Grammar = m.Cg(m.Cc(true), "G") * + m.Cf(m.V"Definition" / firstdef * m.Cg(m.V"Definition")^0, + adddef) / mm.P +} + +local pattern = S * m.Cg(m.Cc(false), "G") * exp / mm.P * (-any + patt_error) + + +local function compile (p, defs) + if mm.type(p) == "pattern" then return p end -- already compiled + local cp = pattern:match(p, 1, defs) + if not cp then error("incorrect pattern", 3) end + return cp +end + +local function match (s, p, i) + local cp = mem[p] + if not cp then + cp = compile(p) + mem[p] = cp + end + return cp:match(s, i or 1) +end + +local function find (s, p, i) + local cp = fmem[p] + if not cp then + cp = compile(p) / 0 + cp = mm.P{ mm.Cp() * cp * mm.Cp() + 1 * mm.V(1) } + fmem[p] = cp + end + local i, e = cp:match(s, i or 1) + if i then return i, e - 1 + else return i + end +end + +local function gsub (s, p, rep) + local g = gmem[p] or {} -- ensure gmem[p] is not collected while here + gmem[p] = g + local cp = g[rep] + if not cp then + cp = compile(p) + cp = mm.Cs((cp / rep + 1)^0) + g[rep] = cp + end + return cp:match(s) +end + + +-- exported names +local re = { + compile = compile, + match = match, + find = find, + gsub = gsub, + updatelocale = updatelocale, +} + +return re diff --git a/luaclib/src/lsys.c b/luaclib/src/lsys.c index 133f7c1c..276a0ba8 100644 --- a/luaclib/src/lsys.c +++ b/luaclib/src/lsys.c @@ -1,54 +1,201 @@ #define LUA_LIB -#include "../../src/core.h" +#include +#include + +#define MAX_IPV4 (4294967295L) + +// 提供一个精确到微秒的时间戳 +static int lnow(lua_State *L){ + lua_pushnumber(L, now()); + return 1; +} // 提供一个精确到毫秒的时间戳 -static int -lnow(lua_State *L){ - lua_pushnumber(L, now()); - return 1; -} - -int /* 此方法可用于检查是否为有效ipv4地址*/ -lipv4(lua_State *L){ - const char *IP = lua_tostring(L, 1); - if (!IP) return luaL_error(L, "ipv4 error: 请至少传递一个string类型参数\n"); - if (ipv4(IP)) lua_pushboolean(L, 1); - else lua_pushboolean(L, 0); - return 1; -} - -int /* 此方法可用于检查是否为有效ipv6地址*/ -lipv6(lua_State *L){ - const char *IP = lua_tostring(L, 1); - if (!IP) return luaL_error(L, "ipv6 error: 请至少传递一个string类型参数\n"); - if (ipv6(IP)) lua_pushboolean(L, 1); - else lua_pushboolean(L, 0); - return 1; -} - -int -ldate(lua_State *L){ - const char *fmt = lua_tostring(L, 1); - if (!fmt) return luaL_error(L, "Date: 错误的格式化方法"); - time_t timestamp = lua_tointeger(L, 2); - char fmttime[64]; - time_t t = time(×tamp); - strftime(fmttime, 64, fmt, localtime(&t)); - lua_pushstring(L, fmttime); - return 1; -} - -LUAMOD_API int -luaopen_sys(lua_State *L){ - luaL_checkversion(L); - luaL_Reg sys_libs[] = { - {"now", lnow}, - {"ipv4", lipv4}, - {"ipv6", lipv6}, - {"date", ldate}, - {NULL, NULL} - }; - luaL_newlib(L, sys_libs); - return 1; -} \ No newline at end of file +static int ltime(lua_State *L){ + lua_pushinteger(L, (uint64_t)(now() * 1e3)); + return 1; +} + +/* 此方法可用于检查是否为有效ipv4地址*/ +static int lipv4(lua_State *L){ + size_t str_len = 0; + const char *IP = luaL_checklstring(L, 1, &str_len); + if (!IP || str_len == 0) + return luaL_error(L, "ipv4 error: A parameter of type string is required\n"); + lua_pushboolean(L, ipv4(IP)); + return 1; +} + +/* 此方法可用于检查是否为有效ipv6地址*/ +static int lipv6(lua_State *L){ + size_t str_len = 0; + const char *IP = luaL_checklstring(L, 1, &str_len); + if (!IP || str_len == 0) + return luaL_error(L, "ipv6 error: A parameter of type string is required\n"); + lua_pushboolean(L, ipv6(IP)); + return 1; +} + +/* string 转换为 IPv4 */ +static int lstr2ip(lua_State *L){ + size_t str_len = 0; + const char *IP = luaL_checklstring(L, 1, &str_len); + if (!IP || str_len < 3 || str_len > 15) + return luaL_error(L, "Invalid IP."); + uint32_t addr = 0; + if (inet_pton(AF_INET, IP, (void*)&addr) != 1) + return 0; + lua_pushinteger(L, addr); + return 1; +} + +/* IPv4 转换为 string */ +static int lip2str(lua_State *L){ + lua_Unsigned IP = luaL_checkinteger(L, 1); + if (IP > MAX_IPV4) + return luaL_error(L, "Invalid IP."); + char str[INET_ADDRSTRLEN]; + memset(str, 0x0, INET_ADDRSTRLEN); + if (!inet_ntop(AF_INET, (const void*)&IP, str, INET_ADDRSTRLEN)) + return 0; + lua_pushlstring(L, str, strlen(str)); + return 1; +} + +/* 返回格式化后的时间 */ +static int ldate(lua_State *L){ + size_t str_len = 0; + const char *fmt = luaL_checklstring(L, 1, &str_len); + if (!fmt || str_len == 0) + return luaL_error(L, "Date: Invalid format."); + + time_t timestamp = lua_tointeger(L, 2); + if (0 >= timestamp) + timestamp = time(NULL); + + size_t len = 128 + str_len; + char fmttime[len]; + memset(fmttime, 0x0, len); + int result = strftime(fmttime, len, fmt, localtime(×tamp)); + if (result < 0) + return 0; + lua_pushlstring(L, fmttime, result); + return 1; +} + +/* 返回当前操作系统类型 */ +static int los(lua_State *L){ + lua_pushstring(L, os()); + return 1; +} + +/* 返回主机名 */ +static int lhostname(lua_State *L){ + size_t max_hostaname = 4096; + char *hostname = lua_newuserdata(L, max_hostaname); + memset(hostname, 0x0, max_hostaname); + int len = gethostname(hostname, max_hostaname); + if (0 > len) + return 0; + lua_pushlstring(L, hostname, strlen(hostname)); + return 1; +} + +/* 创建表 */ +static int lnew_tab(lua_State *L){ + // lua_Integer array_size = luaL_checkinteger(L, 1); // array 部分大小 + // lua_Integer hash_size = luaL_checkinteger(L, 2); // hash 部分大小 + lua_createtable(L, luaL_checkinteger(L, 1), luaL_checkinteger(L, 2)); + return 1; +} + +/* 创建表 */ +static int lusage(lua_State *L) { + #include + struct rusage usage; + int ret = getrusage(RUSAGE_SELF , &usage); + if (ret == -1){ + lua_pushnil(L); + lua_pushstring(L, strerror(errno)); + return 2; + } + + lua_createtable(L, 0, 8); + + #define luaL_setki(L, k, i) ({lua_pushstring(L, (k)); lua_pushinteger(L, (i)); lua_rawset(L, -3);}) + #define luaL_setkn(L, k, v) ({lua_pushstring(L, (k)); lua_pushnumber(L, (v)); lua_rawset(L, -3);}) + luaL_setkn(L, "utime", usage.ru_utime.tv_sec + (usage.ru_utime.tv_usec * 1e-6)); + luaL_setkn(L, "ktime", usage.ru_stime.tv_sec + (usage.ru_stime.tv_usec * 1e-6)); + luaL_setki(L, "rss", usage.ru_maxrss); luaL_setki(L, "swap", usage.ru_nswap); + luaL_setki(L, "inblock", usage.ru_inblock); luaL_setki(L, "oublock", usage.ru_oublock); + luaL_setki(L, "hard_page_fault", usage.ru_majflt); luaL_setki(L, "soft_page_fault", usage.ru_minflt); + #undef luaL_setki + #undef luaL_setkn + + return 1; +} + +static int linterface(lua_State *L){ + struct ifaddrs *ifc, *ifc1; + if(getifaddrs(&ifc)) + return 0; + lua_createtable(L, 32, 0); + ifc1 = ifc; + int index = 1; + for(; NULL != ifc; ifc = (*ifc).ifa_next) { + if ((*ifc).ifa_addr && (*ifc).ifa_netmask && (*ifc).ifa_name) { + char ip[64] = {0}; + char mask[64] = {0}; + // IPv4 + if ((*ifc).ifa_addr->sa_family == AF_INET && (*ifc).ifa_netmask->sa_family == AF_INET) { + inet_ntop(AF_INET, &(((struct sockaddr_in*)((*ifc).ifa_addr))->sin_addr), ip, 64); + inet_ntop(AF_INET, &(((struct sockaddr_in*)((*ifc).ifa_netmask))->sin_addr), mask, 64); + if (0 != strncmp("0.0.0.0", ip, strlen(ip)) && 0 != strncmp("0.0.0.0", mask, strlen(mask))){ + lua_createtable(L, 0, 4); + lua_pushliteral(L, "Interface"); lua_pushstring(L, (*ifc).ifa_name); lua_rawset(L, -3); + lua_pushliteral(L, "IP"); lua_pushstring(L, ip); lua_rawset(L, -3); + lua_pushliteral(L, "Mask"); lua_pushstring(L, mask); lua_rawset(L, -3); + lua_pushliteral(L, "Version"); lua_pushliteral(L, "IPv4"); lua_rawset(L, -3); + lua_rawseti(L, -2, index++); + } + } + // IPv6 + if ((*ifc).ifa_addr->sa_family == AF_INET6 && (*ifc).ifa_netmask->sa_family == AF_INET6) { + inet_ntop(AF_INET6, &(((struct sockaddr_in6*)((*ifc).ifa_addr))->sin6_addr), ip, 64); + inet_ntop(AF_INET6, &(((struct sockaddr_in6*)((*ifc).ifa_netmask))->sin6_addr), mask, 64); + if (0 != strncmp("::", ip, strlen(ip)) && 0 != strncmp(ip, "fe80", 4)) { + lua_createtable(L, 0, 4); + lua_pushliteral(L, "Interface"); lua_pushstring(L, (*ifc).ifa_name); lua_rawset(L, -3); + lua_pushliteral(L, "IP"); lua_pushstring(L, ip); lua_rawset(L, -3); + lua_pushliteral(L, "Mask"); lua_pushstring(L, mask); lua_rawset(L, -3); + lua_pushliteral(L, "Version"); lua_pushliteral(L, "IPv6"); lua_rawset(L, -3); + lua_rawseti(L, -2, index++); + } + } + } + } + freeifaddrs(ifc1); + return 1; +} + +LUAMOD_API int luaopen_sys(lua_State *L){ + luaL_checkversion(L); + luaL_Reg sys_libs[] = { + {"os", los}, + {"now", lnow}, + {"time", ltime}, + {"date", ldate}, + {"ipv4", lipv4}, + {"ipv6", lipv6}, + {"usage", lusage}, + {"str2ip", lstr2ip}, + {"ip2str", lip2str}, + {"hostname", lhostname}, + {"interface", linterface}, + {"new_tab", lnew_tab}, + {NULL, NULL} + }; + luaL_newlib(L, sys_libs); + return 1; +} diff --git a/luaclib/src/ltask.c b/luaclib/src/ltask.c index 172fde9f..3133926f 100644 --- a/luaclib/src/ltask.c +++ b/luaclib/src/ltask.c @@ -1,12 +1,11 @@ #define LUA_LIB -#include "../../src/core.h" +#include -static void -TASK_CB(CORE_P_ core_task *task, int revents){ +static void TASK_CB(CORE_P_ core_task *task, int revents){ lua_State *co = (lua_State *) core_get_watcher_userdata(task); if (co && (lua_status(co) == LUA_YIELD || lua_status(co) == LUA_OK)){ - int status = lua_resume(co, NULL, lua_status(co) == LUA_YIELD ? lua_gettop(co) : lua_gettop(co) - 1); + int status = CO_RESUME(co, NULL, lua_status(co) == LUA_YIELD ? lua_gettop(co) : lua_gettop(co) - 1); if (status != LUA_YIELD && status != LUA_OK){ LOG("ERROR", lua_tostring(co, -1)); } @@ -14,60 +13,47 @@ TASK_CB(CORE_P_ core_task *task, int revents){ } } -int -task_new(lua_State *L){ +static int task_new(lua_State *L){ core_task *task = lua_newuserdata(L, sizeof(core_task)); - if (!task) return 0; - + if (!task) + return 0; core_task_init(task, TASK_CB); - luaL_setmetatable(L, "__Task__"); - return 1; } -int -task_start(lua_State *L){ +static int task_start(lua_State *L){ core_task *task = (core_task *) luaL_testudata(L, 1, "__Task__"); - if (!task) return luaL_error(L, "attemp to pass a invaild core_task value."); - + if (!task) + return luaL_error(L, "attemp to pass a invaild core_task value."); lua_State *co = lua_tothread(L, 2); - if (!co) return luaL_error(L, "attemp to pass a invaild lua_State value."); - + if (!co) + return luaL_error(L, "attemp to pass a invaild lua_State value."); /* 这里假设栈大小永远够用, 因为调用与回调都不需要传入那么多参数 */ lua_xmove(L, co, lua_gettop(L) - 2); - core_set_watcher_userdata(task, co); - core_task_start(CORE_LOOP_ task); - return 1; } -int -task_stop(lua_State *L){ +static int task_stop(lua_State *L){ core_task *task = (core_task *) luaL_testudata(L, 1, "__Task__"); - if (!task) return luaL_error(L, "attemp to pass a invaild core_task value."); - + if (!task) + return luaL_error(L, "attemp to pass a invaild core_task value."); core_task_stop(CORE_LOOP_ task); - return 0; } -LUAMOD_API int -luaopen_task(lua_State *L){ - +LUAMOD_API int luaopen_task(lua_State *L){ luaL_checkversion(L); - luaL_newmetatable(L, "__Task__"); lua_pushstring (L, "__index"); lua_pushvalue(L, -2); lua_rawset(L, -3); - lua_pushliteral(L, "__mode"); - lua_pushliteral(L, "kv"); - lua_rawset(L, -3); - + lua_pushliteral(L, "__mode"); + lua_pushliteral(L, "kv"); + lua_rawset(L, -3); luaL_Reg task_libs[] = { {"new", task_new}, {"start", task_start}, diff --git a/luaclib/src/ltcp.c b/luaclib/src/ltcp.c index b376dcc7..71bcd779 100644 --- a/luaclib/src/ltcp.c +++ b/luaclib/src/ltcp.c @@ -1,506 +1,1267 @@ #define LUA_LIB +#include #include #include #include -#include "../../src/core.h" -static inline -void SETSOCKETOPT(int sockfd){ - /* 设置非阻塞 */ - non_blocking(sockfd); - - int ENABLE = 1; - - /* 地址/端口重用 */ - setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &ENABLE, sizeof(ENABLE)); - - /* 关闭小包延迟合并算法 */ - setsockopt(sockfd, SOL_SOCKET, TCP_NODELAY, &ENABLE, sizeof(ENABLE)); +#ifndef alloca + #define alloca __alloca +#endif + +#ifndef MSG_NOSIGNAL + #define MSG_NOSIGNAL (0) +#endif + +#define MBSIZE (262144) + +#define None (-1) +#define SERVER (0) +#define CLIENT (1) + +static inline void SETSOCKETOPT(int sockfd, int mode){ + int Enable = 1; + int ret = 0; + /* 设置非阻塞 */ + non_blocking(sockfd); +/* 地址重用 */ +#ifdef SO_REUSEADDR + ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &Enable, sizeof(Enable)); + if (ret < 0) { + LOG("ERROR", "Setting SO_REUSEADDR failed."); + LOG("ERROR", strerror(errno)); + return core_exit(); + } +#endif + +/* 端口重用 */ +#ifdef SO_REUSEPORT + if (mode == SERVER) { + #ifdef SO_REUSEPORT_LB + // BSD系统的多进程负载需要使用此宏 + ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT_LB, &Enable, sizeof(Enable)); + #else + ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &Enable, sizeof(Enable)); + #endif + if (ret < 0) { + LOG("ERROR", "Setting SO_REUSEPORT failed."); + LOG("ERROR", strerror(errno)); + return core_exit(); + } + } +#endif + +#ifdef SO_NOSIGPIPE + // 屏蔽信号 + ret = setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, &Enable, sizeof(Enable)); + if (ret < 0) { + LOG("ERROR", "Setting SO_NOSIGPIPE failed."); + LOG("ERROR", strerror(errno)); + return core_exit(); + } +#endif + +/* 关闭连接不会阻塞 */ +#ifdef SO_LINGER + struct linger lin = { .l_onoff = 0, .l_linger = 0 }; + ret = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &lin , sizeof(lin)); + if (ret < 0){ + LOG("ERROR", "Setting SO_LINGER failed."); + LOG("ERROR", strerror(errno)); + return core_exit(); + } +#endif + +/* 开启 TCP keepalive */ +#ifdef SO_KEEPALIVE + if (mode != None){ + ret = setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &Enable , sizeof(Enable)); + if (ret < 0){ + LOG("ERROR", "Setting SO_KEEPALIVE failed."); + LOG("ERROR", strerror(errno)); + return core_exit(); + } + } +#endif + +/* 关闭小包延迟合并算法 */ +#ifdef TCP_NODELAY + ret = setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &Enable, sizeof(Enable)); + if (ret < 0){ + LOG("ERROR", "Setting TCP_NODELAY failed."); + LOG("ERROR", strerror(errno)); + return core_exit(); + } +#endif + +/* 开启延迟Accept, 没数据来之前不回调accept */ +#if defined(TCP_DEFER_ACCEPT) + if (mode == SERVER) { + ret = setsockopt(sockfd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &Enable, sizeof(Enable)); + if (ret < 0){ + LOG("WARN", "Setting TCP_DEFER_ACCEPT failed."); + LOG("WARN", strerror(errno)); + // 不能退出的原因是因为要兼容那垃圾WSL实现. + // return core_exit(); + } + } +#elif defined(SO_ACCEPTFILTER) + /* TODO: 暂不实现 */ +#endif + +/* 开启TCP快速连接 */ +#if defined(TCP_FASTOPEN) && defined(MSG_FASTOPEN) + if (mode == SERVER) { + int len = 5; + ret = setsockopt(sockfd, IPPROTO_TCP, TCP_FASTOPEN, &len, sizeof(len)); + if (ret < 0){ + LOG("WARN", "Setting TCP_FASTOPEN failed."); + LOG("WARN", strerror(errno)); + // 不能退出的原因是因为要兼容那垃圾WSL实现. + // return core_exit(); + } + } +#endif + +/* 设置 TCP keepalive 空闲时间 */ +#ifdef TCP_KEEPIDLE + int keepidle = 30; + ret = setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle , sizeof(keepidle)); + if (ret < 0){ + LOG("ERROR", "Setting TCP_KEEPIDLE failed."); + LOG("ERROR", strerror(errno)); + return core_exit(); + } +#endif + +/* 设置 TCP keepalive 探测总次数 */ +#ifdef TCP_KEEPCNT + int keepcount = 3; + ret = setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT, &keepcount , sizeof(keepcount)); + if (ret < 0){ + LOG("ERROR", "Setting TCP_KEEPCNT failed."); + LOG("ERROR", strerror(errno)); + return core_exit(); + } +#endif + +/* 设置 TCP keepalive 每次探测间隔时间 */ +#ifdef TCP_KEEPINTVL + int keepinterval = 5; + ret = setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, &keepinterval , sizeof(keepinterval)); + if (ret < 0){ + LOG("ERROR", "Setting TCP_KEEPINTVL failed."); + LOG("ERROR", strerror(errno)); + return core_exit(); + } +#endif + +/* 开启IPV6与ipv4双栈 */ +#ifdef IPV6_V6ONLY + if (mode != None) { + int No = 0; + ret = setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&No, sizeof(No)); + if (ret < 0){ + LOG("ERROR", "Setting IPV6_V6ONLY failed."); + LOG("ERROR", strerror(errno)); + return core_exit(); + } + } +#endif } /* server fd */ -static int -create_server_fd(int port, int backlog){ - errno = 0; - - int sockfd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); - if (0 >= sockfd) return -1; - - SETSOCKETOPT(sockfd); - - struct sockaddr_in6 SA; - SA.sin6_family = AF_INET6; - SA.sin6_port = htons(port); - SA.sin6_addr = in6addr_any; - - int bind_siccess = bind(sockfd, (struct sockaddr *)&SA, sizeof(struct sockaddr_in6)); - if (0 > bind_siccess) { - return -1; /* 绑定套接字失败 */ - } - - int listen_success = listen(sockfd, backlog); - if (0 > listen_success) { - return -1; /* 监听套接字失败 */ - } - return sockfd; +static int create_server_fd(const char *ip, int port, int backlog){ + errno = 0; + /* 建立 TCP Server Socket */ + int sockfd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); + if (0 >= sockfd){ + LOG("ERROR", strerror(errno)); + return -1; + } + /* socket option set */ + SETSOCKETOPT(sockfd, SERVER); + + struct sockaddr_in6 SA; + memset(&SA, 0x0, sizeof(SA)); + + SA.sin6_family = AF_INET6; + SA.sin6_port = htons(port); + SA.sin6_addr = in6addr_any; + + struct in6_addr addr; + /* 如果填写的是`::1` */ + if (!strcmp(ip, "::1") && inet_pton(AF_INET6, "::1", &addr) == 1){ + SA.sin6_addr = addr; + /* 如果填写的是`127.0.0.1` */ + } else if (!strcmp(ip, "127.0.0.1") && inet_pton(AF_INET6, "::ffff:127.0.0.1", &addr) == 1) { + SA.sin6_addr = addr; + /* 如果填写的是其它`IPv6`地址 */ + } else if (inet_pton(AF_INET6, ip, &addr) == 1) { + SA.sin6_addr = addr; + /* 如果填写的是 `0.0.0.0` 则监听所有地址 */ + } else if (!strcmp(ip, "0.0.0.0")) { + SA.sin6_addr = in6addr_any; + /* 检查IPv4地址或者是非法IP地址 */ + } else { + struct in_addr addr4; + if (inet_pton(AF_INET, ip, &addr4) == 1) { + char *ipv6 = alloca(strlen(ip) + 8); + memset(ipv6, 0x0, strlen(ip) + 8); + memmove(ipv6, "::ffff:", 7); + memmove(ipv6 + 7, ip, strlen(ip)); + if (inet_pton(AF_INET6, ipv6, &addr) != 1){ + close(sockfd); + return -1; + } + SA.sin6_addr = addr; + } + } + + /* 绑定套接字失败 */ + int bind_success = bind(sockfd, (struct sockaddr *)&SA, sizeof(SA)); + if (0 > bind_success) { + close(sockfd); + return -1; + } + + /* 监听套接字失败 */ + int listen_success = listen(sockfd, backlog); + if (0 > listen_success) { + close(sockfd); + return -1; + } + + return sockfd; } /* client fd */ -static int -create_client_fd(const char *ipaddr, int port){ - errno = 0; - - /* 建立socket */ - int sockfd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); - if (0 >= sockfd) return -1; - - SETSOCKETOPT(sockfd); +static int create_client_fd(const char *ipaddr, int port){ + errno = 0; + /* 建立 TCP Client Socket */ + int sockfd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); + if (0 >= sockfd) { + LOG("ERROR", strerror(errno)); + return -1; + } + + /* socket option set */ + SETSOCKETOPT(sockfd, CLIENT); + + struct sockaddr_in6 SA; + memset(&SA, 0x0, sizeof(SA)); + + SA.sin6_family = AF_INET6; + SA.sin6_port = htons(port); + int error = inet_pton(AF_INET6, ipaddr, &SA.sin6_addr); + if (1 != error) { + LOG("ERROR", strerror(errno)); + close(sockfd); + return -1; + } + + int ret = connect(sockfd, (struct sockaddr*)&SA, sizeof(SA)); + if (ret != 0 && errno != EINPROGRESS) { + close(sockfd); + return -1; + } + return sockfd; +} - struct sockaddr_in6 SA; - SA.sin6_family = AF_INET6; - SA.sin6_port = htons(port); - inet_pton(AF_INET6, ipaddr, &SA.sin6_addr); - connect(sockfd, (struct sockaddr*)&SA, sizeof(SA)); - if (errno != EINPROGRESS){ - close(sockfd); - return -1; - } - return sockfd; +static int create_server_unixsock(const char* path, size_t path_len, int backlog) { + errno = 0; + int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (0 >= sockfd){ + LOG("ERROR", strerror(errno)); + return -1; + } + + struct sockaddr_un UN; + memset(&UN, 0x0, sizeof(UN)); + + UN.sun_family = AF_LOCAL; + memmove(UN.sun_path, path, path_len); + + non_blocking(sockfd); + + /* 绑定套接字失败 */ + int bind_success = bind(sockfd, (struct sockaddr *)&UN, sizeof(UN)); + if (0 > bind_success) { + LOG("ERROR", strerror(errno)); + close(sockfd); + return -1; + } + + /* 监听套接字失败 */ + int listen_success = listen(sockfd, backlog); + if (0 > listen_success) { + LOG("ERROR", strerror(errno)); + close(sockfd); + return -1; + } + + return sockfd; } +static int create_client_unixsock(const char* path, size_t path_len) { + errno = 0; + int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0); + if (0 >= sockfd){ + LOG("ERROR", strerror(errno)); + return -1; + } + struct sockaddr_un UN; + memset(&UN, 0x0, sizeof(UN)); -static void -TCP_IO_CB(CORE_P_ core_io *io, int revents) { + UN.sun_family = AF_LOCAL; + memmove(UN.sun_path, path, path_len); - int status = 0; + non_blocking(sockfd); - if (revents & EV_ERROR) { - LOG("ERROR", "Recevied a core_io object internal error from libev."); - return ; - } + int ret = connect(sockfd, (struct sockaddr*)&UN, sizeof(UN)); + if (0 > ret) { + // LOG("ERROR", strerror(errno)); + close(sockfd); + return -1; + } - lua_State *co = (lua_State *)core_get_watcher_userdata(io); - if (lua_status(co) == LUA_YIELD || lua_status(co) == LUA_OK){ - status = lua_resume(co, NULL, 0); - if (status != LUA_YIELD && status != LUA_OK){ - LOG("ERROR", lua_tostring(co, -1)); - core_io_stop(CORE_LOOP_ io); - } - } + return sockfd; } -static void -IO_CONNECT(CORE_P_ core_io *io, int revents){ - - if (revents & EV_ERROR) { - LOG("ERROR", "Recevied a core_io object internal error from libev."); - return ; - } - - if (revents & EV_WRITE){ - lua_State *co = (lua_State *)core_get_watcher_userdata(io); - if (lua_status(co) == LUA_YIELD || lua_status(co) == LUA_OK){ - socklen_t len; - int status = 0, CONNECTED = 0, err = 0; - if(getsockopt(io->fd, SOL_SOCKET, SO_ERROR, &err, &len) == 0 && err == 0) CONNECTED = 1; - lua_pushboolean(co, CONNECTED); - status = lua_resume(co, NULL, 1); - if (status != LUA_YIELD && status != LUA_OK){ - LOG("ERROR", lua_tostring(co, -1)); - core_io_stop(CORE_LOOP_ io); - } - } - } - -} - -static void /* 接受链接 */ -IO_ACCEPT(CORE_P_ core_io *io, int revents){ - - if (revents & EV_READ){ - for(;;) { - errno = 0; - struct sockaddr_in6 SA; - socklen_t slen = sizeof(struct sockaddr_in6); - int client = accept(io->fd, (struct sockaddr*)&SA, &slen); - if (0 >= client) { - if (errno != EAGAIN) - LOG("INFO", strerror(errno)); - return ; - } - lua_State *co = (lua_State *) core_get_watcher_userdata(io); - if (lua_status(co) == LUA_YIELD || lua_status(co) == LUA_OK){ - char buf[INET6_ADDRSTRLEN]; - inet_ntop(AF_INET6, &SA.sin6_addr, buf, INET6_ADDRSTRLEN); - lua_pushinteger(co, client); - lua_pushlstring(co, buf, strlen(buf)); - int status = lua_resume(co, NULL, lua_status(co) == LUA_YIELD ? lua_gettop(co) : lua_gettop(co) - 1); - if (status != LUA_YIELD && status != LUA_OK) { - LOG("ERROR", lua_tostring(co, -1)); - LOG("ERROR", "Error Lua Accept Method"); - } - } - } - } +static void TCP_IO_CB(CORE_P_ core_io *io, int revents) { + if (revents & EV_ERROR) { + LOG("ERROR", "Recevied a core_io object internal error from libev."); + return ; + } + lua_State *co = (lua_State *)core_get_watcher_userdata(io); + if (lua_status(co) == LUA_YIELD || lua_status(co) == LUA_OK){ + int status = CO_RESUME(co, NULL, 0); + if (status != LUA_YIELD && status != LUA_OK){ + LOG("ERROR", lua_tostring(co, -1)); + core_io_stop(CORE_LOOP_ io); + } + } } -int -tcp_read(lua_State *L){ - - errno = 0; - - int fd = lua_tointeger(L, 1); - if (0 >= fd) return 0; +static void IO_CONNECT(CORE_P_ core_io *io, int revents){ + if (revents & EV_ERROR) { + LOG("ERROR", "Recevied a core_io object internal error from libev."); + return ; + } + if (revents & EV_WRITE){ + lua_State *co = (lua_State *)core_get_watcher_userdata(io); + if (lua_status(co) == LUA_YIELD || lua_status(co) == LUA_OK){ + socklen_t len = sizeof(socklen_t); int err = 0; int connected = 0; + if(getsockopt(io->fd, SOL_SOCKET, SO_ERROR, &err, (socklen_t*)&len) == 0 && err == 0) connected = 1; + lua_pushboolean(co, connected); + if (err) lua_pushstring(co, strerror(err)); + int status = CO_RESUME(co, NULL, lua_gettop(co) - 1); + if (status != LUA_YIELD && status != LUA_OK){ + LOG("ERROR", lua_tostring(co, -1)); + core_io_stop(CORE_LOOP_ io); + } + } + } +} - int bytes = lua_tointeger(L, 2); - if (0 >= bytes) return 0; +/* 接受链接 */ +static void IO_ACCEPT(CORE_P_ core_io *io, int revents){ + if (revents & EV_READ){ + lua_State *co = (lua_State *) core_get_watcher_userdata(io); + int status = lua_status(co); + if (status != LUA_YIELD && status != LUA_OK) { + LOG("ERROR", "accept get a invalid lua vm."); + return; + } + for(;;) { + errno = 0; + struct sockaddr_in6 SA; + socklen_t slen = sizeof(SA); + memset(&SA, 0x0, slen); + int client = accept(io->fd, (struct sockaddr*)&SA, &slen); + if (0 >= client) { + if (errno != EWOULDBLOCK) + LOG("ERROR", strerror(errno)); + return; + } + SETSOCKETOPT(client, None); + char buf[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &SA.sin6_addr, buf, INET6_ADDRSTRLEN); + lua_pushinteger(co, client); + lua_pushlstring(co, buf, strlen(buf)); + lua_pushinteger(co, SA.sin6_port); + status = CO_RESUME(co, NULL, status == LUA_YIELD ? lua_gettop(co) : lua_gettop(co) - 1); + if (status != LUA_YIELD && status != LUA_OK) { + LOG("ERROR", lua_tostring(co, -1)); + LOG("ERROR", "Error Lua Accept Method"); + core_io_stop(CORE_LOOP_ io); + } + } + } +} - do { - char str[bytes]; - int rsize = read(fd, str, bytes); +static void IO_ACCEPT_EX(CORE_P_ core_io *io, int revents) { + if (revents & EV_READ){ + lua_State *co = (lua_State *) core_get_watcher_userdata(io); + int status = lua_status(co); + if (status != LUA_YIELD && status != LUA_OK) { + LOG("ERROR", "accept_ex get a invalid lua vm."); + return ; + } + for (;;) { + errno = 0; + struct sockaddr_un UN; + socklen_t slen = sizeof(UN); + memset(&UN, 0x0, slen); + int client = accept(io->fd, (struct sockaddr *)&UN, &slen); + if (0 >= client) { + if (errno != EWOULDBLOCK) + LOG("INFO", strerror(errno)); + return ; + } + non_blocking(client); + lua_pushinteger(co, client); + // lua_pushlstring(co, UN.sun_path, strlen(UN.sun_path)); // unix domain path + status = CO_RESUME(co, NULL, status == LUA_YIELD ? lua_gettop(co) : lua_gettop(co) - 1); + if (status != LUA_YIELD && status != LUA_OK) { + LOG("ERROR", lua_tostring(co, -1)); + LOG("ERROR", "Error Lua Accept Method"); + core_io_stop(CORE_LOOP_ io); + } + } + } +} - if (rsize > 0) { - lua_pushlstring(L, str, rsize); - lua_pushinteger(L, rsize); - return 2; - } - if (0 > rsize) { - if (errno == EINTR) continue; - } - } while(0); +struct io_sendfile { + int32_t fd; + off_t pos; + off_t offset; + lua_State *L; +}; - return 0; +static void +IO_SENDFILE(CORE_P_ core_io *io, int revents){ + if (revents & EV_WRITE){ + errno = 0; + struct io_sendfile *sf = core_get_watcher_userdata(io); +#if defined(__APPLE__) || defined(__FreeBSD__) + int tag = 0; off_t nBytes = 0; + for (;;) { + #if defined(__APPLE__) + tag = sendfile(sf->fd, io->fd, sf->pos, &nBytes, NULL, 0); + #else + tag = sendfile(sf->fd, io->fd, sf->pos, 0, NULL, &nBytes, SF_NODISKIO | SF_NOCACHE); + #endif + sf->pos += nBytes; + if (0 > tag) { + if (errno == EINTR) continue; + if (errno == EWOULDBLOCK) return; + lua_pushboolean(sf->L, 0); + break; + } + // 当nBytes与tag同时为0时说明发送成功, 其它情况下都当做发送失败. + if (0 == nBytes){ lua_pushboolean(sf->L, 1); break; } + } +#elif defined(linux) || defined(__linux__) + #include + for (;;) { + int tag = sendfile(io->fd, sf->fd, NULL, sf->offset); + if (0 >= tag) { + if (!tag){ lua_pushboolean(sf->L, 1); break; } + if (errno == EINTR) continue; + if (errno == EWOULDBLOCK) return; + lua_pushboolean(sf->L, 0); + break; + } + } +#else + char buf[sf->offset]; + for(;;) { + int rBytes = pread(sf->fd, buf, sf->offset, sf->pos); + if (rBytes == 0) { lua_pushboolean(sf->L, 1); break; } // 所有数据写入发送完毕. + int wBytes = send(io->fd, buf, rBytes, MSG_DONTWAIT | MSG_NOSIGNAL); + if (wBytes <= 0) { + if (errno == EINTR) continue; + if (errno == EWOULDBLOCK) return; + lua_pushboolean(sf->L, 0); + break; + } + sf->pos += wBytes; + } +#endif + core_set_watcher_userdata(io, NULL); + int status = CO_RESUME(sf->L, NULL, lua_status(sf->L) == LUA_YIELD ? lua_gettop(sf->L) : lua_gettop(sf->L) - 1); + if (status != LUA_YIELD && status != LUA_OK) { + LOG("ERROR", lua_tostring(sf->L, -1)); + LOG("ERROR", "Error Lua SENDFILE Method"); + } + close(sf->fd); + xfree(sf); + } } -int -tcp_sslread(lua_State *L){ - - errno = 0; +static int tcp_sendfile(lua_State *L){ + core_io *io = (core_io *) luaL_testudata(L, 1, "__TCP__"); + int fd = open(luaL_checkstring(L, 3), O_RDONLY); + if (fd < 0) + return luaL_error(L, "[%s]: %s.", luaL_checkstring(L, 3), strerror(errno)); + + struct io_sendfile *sf = xmalloc(sizeof(struct io_sendfile)); + sf->L = lua_tothread(L, 2); + sf->fd = fd; + sf->offset = luaL_checkinteger(L, 5); + sf->pos = 0; + + core_set_watcher_userdata(io, sf); + core_io_init(io, IO_SENDFILE, luaL_checkinteger(L, 4), EV_WRITE); + core_io_start(CORE_LOOP_ io); + return 1; +} - SSL *ssl = lua_touserdata(L, 1); - if (!ssl) return 0; +static int tcp_peek(lua_State *L) { + errno = 0; + + int fd = lua_tointeger(L, 1); + if (0 >= fd) return 0; + + int bsize = lua_tointeger(L, 2); + char* buffer = lua_newuserdata(L, bsize); + + if (0 == lua_toboolean(L, 3)) { + lua_pushinteger(L, read(fd, buffer, bsize)); + return 1; + } + + while (1){ + int len = recv(fd, buffer, bsize, MSG_PEEK); + if (len <= 0){ + if (errno == EINTR) continue; + if (errno == EWOULDBLOCK) { + lua_pushnil(L); + lua_pushinteger(L, 0); + break; + } + lua_pushnil(L); + lua_pushstring(L, strerror(errno)); + break; + } + lua_pushlstring(L, buffer, len); + lua_pushinteger(L, len); + break; + } + return 2; +} - int bytes = lua_tointeger(L, 2); - if (0 >= bytes) return 0; +static int tcp_sslpeek(lua_State *L) { + errno = 0; + + SSL *ssl = lua_touserdata(L, 1); + if (!ssl) return 0; + + int bsize = lua_tointeger(L, 2); + char* buffer = lua_newuserdata(L, bsize); + + if (0 == lua_toboolean(L, 3)) { + lua_pushinteger(L, SSL_read(ssl, buffer, bsize)); + return 1; + } + + while (1){ + int len = SSL_peek(ssl, buffer, bsize); + if (len <= 0){ + if (errno == EINTR) + continue; + if (SSL_ERROR_WANT_READ == SSL_get_error(ssl, len)){ + lua_pushnil(L); + lua_pushinteger(L, 0); + break; + } + lua_pushnil(L); + lua_pushstring(L, strerror(errno)); + break; + } + lua_pushlstring(L, buffer, len); + lua_pushinteger(L, len); + break; + } + return 2; +} - do { - char str[bytes]; - int rsize = SSL_read(ssl, str, bytes); - if (0 < rsize) { - lua_pushlstring(L, str, rsize); - lua_pushinteger(L, rsize); - return 2; - } - if (0 > rsize){ - if (errno == EINTR) continue; - if (SSL_ERROR_WANT_READ == SSL_get_error(ssl, rsize)){ - lua_pushnil(L); - lua_pushinteger(L, 0); - return 2; - } - } - } while (0); +static int tcp_read(lua_State *L){ + int fd = lua_tointeger(L, 1); + if (0 >= fd) + return 0; + + lua_Integer bytes = lua_tointeger(L, 2); + if (0 >= bytes) + return 0; + + errno = 0; + char* str = NULL; + if (bytes <= MBSIZE) + str = alloca(bytes); + else + str = lua_newuserdata(L, bytes); + + do { + int rsize = read(fd, str, bytes); + if (rsize > 0) { + lua_pushlstring(L, str, rsize); + lua_pushinteger(L, rsize); + return 2; + } + if (0 > rsize) { + if (errno == EINTR) continue; + if (errno == EWOULDBLOCK) { + lua_pushnil(L); + lua_pushinteger(L, 0); + return 2; + } + } + } while(0); + lua_pushnil(L); + lua_pushstring(L, strerror(errno)); + return 2; +} - return 0; +static int tcp_sslread(lua_State *L){ + SSL *ssl = lua_touserdata(L, 1); + if (!ssl) + return 0; + + lua_Integer bytes = lua_tointeger(L, 2); + if (0 >= bytes) + return 0; + + errno = 0; + char* str = NULL; + if (bytes <= MBSIZE) + str = alloca(bytes); + else + str = lua_newuserdata(L, bytes); + + do { + int rsize = SSL_read(ssl, str, bytes); + if (0 < rsize) { + lua_pushlstring(L, str, rsize); + lua_pushinteger(L, rsize); + return 2; + } + if (0 > rsize){ + if (errno == EINTR) continue; + if (SSL_ERROR_WANT_READ == SSL_get_error(ssl, rsize)){ + lua_pushnil(L); + lua_pushinteger(L, 0); + return 2; + } + } + } while (0); + return 0; } -int -tcp_write(lua_State *L){ +static int tcp_write(lua_State *L){ + size_t resp_len = 0; + int fd = lua_tointeger(L, 1); + const char *response = luaL_checklstring(L, 2, &resp_len); + if (!response) + return luaL_error(L, "tcp_write ERROR: attempt to write an empty string."); - errno = 0; + errno = 0; + int offset = lua_tointeger(L, 3); + do { + int wsize = send(fd, response + offset, resp_len - offset, MSG_DONTWAIT | MSG_NOSIGNAL); + if (wsize > 0) { lua_pushinteger(L, wsize); return 1; } - int fd = lua_tointeger(L, 1); - if (0 >= fd) return 0; + if (wsize < 0){ + if (errno == EINTR) continue; + if (errno == EWOULDBLOCK){ lua_pushinteger(L, 0); return 1;} + } - const char *response = lua_tostring(L, 2); - if (!response) return 0; + } while (0); - int resp_len = lua_tointeger(L, 3); + return 0; +} - do { +static int tcp_sslwrite(lua_State *L){ + SSL *ssl = lua_touserdata(L, 1); + if (!ssl) + return 0; + + const char *response = lua_tostring(L, 2); + if (!response) + return luaL_error(L, "tcp_sslwrite ERROR: attempt to write an empty string."); + + errno = 0; + int resp_len = lua_tointeger(L, 3); + do { + int wsize = SSL_write(ssl, response, resp_len); + if (wsize > 0) { lua_pushinteger(L, wsize); return 1; } + if (wsize < 0){ + if (errno == EINTR) continue; + if (SSL_ERROR_WANT_WRITE == SSL_get_error(ssl, wsize)){ lua_pushinteger(L, 0); return 1; } + } + } while (0); + + return 0; +} - int wsize = write(fd, response, resp_len); +static int new_server_fd(lua_State *L){ + const char *ip = lua_tostring(L, 1); + if (!ip) + return 0; - if (wsize > 0) { lua_pushinteger(L, wsize); return 1; } + int port = lua_tointeger(L, 2); + if (!port) + return 0; - if (wsize < 0){ - if (errno == EINTR) continue; - if (errno == EAGAIN){ lua_pushinteger(L, 0); return 1;} - } + int backlog = lua_tointeger(L, 3); - } while (0); + int fd = create_server_fd(ip, port, 0 >= backlog ? 128 : backlog); + if (0 >= fd) + return 0; - return 0; + lua_pushinteger(L, fd); + return 1; } -int -tcp_sslwrite(lua_State *L){ - - SSL *ssl = lua_touserdata(L, 1); - if (!ssl) return 0; - - const char *response = lua_tostring(L, 2); - if (!response) return 0; - - int resp_len = lua_tointeger(L, 3); +static int new_client_fd(lua_State *L){ + const char *ip = lua_tostring(L, 1); + if (!ip) + return 0; - errno = 0; + int port = lua_tointeger(L, 2); + if (!port) + return 0; - do { - int wsize = SSL_write(ssl, response, resp_len); - if (wsize > 0) { lua_pushinteger(L, wsize); return 1; } - if (wsize < 0){ - if (errno == EINTR) continue; - if (SSL_ERROR_WANT_WRITE == SSL_get_error(ssl, wsize)){ lua_pushinteger(L, 0); return 1; } - } - } while (0); + int fd = create_client_fd(ip, port); + if (0 >= fd) + return 0; - return 0; + lua_pushinteger(L, fd); + return 1; } -int -new_server_fd(lua_State *L){ - const char *ip = lua_tostring(L, 1); - if(!ip) return 0; +static int new_server_unixsock_fd(lua_State *L) { + size_t size = 0; + const char* path = luaL_checklstring(L, 1, &size); + if (!path || size < 2) + return 0; - int port = lua_tointeger(L, 2); - if(!port) return 0; + /* 传递rm为非nil与false值, 删除已经存在的文件 */ + int rm = lua_toboolean(L, 2); - int backlog = lua_tointeger(L, 3); + /* 文件存在或无法删除的情况下都将创建unixsock失败 */ + if (!access(path, F_OK)) { + if (!rm || unlink(path)) + return 0; + } - int fd = create_server_fd(port, 0 >= backlog ? 128 : backlog); - if (0 >= fd) return 0; + int backlog = luaL_checkinteger(L, 3); - lua_pushinteger(L, fd); + int fd = create_server_unixsock(path, size, 0 >= backlog ? 128 : backlog); + if (fd <= 0) + return 0; - return 1; + lua_pushinteger(L, fd); + return 1; } -int -new_client_fd(lua_State *L){ - const char *ip = lua_tostring(L, 1); - if(!ip) return 0; +static int new_client_unixsock_fd(lua_State *L){ + size_t size = 0; + const char* path = luaL_checklstring(L, 1, &size); + if (!path || size < 2) + return 0; - int port = lua_tointeger(L, 2); - if(!port) return 0; + // 如果文件不存在返回失败 + if (access(path, F_OK)) + return 0; - int fd = create_client_fd(ip, port); - if (0 >= fd) return 0; + int fd = create_client_unixsock(path, size); + if (fd <= 0) + return 0; - lua_pushinteger(L, fd); - - return 1; + lua_pushinteger(L, fd); + return 1; } -int -tcp_listen(lua_State *L){ - core_io *io = (core_io *) luaL_testudata(L, 1, "__TCP__"); - if(!io) return 0; - - /* socket文件描述符 */ - int fd = lua_tointeger(L, 2); - if (0 >= fd) return 0; +static int tcp_listen(lua_State *L){ + core_io *io = (core_io *) luaL_testudata(L, 1, "__TCP__"); + if (!io) + return 0; + + /* socket文件描述符 */ + int fd = lua_tointeger(L, 2); + if (0 >= fd) + return 0; + + /* 回调协程 */ + lua_State *co = lua_tothread(L, 3); + if (!co) + return 0; + + core_set_watcher_userdata(io, co); + core_io_init(io, IO_ACCEPT, fd, EV_READ); + core_io_start(CORE_LOOP_ io); + return 1; +} - /* 回调协程 */ - lua_State *co = lua_tothread(L, 3); - if (!co) return 0; +static int tcp_listen_ex(lua_State *L) { + core_io *io = (core_io *) luaL_testudata(L, 1, "__TCP__"); + if (!io) + return 0; + + /* socket文件描述符 */ + int fd = lua_tointeger(L, 2); + if (0 >= fd) + return 0; + /* 回调协程 */ + lua_State *co = lua_tothread(L, 3); + if (!co) + return 0; + + core_set_watcher_userdata(io, co); + core_io_init(io, IO_ACCEPT_EX, fd, EV_READ); + core_io_start(CORE_LOOP_ io); + return 1; +} - core_set_watcher_userdata(io, co); +static int tcp_connect(lua_State *L){ + core_io *io = (core_io *) luaL_testudata(L, 1, "__TCP__"); + if(!io) return 0; - core_io_init(io, IO_ACCEPT, fd, EV_READ); + /* socket文件描述符 */ + int fd = lua_tointeger(L, 2); + if (0 >= fd) + return 0; - core_io_start(CORE_LOOP_ io); + /* 回调协程 */ + lua_State *co = lua_tothread(L, 3); + if (!co) + return 0; - return 0; + core_set_watcher_userdata(io, co); + core_io_init(io, IO_CONNECT, fd, EV_READ | EV_WRITE); + core_io_start(CORE_LOOP_ io); + return 0; } -int -tcp_connect(lua_State *L){ - - core_io *io = (core_io *) luaL_testudata(L, 1, "__TCP__"); - if(!io) return 0; - - /* socket文件描述符 */ - int fd = lua_tointeger(L, 2); - if (0 >= fd) return 0; - - /* 回调协程 */ - lua_State *co = lua_tothread(L, 3); - if (!co) return 0; +static int tcp_sslconnect(lua_State *L){ + + SSL *ssl = (SSL*) lua_touserdata(L, 1); + if (!ssl) + return 0; + + int reason = SSL_get_error(ssl, SSL_do_handshake(ssl)); + /* 握手结束 -> 握手成功 */ + if (reason == SSL_ERROR_NONE){ + lua_pushboolean(L, 1); + return 1; + } + /* 需要再进一步交互 */ + if (SSL_ERROR_WANT_READ == reason || SSL_ERROR_WANT_WRITE == reason){ + lua_pushnil(L); + lua_pushinteger(L, reason - 1); + return 2; + } + /* 握手失败 */ + return 0; +} - core_set_watcher_userdata(io, co); +static int tcp_start(lua_State *L){ + core_io *io = (core_io *) luaL_testudata(L, 1, "__TCP__"); + if(!io) + return 0; + + /* socket文件描述符 */ + int fd = lua_tointeger(L, 2); + if (0 >= fd) + return 0; + + /* 监听事件 */ + int events = lua_tointeger(L, 3); + if (0 >= events || events > 3) + return 0; + + /* 回调协程 */ + lua_State *co = lua_tothread(L, 4); + if (!co) + return 0; + + core_set_watcher_userdata(io, co); + core_io_init(io, TCP_IO_CB, fd, events); + core_io_start(CORE_LOOP_ io); + return 0; +} - core_io_init(io, IO_CONNECT, fd, EV_READ | EV_WRITE); +// 设置SSL客户端的SNI特性 +static int ssl_set_connect_server(lua_State *L) { + SSL *ssl = (SSL*) lua_touserdata(L, 1); + if (!ssl) + return luaL_error(L, "Invalid SSL ssl."); - core_io_start(CORE_LOOP_ io); + size_t size = 0; + const char *hostname = (const char *)luaL_checklstring(L, 2, &size); + if (!hostname || size < 1) + return luaL_error(L, "Invalid host name."); - return 0; +#if defined(SSL_set_tlsext_host_name) + SSL_set_tlsext_host_name(ssl, hostname); +#endif + return 1; } -int -tcp_sslconnect(lua_State *L){ +static int ssl_set_connect_mode(lua_State *L) { + SSL *ssl = (SSL*) lua_touserdata(L, 1); + if (!ssl) + return luaL_error(L, "Invalid SSL ssl."); - SSL *ssl = (SSL*) lua_touserdata(L, 1); - if (!ssl) return 0; + SSL_CTX *ctx = (SSL_CTX*) lua_touserdata(L, 2); + if (!ctx) + return luaL_error(L, "Invalid SSL_CTX ctx."); - int status = SSL_do_handshake(ssl); - if (1 == status) { - lua_pushboolean(L, 1); - return 1; - } - if (SSL_ERROR_WANT_READ == SSL_get_error(ssl, status)) { - lua_pushnil(L); - lua_pushinteger(L, EV_READ); - return 2; - } - if (SSL_ERROR_WANT_WRITE == SSL_get_error(ssl, status)){ - lua_pushnil(L); - lua_pushinteger(L, EV_WRITE); - return 2; - } - return 0; + SSL_set_connect_state(ssl); + return 1; } -int -tcp_start(lua_State *L){ +static int ssl_set_accept_mode(lua_State *L) { + SSL *ssl = (SSL*) lua_touserdata(L, 1); + if (!ssl) + return luaL_error(L, "Invalid SSL ssl."); - core_io *io = (core_io *) luaL_testudata(L, 1, "__TCP__"); - if(!io) return 0; + SSL_CTX *ctx = (SSL_CTX*) lua_touserdata(L, 2); + if (!ctx) + return luaL_error(L, "Invalid SSL_CTX ctx."); - /* socket文件描述符 */ - int fd = lua_tointeger(L, 2); - if (0 >= fd) return 0; + SSL_set_accept_state(ssl); - /* 监听事件 */ - int events = lua_tointeger(L, 3); - if (0 >= events || events > 3) return 0; + return 1; +} - /* 回调协程 */ - lua_State *co = lua_tothread(L, 4); - if (!co) return 0; +// 加载证书 +static int ssl_set_certificate(lua_State *L) { + SSL *ssl = (SSL*) lua_touserdata(L, 1); + if (!ssl) + return luaL_error(L, "Invalid SSL ssl."); + + SSL_CTX *ctx = (SSL_CTX*) lua_touserdata(L, 2); + if (!ctx) + return luaL_error(L, "Invalid SSL_CTX ctx."); + + size_t size = 0; + const char* path = luaL_checklstring(L, 3, &size); + if (!path || size < 1) + return luaL_error(L, "Invalid cert path"); + + X509 *cert; + /* 为`SSL`设置证书, 所有加载策略都失败就返回失败. */ + BIO* IO = BIO_new(BIO_s_mem()); BIO_write(IO, path, size); + cert = PEM_read_bio_X509(IO, NULL, NULL, NULL); + BIO_free(IO); + + /* 内存证书 */ + if (cert && 1 == SSL_use_certificate(ssl, cert)) + return 0; + + FILE* fp = fopen(path, "rb"); + cert = PEM_read_X509(fp, NULL, NULL, NULL); + if (fp) + fclose(fp); + + /* 文件证书 */ + if (cert && 1 == SSL_use_certificate(ssl, cert)) + return 0; + + return luaL_error(L, "[ssl error]: read cert failed."); +} - core_set_watcher_userdata(io, co); +// 加载私钥 +static int ssl_set_privatekey(lua_State *L) { + SSL *ssl = (SSL*) lua_touserdata(L, 1); + if (!ssl) + return luaL_error(L, "Invalid SSL ssl."); + + SSL_CTX *ctx = (SSL_CTX*) lua_touserdata(L, 2); + if (!ctx) + return luaL_error(L, "Invalid SSL_CTX ctx."); + + size_t size = 0; + const char* path = luaL_checklstring(L, 3, &size); + if (!path || size < 1) + return luaL_error(L, "Invalid cert path"); + + EVP_PKEY *key; + /* 为`SSL`设置私钥, 所有加载策略都失败就返回失败. */ + BIO* IO = BIO_new(BIO_s_mem()); BIO_write(IO, path, size); + key = PEM_read_bio_PrivateKey(IO, NULL, NULL, NULL); + BIO_free(IO); + + /* 内存私钥 */ + if (key && 1 == SSL_use_PrivateKey(ssl, key)) + return 0; + + FILE* fp = fopen(path, "rb"); + key = PEM_read_PrivateKey(fp, NULL, NULL, NULL); + if (fp) + fclose(fp); + + /* 文件私钥 */ + if (key && 1 == SSL_use_PrivateKey(ssl, key)) + return 0; + + return luaL_error(L, "[ssl error]: read private key failed."); +} - core_io_init(io, TCP_IO_CB, fd, events); +// 如果私钥有安装密钥, 则再这里设置 +static int ssl_set_userdata_key(lua_State *L) { + SSL *ssl = (SSL*) lua_touserdata(L, 1); + if (!ssl) + return luaL_error(L, "Invalid SSL ssl."); - core_io_start(CORE_LOOP_ io); + SSL_CTX *ctx = (SSL_CTX*) lua_touserdata(L, 2); + if (!ctx) + return luaL_error(L, "Invalid SSL_CTX ctx."); - return 0; + size_t size = 0; + const char* password = luaL_checklstring(L, 3, &size); + if (!password || size < 1) + return 1; + SSL_CTX_set_default_passwd_cb_userdata(ctx, (void*)password); + return 1; } -int -ssl_new(lua_State *L){ +// 验证证书与私钥是否有效 +static int ssl_verify(lua_State *L) { + SSL *ssl = (SSL*) lua_touserdata(L, 1); + if (!ssl) + return luaL_error(L, "Invalid SSL ssl."); - int fd = lua_tointeger(L, 1); + SSL_CTX *ctx = (SSL_CTX*) lua_touserdata(L, 2); + if (!ctx) + return luaL_error(L, "Invalid SSL_CTX ctx."); - SSL_CTX *ssl_ctx = SSL_CTX_new(SSLv23_method()); - if (!ssl_ctx) return 0; + // 检查证书与私钥是否一致. + if (1 != SSL_check_private_key(ssl) || 1 != SSL_CTX_check_private_key(ctx)) + return 0; - SSL *ssl = SSL_new(ssl_ctx); - if (!ssl) return 0; + lua_pushboolean(L, 1); + return 1; +} - SSL_set_fd(ssl, fd); +static int ssl_set_alpn(lua_State *L) { + SSL *ssl = (SSL*) lua_touserdata(L, 1); + if (!ssl) + return luaL_error(L, "Invalid SSL ssl."); - SSL_set_connect_state(ssl); + SSL_CTX *ctx = (SSL_CTX*) lua_touserdata(L, 2); + if (!ctx) + return luaL_error(L, "Invalid SSL_CTX ctx."); - lua_pushlightuserdata(L, (void*) ssl_ctx); + size_t lsize = 0; + const char *str = luaL_checklstring(L, 3, &lsize); + if (!str || lsize < 1) + return 0; - lua_pushlightuserdata(L, (void*) ssl); +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + SSL_set_alpn_protos(ssl, (const unsigned char *)str, lsize); + SSL_CTX_set_alpn_protos(ctx, (const unsigned char *)str, lsize); +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L - return 2; + return 1; } -int -ssl_free(lua_State *L){ +static int ssl_get_alpn(lua_State *L) { + SSL *ssl = (SSL*) lua_touserdata(L, 1); + if (!ssl) + return luaL_error(L, "Invalid SSL ssl."); + + SSL_CTX *ctx = (SSL_CTX*) lua_touserdata(L, 2); + if (!ctx) + return luaL_error(L, "Invalid SSL_CTX ctx."); + + uint32_t len = 0; + const uint8_t *data = NULL; +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + SSL_get0_alpn_selected(ssl, (const uint8_t **)&data, (unsigned int *)&len); +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + if (!data || len < 1) + return 0; + lua_pushlstring(L, (const char *)data, len); + return 1; +} - SSL_CTX *ssl_ctx = (SSL_CTX*) lua_touserdata(L, 1); - if (ssl_ctx) SSL_CTX_free(ssl_ctx); // 销毁ctx上下文; +static int ssl_new(lua_State *L){ + SSL_CTX *ssl_ctx = SSL_CTX_new(SSLv23_method()); + if (!ssl_ctx) + return 0; - SSL *ssl = (SSL*) lua_touserdata(L, 2); - if (ssl) SSL_free(ssl); // 销毁基于ctx的ssl对象; + SSL *ssl = SSL_new(ssl_ctx); + if (!ssl) + return 0; - return 0; + lua_pushlightuserdata(L, (void*) ssl); + lua_pushlightuserdata(L, (void*) ssl_ctx); + return 2; } -int -tcp_new(lua_State *L){ - - core_io *io = (core_io *) lua_newuserdata(L, sizeof(core_io)); +static int ssl_set_fd(lua_State *L) { - if(!io) return 0; + SSL *ssl = (SSL*) lua_touserdata(L, 1); + if (!ssl) + return luaL_error(L, "Invalid SSL ssl."); - luaL_setmetatable(L, "__TCP__"); - - return 1; + int fd = lua_tointeger(L, 2); + SSL_set_fd(ssl, fd); + SSL_set_connect_state(ssl); + return 1; } +static int ssl_new_fd(lua_State *L){ -int -tcp_stop(lua_State *L){ + int fd = lua_tointeger(L, 1); - core_io *io = (core_io *) luaL_testudata(L, 1, "__TCP__"); - if(!io) return 0; + SSL_CTX *ssl_ctx = SSL_CTX_new(SSLv23_method()); + if (!ssl_ctx) + return 0; - core_io_stop(CORE_LOOP_ io); + SSL *ssl = SSL_new(ssl_ctx); + if (!ssl) + return 0; - return 0; + SSL_set_fd(ssl, fd); + SSL_set_connect_state(ssl); + lua_pushlightuserdata(L, (void*) ssl); + lua_pushlightuserdata(L, (void*) ssl_ctx); + return 2; +} +static int ssl_free(lua_State *L){ + SSL *ssl = (SSL*) lua_touserdata(L, 1); + if (ssl) + SSL_free(ssl); // 销毁基于ctx的ssl对象; + SSL_CTX *ssl_ctx = (SSL_CTX*) lua_touserdata(L, 2); + if (ssl_ctx) + SSL_CTX_free(ssl_ctx); // 销毁ctx上下文; + return 0; } -int -tcp_close(lua_State *L){ +static int tcp_new(lua_State *L){ + core_io *io = (core_io *) lua_newuserdata(L, sizeof(core_io)); + if(!io) + return 0; + luaL_setmetatable(L, "__TCP__"); + return 1; +} - int fd = lua_tointeger(L, 1); - if (fd && fd > 0) close(fd); +static int tcp_stop(lua_State *L){ + core_io *io = (core_io *) luaL_testudata(L, 1, "__TCP__"); + if(!io) + return 0; + core_io_stop(CORE_LOOP_ io); + return 0; +} - return 0; +static int tcp_close(lua_State *L){ + int fd = lua_tointeger(L, 1); + if (fd && fd > 0) + close(fd); + return 0; +} +/* 修改写缓冲区大小 */ +static int tcp_set_write_buf(lua_State *L) { + int fd = lua_tointeger(L, 1); + if (fd < 0) + return 0; + int bsize = lua_tointeger(L, 2); + if (bsize <= 65535) + return 0; + +#if defined(SO_SNDBUF) + if (-1 == setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &bsize, sizeof(socklen_t))) + return 0; +#endif + + return 1; } -LUAMOD_API int -luaopen_tcp(lua_State *L){ - luaL_checkversion(L); - /* 添加SSL支持 */ - SSL_library_init(); - SSL_load_error_strings(); - // CRYPTO_set_mem_functions(xmalloc, xrealloc, xfree); - // OpenSSL_add_ssl_algorithms(); - /* 添加SSL支持 */ - luaL_newmetatable(L, "__TCP__"); - lua_pushstring (L, "__index"); - lua_pushvalue(L, -2); - lua_rawset(L, -3); - lua_pushliteral(L, "__mode"); - lua_pushliteral(L, "kv"); - lua_rawset(L, -3); +/* 修改读缓冲区大小 */ +static int tcp_set_read_buf(lua_State *L) { + int fd = lua_tointeger(L, 1); + if (fd < 0) + return 0; + int bsize = lua_tointeger(L, 2); + if (bsize <= 65535) + return 0; + +#if defined(SO_RCVBUF) + if (-1 == setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &bsize, sizeof(socklen_t))) + return 0; +#endif + + return 1; +} - luaL_Reg tcp_libs[] = { - {"read", tcp_read}, - {"write", tcp_write}, - {"ssl_read", tcp_sslread}, - {"ssl_write", tcp_sslwrite}, - {"stop", tcp_stop}, - {"start", tcp_start}, - {"close", tcp_close}, - {"listen", tcp_listen}, - {"connect", tcp_connect}, - {"ssl_connect", tcp_sslconnect}, - {"new", tcp_new}, - {"new_ssl", ssl_new}, - {"free_ssl", ssl_free}, - {"new_server_fd", new_server_fd}, - {"new_client_fd", new_client_fd}, - {NULL, NULL} - }; - luaL_setfuncs(L, tcp_libs, 0); - luaL_newlib(L, tcp_libs); - return 1; +LUAMOD_API int luaopen_tcp(lua_State *L){ + luaL_checkversion(L); + /* 添加SSL支持 */ + SSL_library_init(); + // SSL_load_error_strings(); + // ERR_load_crypto_strings(); + // OpenSSL_add_ssl_algorithms(); + // CRYPTO_set_mem_functions(xmalloc, xrealloc, xfree); + /* 添加SSL支持 */ + luaL_newmetatable(L, "__TCP__"); + lua_pushstring (L, "__index"); + lua_pushvalue(L, -2); + lua_rawset(L, -3); + lua_pushliteral(L, "__mode"); + lua_pushliteral(L, "kv"); + lua_rawset(L, -3); + luaL_Reg tcp_libs[] = { + {"read", tcp_read}, + {"write", tcp_write}, + {"peek", tcp_peek}, + {"sslpeek", tcp_sslpeek}, + {"ssl_read", tcp_sslread}, + {"ssl_write", tcp_sslwrite}, + {"stop", tcp_stop}, + {"start", tcp_start}, + {"close", tcp_close}, + {"listen", tcp_listen}, + {"listen_ex", tcp_listen_ex}, + {"connect", tcp_connect}, + {"ssl_connect", tcp_sslconnect}, + {"new", tcp_new}, + {"new_ssl", ssl_new}, + {"ssl_set_fd", ssl_set_fd}, + {"new_ssl_fd", ssl_new_fd}, + {"free_ssl", ssl_free}, + {"new_server_fd", new_server_fd}, + {"new_client_fd", new_client_fd}, + {"new_server_unixsock_fd", new_server_unixsock_fd}, + {"new_client_unixsock_fd", new_client_unixsock_fd}, + {"sendfile", tcp_sendfile}, + {"ssl_verify", ssl_verify}, + {"ssl_set_alpn", ssl_set_alpn}, + {"ssl_get_alpn", ssl_get_alpn}, + {"tcp_set_read_buf", tcp_set_read_buf}, + {"tcp_set_write_buf", tcp_set_write_buf}, + {"ssl_set_accept_mode", ssl_set_accept_mode}, + {"ssl_set_connect_mode", ssl_set_connect_mode}, + {"ssl_set_privatekey", ssl_set_privatekey}, + {"ssl_set_certificate", ssl_set_certificate}, + {"ssl_set_userdata_key", ssl_set_userdata_key}, + {"ssl_set_connect_server", ssl_set_connect_server}, + {NULL, NULL} + }; + luaL_setfuncs(L, tcp_libs, 0); + luaL_newlib(L, tcp_libs); + return 1; } diff --git a/luaclib/src/ltimer.c b/luaclib/src/ltimer.c index 71c0c3d8..83bd2eb6 100644 --- a/luaclib/src/ltimer.c +++ b/luaclib/src/ltimer.c @@ -1,92 +1,67 @@ #define LUA_LIB -#include "../../src/core.h" +#include /* === 定时器 === */ -static void -TIMEOUT_CB(CORE_P_ core_timer *timer, int revents){ - - if (revents & EV_TIMER){ - - lua_State *co = (lua_State *) core_get_watcher_userdata(timer); - - int status = lua_resume(co, NULL, lua_gettop(co) > 0 ? lua_gettop(co) - 1 : 0); - - if (status != LUA_OK && status != LUA_YIELD){ - - LOG( "ERROR", lua_tostring(co, -1)); - +static void TIMEOUT_CB(CORE_P_ core_timer *timer, int revents){ + if (revents & EV_TIMER){ + lua_State *co = (lua_State *) core_get_watcher_userdata(timer); + int status = CO_RESUME(co, NULL, lua_gettop(co) > 0 ? lua_gettop(co) - 1 : 0); + if (status != LUA_OK && status != LUA_YIELD){ + LOG( "ERROR", lua_tostring(co, -1)); core_timer_stop(CORE_LOOP_ timer); - - } - } + } + } } -int -timer_stop(lua_State *L){ - +static int timer_stop(lua_State *L){ core_timer *timer = (core_timer *) luaL_testudata(L, 1, "__TIMER__"); - if(!timer) return 0; - + if(!timer) + return 0; core_timer_stop(CORE_LOOP_ timer); - return 0; } -int -timer_start(lua_State *L){ - +static int timer_start(lua_State *L){ core_timer *timer = (core_timer *) luaL_testudata(L, 1, "__TIMER__"); - if(!timer) return 0; - + if(!timer) + return 0; lua_Number timeout = luaL_checknumber(L, 2); - if (timeout <= 0 ) return 0; - + if (timeout <= 0 ) + return 0; lua_State *co = lua_tothread(L, 3); - if(!co) return 0; - + if(!co) + return 0; core_set_watcher_userdata(timer, (void*)co); - core_timer_start(CORE_LOOP, timer, timeout); - return 0; - } -int -timer_new(lua_State *L){ - +static int timer_new(lua_State *L){ core_timer *timer = (core_timer *) lua_newuserdata(L, sizeof(core_timer)); - if(!timer) return 0; - + if(!timer) + return 0; core_timer_init(timer, TIMEOUT_CB); - luaL_setmetatable(L, "__TIMER__"); - return 1; - } -LUAMOD_API int -luaopen_timer(lua_State *L){ - +LUAMOD_API int luaopen_timer(lua_State *L){ luaL_checkversion(L); - - luaL_newmetatable(L, "__TIMER__"); - lua_pushstring (L, "__index"); - lua_pushvalue(L, -2); - lua_rawset(L, -3); - lua_pushliteral(L, "__mode"); - lua_pushliteral(L, "kv"); - lua_rawset(L, -3); - + luaL_newmetatable(L, "__TIMER__"); + lua_pushstring (L, "__index"); + lua_pushvalue(L, -2); + lua_rawset(L, -3); + lua_pushliteral(L, "__mode"); + lua_pushliteral(L, "kv"); + lua_rawset(L, -3); luaL_Reg timer_libs[] = { {"new", timer_new}, {"stop", timer_stop}, {"start", timer_start}, {NULL, NULL}, }; - luaL_setfuncs(L, timer_libs, 0); + luaL_setfuncs(L, timer_libs, 0); luaL_newlib(L, timer_libs); - return 1; + return 1; } diff --git a/luaclib/src/ludp.c b/luaclib/src/ludp.c index 14be3707..7a69247f 100644 --- a/luaclib/src/ludp.c +++ b/luaclib/src/ludp.c @@ -1,46 +1,88 @@ #define LUA_LIB -#include "../../src/core.h" +#include -int -udp_socket_new(const char *ipaddr, int port){ +#ifndef alloca + #define alloca __alloca +#endif - errno = 0; - /* 建立socket*/ - int sockfd = socket(AF_INET6, SOCK_DGRAM, 0); - if (0 >= sockfd) return -1; +#define MBSIZE (262144) +static inline void SETSOCKETOPT(int sockfd) { + int Enable = 1; + int ret = 0; /* 设置非阻塞 */ non_blocking(sockfd); - int ENABLE = 1; - /* 端口重用 */ - setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &ENABLE, sizeof(ENABLE)); +/* 地址重用 */ +#ifdef SO_REUSEADDR + ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &Enable, sizeof(Enable)); + if (ret < 0) { + LOG("ERROR", "Setting SO_REUSEADDR failed."); + LOG("ERROR", strerror(errno)); + return core_exit(); + } +#endif + +/* 端口重用 */ +#ifdef SO_REUSEPORT + ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &Enable, sizeof(Enable)); + if (ret < 0) { + LOG("ERROR", "Setting SO_REUSEPORT failed."); + LOG("ERROR", strerror(errno)); + return core_exit(); + } +#endif + +/* 开启IPV6与ipv4双栈 */ +#ifdef IPV6_V6ONLY + int No = 0; + ret = setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&No, sizeof(No)); + if (ret < 0){ + LOG("ERROR", "Setting IPV6_V6ONLY failed."); + LOG("ERROR", strerror(errno)); + return core_exit(); + } +#endif + +} +static int udp_socket_new(const char *ipaddr, int port){ + errno = 0; + /* 建立 UDP Socket */ + int sockfd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + if (0 >= sockfd) + return -1; + SETSOCKETOPT(sockfd); struct sockaddr_in6 SA; + memset(&SA, 0x0, sizeof(SA)); SA.sin6_family = AF_INET6; SA.sin6_port = htons(port); - inet_pton(AF_INET6, ipaddr, &SA.sin6_addr); - - connect(sockfd, (struct sockaddr*)&SA, sizeof(SA)); - + int error = inet_pton(AF_INET6, ipaddr, &SA.sin6_addr); + if (1 != error) { + LOG("ERROR", strerror(errno)); + close(sockfd); + return -1; + } + int ret = connect(sockfd, (struct sockaddr*)&SA, sizeof(SA)); + if (ret == -1) { + LOG("ERROR", strerror(errno)); + close(sockfd); + return -1; + } return sockfd; } -static void -UDP_IO_CB(CORE_P_ ev_io *io, int revents){ - +static void UDP_IO_CB(CORE_P_ core_io *io, int revents){ int status = 0; - if (revents & EV_ERROR) { - LOG("ERROR", "Recevied a ev_io object internal error from libev."); + LOG("ERROR", "Recevied a core_io object internal error from libev."); return ; } - if (revents & EV_READ){ lua_State *co = (lua_State *)core_get_watcher_userdata(io); if (lua_status(co) == LUA_YIELD || lua_status(co) == LUA_OK){ - status = lua_resume(co, NULL, lua_gettop(co) > 0 ? lua_gettop(co) - 1 : 0); + status = CO_RESUME(co, NULL, lua_gettop(co) > 0 ? lua_gettop(co) - 1 : 0); if (status != LUA_YIELD && status != LUA_OK){ LOG("ERROR", lua_tostring(co, -1)); } @@ -48,147 +90,106 @@ UDP_IO_CB(CORE_P_ ev_io *io, int revents){ } } -int -udp_send(lua_State *L){ - +static int udp_send(lua_State *L){ int fd = lua_tointeger(L, 1); - if (fd < 0) return 0; - + if (fd < 0) + return 0; const char* data = lua_tostring(L, 2); - if (!data) return 0; - + if (!data) + return 0; size_t len = lua_tointeger(L, 3); - int wsize = write(fd, data, len); - lua_pushinteger(L, wsize); - return 1; - } -int -udp_recv(lua_State *L){ - +static int udp_recv(lua_State *L){ int fd = lua_tointeger(L, 1); - if (fd < 0) return 0; - - char str[4096] = {0}; - - int rsize = read(fd, str, 4096); - - if (rsize < 0) return 0; - + if (fd < 0) + return 0; + int bsize = MBSIZE; + char* str = alloca(MBSIZE); + int rsize = read(fd, str, bsize); + if (rsize < 0) + return 0; lua_pushlstring(L, str, rsize); - lua_pushinteger(L, rsize); - return 2; - } -int -udp_connect(lua_State *L){ - +static int udp_connect(lua_State *L){ const char *ip = lua_tostring(L, 1); - if(!ip) {lua_settop(L, 0); return 0;} - + if(!ip) + return 0; int port = lua_tointeger(L, 2); - if(!port) {lua_settop(L, 0); return 0;} - + if(!port) + return 0; int fd = udp_socket_new(ip, port); - + if (0 >= fd) + return 0; lua_pushinteger(L, fd > 0 ? fd : -1); - return 1; - } -int -udp_start(lua_State *L){ - - ev_io *io = (ev_io *) luaL_testudata(L, 1, "__UDP__"); +static int udp_start(lua_State *L){ + core_io *io = (core_io *) luaL_testudata(L, 1, "__UDP__"); if(!io) return 0; - int fd = lua_tointeger(L, 2); - if (fd < 0) return 0; - + if (fd < 0) + return 0; /* 回调协程 */ lua_State *co = lua_tothread(L, 3); - if (!co) return 0; - + if (!co) + return 0; core_set_watcher_userdata(io, co); - core_io_init (io, UDP_IO_CB, fd, EV_READ); - core_io_start (CORE_LOOP_ io); - return 0; - } -int -udp_stop(lua_State *L){ - - ev_io *io = (ev_io *) luaL_testudata(L, 1, "__UDP__"); - if(!io) return 0; - +static int udp_stop(lua_State *L){ + core_io *io = (core_io *) luaL_testudata(L, 1, "__UDP__"); + if(!io) + return 0; core_io_stop(CORE_LOOP_ io); - return 0; - } -int -udp_close(lua_State *L){ - +static int udp_close(lua_State *L){ int fd = lua_tointeger(L, 1); - - if (fd && fd > 0) close(fd); - + if (fd && fd > 0) + close(fd); return 0; - } - - -int -udp_new(lua_State *L){ - - ev_io *io = (ev_io *) lua_newuserdata(L, sizeof(ev_io)); - - if(!io) return 0; - +static int udp_new(lua_State *L){ + core_io *io = (core_io *) lua_newuserdata(L, sizeof(core_io)); + if(!io) + return 0; luaL_setmetatable(L, "__UDP__"); - return 1; - } -LUAMOD_API int -luaopen_udp(lua_State *L){ - +LUAMOD_API int luaopen_udp(lua_State *L){ luaL_checkversion(L); - - luaL_newmetatable(L, "__UDP__"); - lua_pushstring (L, "__index"); - lua_pushvalue(L, -2); - lua_rawset(L, -3); - lua_pushliteral(L, "__mode"); - lua_pushliteral(L, "kv"); - lua_rawset(L, -3); - + luaL_newmetatable(L, "__UDP__"); + lua_pushstring (L, "__index"); + lua_pushvalue(L, -2); + lua_rawset(L, -3); + lua_pushliteral(L, "__mode"); + lua_pushliteral(L, "kv"); + lua_rawset(L, -3); luaL_Reg udp_libs[] = { {"new", udp_new}, {"close", udp_close}, - {"start", udp_start}, - {"stop", udp_stop}, + {"start", udp_start}, + {"stop", udp_stop}, {"connect", udp_connect}, - {"send", udp_send}, - {"recv", udp_recv}, + {"send", udp_send}, + {"recv", udp_recv}, {NULL, NULL} }; luaL_setfuncs(L, udp_libs, 0); luaL_newlib(L, udp_libs); return 1; -} \ No newline at end of file +} diff --git a/luaclib/src/lz/Makefile b/luaclib/src/lz/Makefile new file mode 100644 index 00000000..0c0e7353 --- /dev/null +++ b/luaclib/src/lz/Makefile @@ -0,0 +1,26 @@ +.PHONY : build rebuild clean + +default : + @echo "=======================================" + @echo "Please use 'make build' command to build it.." + @echo "Please use 'make rebuild' command to build it.." + @echo "Please use 'make clean' command to clean all." + @echo "=======================================" + +CC = cc + +INCLUDES += -I../../../src -I/usr/local/include +LIBS = -L../ -L../../ -L../../../ -L/usr/local/lib + +CFLAGS = -O3 -Wall -shared -fPIC -Wl,-rpath,. -Wl,-rpath,.. -Wl,-rpath,/usr/local/lib + +# 是用内置库 +# MICRO = +# DLL = -lcore +# 使用`zlib` +MICRO = -DUSE_ZLIB=1 +DLL = -lcore -lz + +build: + @$(CC) -o lz.so lzlib.c miniz.c $(INCLUDES) $(LIBS) $(CFLAGS) $(DLL) $(MICRO) + @mv *.so ../../ diff --git a/luaclib/src/lz/lzlib.c b/luaclib/src/lz/lzlib.c new file mode 100644 index 00000000..9dbc8ddf --- /dev/null +++ b/luaclib/src/lz/lzlib.c @@ -0,0 +1,256 @@ +/* +** LICENSE: BSD +** Author: CandyMi[https://github.com/candymi] +*/ +#define LUA_LIB + +#include + +#if defined(USE_ZLIB) + #include +#else + #define MINIZ_NO_MALLOC + #define MZ_FREE xrio_free + #define MZ_MALLOC xrio_malloc + #define MZ_REALLOC xrio_realloc + #include "miniz.h" +#endif + +/* 分配内存 */ +#if defined(USE_ZLIB) +void* stream_zalloc(void* opaque, unsigned items, unsigned nsize) +#else +static inline void set_int32(char data[4], uint32_t bit) { + data[0] = (uint8_t)(bit) & 0xff; + data[1] = (uint8_t)(bit >> 8) & 0xff; + data[2] = (uint8_t)(bit >> 16) & 0xff; + data[3] = (uint8_t)(bit >> 24) & 0xff; +} +void* stream_zalloc(void* opaque, size_t items, size_t nsize) +#endif +{ + (void)opaque; + return xmalloc(((size_t)items) * ((size_t)nsize)); +} + +/* 释放内存 */ +void stream_free(void* opaque, void* ptr) { + (void)opaque; + xfree(ptr); +} + +void stream_init(z_stream *z) { + memset(z, 0x0, sizeof(z_stream)); + z->zalloc = stream_zalloc; + z->zfree = stream_free; +} + +#if !defined(USE_ZLIB) +/* | 1 | 1 | 1 | 1 | 4 | 1 | 1 | +** ID1 + ID2 + CM + FLG + MTIME + XFL + OS +** |2 + len| null + terminal | 2 byte | +** FEXTRA + FNAME + FCOMMENT + FHCRC +*/ +static inline size_t gzip_check(const uint8_t *buffer, size_t bsize) { + if (bsize <= 10 || memcmp(buffer, "\x1f\x8b\x08", 3)) + return 0; + + int flag = buffer[3]; + size_t len = 10; + buffer += len; + // FEXTRA - 4 byte. + if (flag & 0x04) { + buffer += 2; + len += 4 + (buffer[0] | buffer[1] << 8); + } + // FNAME + if (flag & 0x08) { + while (*buffer++) + len++; + len += 1; /* NULL */ + } + // FCOMMENT + if (flag & 0x10) { + while (*buffer++) + len++; + len += 1; /* NULL */ + } + // FHCRC - 2 byte. + if (flag & 0x02) + len += 2; + return len; +} +#endif + +/* 压缩 */ +static inline int stream_deflate(lua_State* L, z_stream *z, int Z_MYFLUSH, int Z_MYMODE, const uint8_t* in, size_t in_size) { + if (!z) + return luaL_error(L, "[ZLIB ERROR]: `stream_deflate` got invalid `z_stream`."); + + if (Z_OK != deflateInit2(z, Z_DEFAULT_COMPRESSION, Z_DEFLATED, Z_MYMODE, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY)) + return luaL_error(L, "[ZLIB ERROR]: `stream_deflate` init failed."); + + /* 输入 */ + z->next_in = (uint8_t *)in; z->avail_in = in_size; + + /* 输出 */ + size_t out_size = deflateBound(z, in_size) + MAX_WBITS; + uint8_t *out = lua_newuserdata(L, out_size); + z->next_out = out; z->avail_out = out_size; + + /* 计算偏移值 */ + int offset = 0; + if (Z_MYFLUSH == Z_SYNC_FLUSH) + offset = 4; + + /* 压缩数据 */ + deflate(z, Z_MYFLUSH); + deflateEnd(z); + + /* 结束 */ + lua_pushlstring(L, (const char*)out, z->total_out - offset); + lua_pushinteger(L, in_size); + return 2; +} + +/* 解压 */ +static inline int stream_inflate(lua_State* L, z_stream *z, int Z_MYWIND, const uint8_t* in, size_t in_size) { + if (!z) + return luaL_error(L, "[ZLIB ERROR]: `stream_inflate` got Invalid `z_stream`."); + + if (Z_OK != inflateInit2(z, Z_MYWIND)) + return luaL_error(L, "[ZLIB ERROR]: `stream_inflate` init failed."); + + /* 输入 */ + z->next_in = (uint8_t *)in; z->avail_in = in_size; + + /* 输出 */ + luaL_Buffer B; luaL_buffinit(L, &B); + size_t bsize = 4096; uint8_t *buffer = alloca(bsize); + + size_t offset = 0; /* z->total_out - offset 真正的输出缓冲区的长度 */ + while (1) + { + z->next_out = buffer; z->avail_out = bsize; + int ret = inflate(z, Z_SYNC_FLUSH); + // printf("解压 : [ret] = %d, isize = [%ld], osize = [%ld]\n", ret, z->total_in, z->total_out); + if (ret != Z_OK && ret != Z_STREAM_END) { + inflateEnd(z); + lua_pushboolean(L, 0); + lua_pushfstring(L, "[ZLIB ERROR]: Invalid inflate buffer. %d", ret); + return 2; + } + luaL_addlstring(&B, (char *)buffer, z->total_out - offset); + // printf("buf[%s]\n", buffer); +#if defined(USE_ZLIB) + if (z->total_in == in_size) + break; +#else + if (z->total_out - offset < bsize) + break; +#endif + offset = z->total_out; + } + /* 结束 */ + inflateEnd(z); + luaL_pushresult(&B); + return 1; +} + +/* RFC 7692 */ +int lws_compress(lua_State* L) { + z_stream z; stream_init(&z); size_t bsize; + const uint8_t *buffer = (const uint8_t *)luaL_checklstring(L, 1, &bsize); + return stream_deflate(L, &z, Z_SYNC_FLUSH, -MAX_WBITS, buffer, bsize); +} + +int lws_uncompress(lua_State* L) { + z_stream z; stream_init(&z); size_t bsize; + const uint8_t *buffer = (const uint8_t *)luaL_checklstring(L, 1, &bsize); + return stream_inflate(L, &z, -MAX_WBITS, buffer, bsize); +} + +/* RFC 1952 */ +int lgzip_compress(lua_State* L) { + z_stream z; stream_init(&z); size_t bsize; + const uint8_t *buffer = (const uint8_t *)luaL_checklstring(L, 1, &bsize); +#if defined(USE_ZLIB) + stream_deflate(L, &z, Z_FINISH, MAX_WBITS + 16, buffer, bsize); +#else + // 计算文件长度 与 CRC32 校验 + stream_deflate(L, &z, Z_FINISH, -MAX_WBITS, buffer, bsize); + size_t tsize; const char *text = lua_tolstring(L, -2, &tsize); + // 拷贝`GZIP`头部与压缩帧 + char *data = lua_newuserdata(L, tsize + 18); + memcpy(data, "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\x13", 10); memcpy(data + 10, text, tsize); + // 拷贝`CRC32`与`ISIZE`长度 + set_int32(data + tsize + 10, crc32(0, buffer, bsize)); + set_int32(data + tsize + 14, (uint32_t)bsize % 0xffffffff); + // `GZIP`包装完成 + lua_pushlstring(L, data, tsize + 18); lua_pushinteger(L, bsize); +#endif + return 2; +} + +int lgzip_uncompress(lua_State* L) { + z_stream z; stream_init(&z); size_t bsize; + const uint8_t *buffer = (const uint8_t *)luaL_checklstring(L, 1, &bsize); +#if defined(USE_ZLIB) + return stream_inflate(L, &z, MAX_WBITS + 16, buffer, bsize); +#else + size_t hlen = gzip_check(buffer, bsize); + if (!hlen || hlen >= bsize) { + lua_pushboolean(L, 0); + lua_pushliteral(L, "[ZLIB ERROR]: Invalid gzip header."); + return 2; + } + return stream_inflate(L, &z, -MAX_WBITS, buffer + hlen, bsize - hlen); +#endif +} + +/* RFC 1951 */ +int ldeflate_compress(lua_State* L) { + z_stream z; stream_init(&z); size_t bsize; + const uint8_t *buffer = (const uint8_t *)luaL_checklstring(L, 1, &bsize); + return stream_deflate(L, &z, Z_FINISH, -MAX_WBITS, buffer, bsize); +} + +int ldeflate_uncompress(lua_State* L) { + z_stream z; stream_init(&z); size_t bsize; + const uint8_t *buffer = (const uint8_t *)luaL_checklstring(L, 1, &bsize); + return stream_inflate(L, &z, -MAX_WBITS, buffer, bsize); +} + +/* RFC 1950 */ +int lzlib_compress(lua_State* L) { + z_stream z; stream_init(&z); size_t bsize; + const uint8_t *buffer = (const uint8_t *)luaL_checklstring(L, 1, &bsize); + return stream_deflate(L, &z, Z_FINISH, MAX_WBITS, buffer, bsize); +} + +int lzlib_uncompress(lua_State* L) { + z_stream z; stream_init(&z); size_t bsize; + const uint8_t *buffer = (const uint8_t *)luaL_checklstring(L, 1, &bsize); + return stream_inflate(L, &z, MAX_WBITS, buffer, bsize); +} + +LUAMOD_API int luaopen_lz(lua_State *L) { + luaL_checkversion(L); + luaL_Reg zlib_libs[] = { + /* LZ77压缩/解压方法 */ + {"compress", ldeflate_compress}, + {"uncompress", ldeflate_uncompress}, + /* 原生压缩方法 */ + {"compress2", lzlib_compress}, + {"uncompress2", lzlib_uncompress}, + /* gzip压缩/解压方法 */ + {"gzcompress", lgzip_compress}, + {"gzuncompress", lgzip_uncompress}, + /* Websocket压缩/解压方法 */ + {"wscompress", lws_compress}, + {"wsuncompress", lws_uncompress}, + {NULL, NULL} + }; + luaL_newlib(L, zlib_libs); + return 1; +} \ No newline at end of file diff --git a/luaclib/src/lz/miniz.c b/luaclib/src/lz/miniz.c new file mode 100644 index 00000000..cbd8b7b2 --- /dev/null +++ b/luaclib/src/lz/miniz.c @@ -0,0 +1,7735 @@ +#include "miniz.h" +/************************************************************************** + * + * Copyright 2013-2014 RAD Game Tools and Valve Software + * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + + + +typedef unsigned char mz_validate_uint16[sizeof(mz_uint16) == 2 ? 1 : -1]; +typedef unsigned char mz_validate_uint32[sizeof(mz_uint32) == 4 ? 1 : -1]; +typedef unsigned char mz_validate_uint64[sizeof(mz_uint64) == 8 ? 1 : -1]; + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- zlib-style API's */ + +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len) +{ + mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16); + size_t block_len = buf_len % 5552; + if (!ptr) + return MZ_ADLER32_INIT; + while (buf_len) + { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) + { + s1 += ptr[0], s2 += s1; + s1 += ptr[1], s2 += s1; + s1 += ptr[2], s2 += s1; + s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; + s1 += ptr[5], s2 += s1; + s1 += ptr[6], s2 += s1; + s1 += ptr[7], s2 += s1; + } + for (; i < block_len; ++i) + s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; + buf_len -= block_len; + block_len = 5552; + } + return (s2 << 16) + s1; +} + +/* Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ */ +#if 0 + mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) + { + static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; + mz_uint32 crcu32 = (mz_uint32)crc; + if (!ptr) + return MZ_CRC32_INIT; + crcu32 = ~crcu32; + while (buf_len--) + { + mz_uint8 b = *ptr++; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; + crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; + } + return ~crcu32; + } +#elif defined(USE_EXTERNAL_MZCRC) +/* If USE_EXTERNAL_CRC is defined, an external module will export the + * mz_crc32() symbol for us to use, e.g. an SSE-accelerated version. + * Depending on the impl, it may be necessary to ~ the input/output crc values. + */ +mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len); +#else +/* Faster, but larger CPU cache footprint. + */ +mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) +{ + static const mz_uint32 s_crc_table[256] = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, + 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, + 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, + 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, + 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, + 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, + 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, + 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, + 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, + 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, + 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, + 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, + 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, + 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, + 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, + 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, + 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, + 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, + 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, + 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, + 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, + 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, + 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, + 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, + 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, + 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, + 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, + 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, + 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, + 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, + 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + mz_uint32 crc32 = (mz_uint32)crc ^ 0xFFFFFFFF; + const mz_uint8 *pByte_buf = (const mz_uint8 *)ptr; + + while (buf_len >= 4) + { + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF]; + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[1]) & 0xFF]; + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[2]) & 0xFF]; + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[3]) & 0xFF]; + pByte_buf += 4; + buf_len -= 4; + } + + while (buf_len) + { + crc32 = (crc32 >> 8) ^ s_crc_table[(crc32 ^ pByte_buf[0]) & 0xFF]; + ++pByte_buf; + --buf_len; + } + + return ~crc32; +} +#endif + +void mz_free(void *p) +{ + MZ_FREE(p); +} + +MINIZ_EXPORT void *miniz_def_alloc_func(void *opaque, size_t items, size_t size) +{ + (void)opaque, (void)items, (void)size; + return MZ_MALLOC(items * size); +} +MINIZ_EXPORT void miniz_def_free_func(void *opaque, void *address) +{ + (void)opaque, (void)address; + MZ_FREE(address); +} +MINIZ_EXPORT void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size) +{ + (void)opaque, (void)address, (void)items, (void)size; + return MZ_REALLOC(address, items * size); +} + +const char *mz_version(void) +{ + return MZ_VERSION; +} + +#ifndef MINIZ_NO_ZLIB_APIS + +int mz_deflateInit(mz_streamp pStream, int level) +{ + return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY); +} + +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy) +{ + tdefl_compressor *pComp; + mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy); + + if (!pStream) + return MZ_STREAM_ERROR; + if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))) + return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = MZ_ADLER32_INIT; + pStream->msg = NULL; + pStream->reserved = 0; + pStream->total_in = 0; + pStream->total_out = 0; + if (!pStream->zalloc) + pStream->zalloc = miniz_def_alloc_func; + if (!pStream->zfree) + pStream->zfree = miniz_def_free_func; + + pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) + return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pComp; + + if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY) + { + mz_deflateEnd(pStream); + return MZ_PARAM_ERROR; + } + + return MZ_OK; +} + +int mz_deflateReset(mz_streamp pStream) +{ + if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree)) + return MZ_STREAM_ERROR; + pStream->total_in = pStream->total_out = 0; + tdefl_init((tdefl_compressor *)pStream->state, NULL, NULL, ((tdefl_compressor *)pStream->state)->m_flags); + return MZ_OK; +} + +int mz_deflate(mz_streamp pStream, int flush) +{ + size_t in_bytes, out_bytes; + mz_ulong orig_total_in, orig_total_out; + int mz_status = MZ_OK; + + if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out)) + return MZ_STREAM_ERROR; + if (!pStream->avail_out) + return MZ_BUF_ERROR; + + if (flush == MZ_PARTIAL_FLUSH) + flush = MZ_SYNC_FLUSH; + + if (((tdefl_compressor *)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE) + return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR; + + orig_total_in = pStream->total_in; + orig_total_out = pStream->total_out; + for (;;) + { + tdefl_status defl_status; + in_bytes = pStream->avail_in; + out_bytes = pStream->avail_out; + + defl_status = tdefl_compress((tdefl_compressor *)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush); + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tdefl_get_adler32((tdefl_compressor *)pStream->state); + + pStream->next_out += (mz_uint)out_bytes; + pStream->avail_out -= (mz_uint)out_bytes; + pStream->total_out += (mz_uint)out_bytes; + + if (defl_status < 0) + { + mz_status = MZ_STREAM_ERROR; + break; + } + else if (defl_status == TDEFL_STATUS_DONE) + { + mz_status = MZ_STREAM_END; + break; + } + else if (!pStream->avail_out) + break; + else if ((!pStream->avail_in) && (flush != MZ_FINISH)) + { + if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out)) + break; + return MZ_BUF_ERROR; /* Can't make forward progress without some input. + */ + } + } + return mz_status; +} + +int mz_deflateEnd(mz_streamp pStream) +{ + if (!pStream) + return MZ_STREAM_ERROR; + if (pStream->state) + { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len) +{ + (void)pStream; + /* This is really over conservative. (And lame, but it's actually pretty tricky to compute a true upper bound given the way tdefl's blocking works.) */ + return MZ_MAX(128 + (source_len * 110) / 100, 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5); +} + +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level) +{ + int status; + mz_stream stream; + memset(&stream, 0, sizeof(stream)); + + /* In case mz_ulong is 64-bits (argh I hate longs). */ + if ((source_len | *pDest_len) > 0xFFFFFFFFU) + return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_deflateInit(&stream, level); + if (status != MZ_OK) + return status; + + status = mz_deflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) + { + mz_deflateEnd(&stream); + return (status == MZ_OK) ? MZ_BUF_ERROR : status; + } + + *pDest_len = stream.total_out; + return mz_deflateEnd(&stream); +} + +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +{ + return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION); +} + +mz_ulong mz_compressBound(mz_ulong source_len) +{ + return mz_deflateBound(NULL, source_len); +} + +typedef struct +{ + tinfl_decompressor m_decomp; + mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed; + int m_window_bits; + mz_uint8 m_dict[TINFL_LZ_DICT_SIZE]; + tinfl_status m_last_status; +} inflate_state; + +int mz_inflateInit2(mz_streamp pStream, int window_bits) +{ + inflate_state *pDecomp; + if (!pStream) + return MZ_STREAM_ERROR; + if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)) + return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = 0; + pStream->msg = NULL; + pStream->total_in = 0; + pStream->total_out = 0; + pStream->reserved = 0; + if (!pStream->zalloc) + pStream->zalloc = miniz_def_alloc_func; + if (!pStream->zfree) + pStream->zfree = miniz_def_free_func; + + pDecomp = (inflate_state *)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state)); + if (!pDecomp) + return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pDecomp; + + tinfl_init(&pDecomp->m_decomp); + pDecomp->m_dict_ofs = 0; + pDecomp->m_dict_avail = 0; + pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; + pDecomp->m_first_call = 1; + pDecomp->m_has_flushed = 0; + pDecomp->m_window_bits = window_bits; + + return MZ_OK; +} + +int mz_inflateInit(mz_streamp pStream) +{ + return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS); +} + +int mz_inflateReset(mz_streamp pStream) +{ + inflate_state *pDecomp; + if (!pStream) + return MZ_STREAM_ERROR; + + pStream->data_type = 0; + pStream->adler = 0; + pStream->msg = NULL; + pStream->total_in = 0; + pStream->total_out = 0; + pStream->reserved = 0; + + pDecomp = (inflate_state *)pStream->state; + + tinfl_init(&pDecomp->m_decomp); + pDecomp->m_dict_ofs = 0; + pDecomp->m_dict_avail = 0; + pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; + pDecomp->m_first_call = 1; + pDecomp->m_has_flushed = 0; + /* pDecomp->m_window_bits = window_bits */; + + return MZ_OK; +} + +int mz_inflate(mz_streamp pStream, int flush) +{ + inflate_state *pState; + mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32; + size_t in_bytes, out_bytes, orig_avail_in; + tinfl_status status; + + if ((!pStream) || (!pStream->state)) + return MZ_STREAM_ERROR; + if (flush == MZ_PARTIAL_FLUSH) + flush = MZ_SYNC_FLUSH; + if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH)) + return MZ_STREAM_ERROR; + + pState = (inflate_state *)pStream->state; + if (pState->m_window_bits > 0) + decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER; + orig_avail_in = pStream->avail_in; + + first_call = pState->m_first_call; + pState->m_first_call = 0; + if (pState->m_last_status < 0) + return MZ_DATA_ERROR; + + if (pState->m_has_flushed && (flush != MZ_FINISH)) + return MZ_STREAM_ERROR; + pState->m_has_flushed |= (flush == MZ_FINISH); + + if ((flush == MZ_FINISH) && (first_call)) + { + /* MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file. */ + decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF; + in_bytes = pStream->avail_in; + out_bytes = pStream->avail_out; + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags); + pState->m_last_status = status; + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tinfl_get_adler32(&pState->m_decomp); + pStream->next_out += (mz_uint)out_bytes; + pStream->avail_out -= (mz_uint)out_bytes; + pStream->total_out += (mz_uint)out_bytes; + + if (status < 0) + return MZ_DATA_ERROR; + else if (status != TINFL_STATUS_DONE) + { + pState->m_last_status = TINFL_STATUS_FAILED; + return MZ_BUF_ERROR; + } + return MZ_STREAM_END; + } + /* flush != MZ_FINISH then we must assume there's more input. */ + if (flush != MZ_FINISH) + decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT; + + if (pState->m_dict_avail) + { + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; + pStream->avail_out -= n; + pStream->total_out += n; + pState->m_dict_avail -= n; + pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; + } + + for (;;) + { + in_bytes = pStream->avail_in; + out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs; + + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags); + pState->m_last_status = status; + + pStream->next_in += (mz_uint)in_bytes; + pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tinfl_get_adler32(&pState->m_decomp); + + pState->m_dict_avail = (mz_uint)out_bytes; + + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; + pStream->avail_out -= n; + pStream->total_out += n; + pState->m_dict_avail -= n; + pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + + if (status < 0) + return MZ_DATA_ERROR; /* Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). */ + else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in)) + return MZ_BUF_ERROR; /* Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH. */ + else if (flush == MZ_FINISH) + { + /* The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH. */ + if (status == TINFL_STATUS_DONE) + return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END; + /* status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If there's no more room left in the output buffer then something is wrong. */ + else if (!pStream->avail_out) + return MZ_BUF_ERROR; + } + else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail)) + break; + } + + return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; +} + +int mz_inflateEnd(mz_streamp pStream) +{ + if (!pStream) + return MZ_STREAM_ERROR; + if (pStream->state) + { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} +int mz_uncompress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong *pSource_len) +{ + mz_stream stream; + int status; + memset(&stream, 0, sizeof(stream)); + + /* In case mz_ulong is 64-bits (argh I hate longs). */ + if ((*pSource_len | *pDest_len) > 0xFFFFFFFFU) + return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)*pSource_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_inflateInit(&stream); + if (status != MZ_OK) + return status; + + status = mz_inflate(&stream, MZ_FINISH); + *pSource_len = *pSource_len - stream.avail_in; + if (status != MZ_STREAM_END) + { + mz_inflateEnd(&stream); + return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status; + } + *pDest_len = stream.total_out; + + return mz_inflateEnd(&stream); +} + +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +{ + return mz_uncompress2(pDest, pDest_len, pSource, &source_len); +} + +const char *mz_error(int err) +{ + static struct + { + int m_err; + const char *m_pDesc; + } s_error_descs[] = + { + { MZ_OK, "" }, { MZ_STREAM_END, "stream end" }, { MZ_NEED_DICT, "need dictionary" }, { MZ_ERRNO, "file error" }, { MZ_STREAM_ERROR, "stream error" }, { MZ_DATA_ERROR, "data error" }, { MZ_MEM_ERROR, "out of memory" }, { MZ_BUF_ERROR, "buf error" }, { MZ_VERSION_ERROR, "version error" }, { MZ_PARAM_ERROR, "parameter error" } + }; + mz_uint i; + for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i) + if (s_error_descs[i].m_err == err) + return s_error_descs[i].m_pDesc; + return NULL; +} + +#endif /*MINIZ_NO_ZLIB_APIS */ + +#ifdef __cplusplus +} +#endif + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +*/ +/************************************************************************** + * + * Copyright 2013-2014 RAD Game Tools and Valve Software + * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + + + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- Low-level Compression (independent from all decompression API's) */ + +/* Purposely making these tables static for faster init and thread safety. */ +static const mz_uint16 s_tdefl_len_sym[256] = + { + 257, 258, 259, 260, 261, 262, 263, 264, 265, 265, 266, 266, 267, 267, 268, 268, 269, 269, 269, 269, 270, 270, 270, 270, 271, 271, 271, 271, 272, 272, 272, 272, + 273, 273, 273, 273, 273, 273, 273, 273, 274, 274, 274, 274, 274, 274, 274, 274, 275, 275, 275, 275, 275, 275, 275, 275, 276, 276, 276, 276, 276, 276, 276, 276, + 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 277, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, + 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 279, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, 280, + 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, 281, + 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, 282, + 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, 283, + 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 284, 285 + }; + +static const mz_uint8 s_tdefl_len_extra[256] = + { + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0 + }; + +static const mz_uint8 s_tdefl_small_dist_sym[512] = + { + 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17 + }; + +static const mz_uint8 s_tdefl_small_dist_extra[512] = + { + 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7 + }; + +static const mz_uint8 s_tdefl_large_dist_sym[128] = + { + 0, 0, 18, 19, 20, 20, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29 + }; + +static const mz_uint8 s_tdefl_large_dist_extra[128] = + { + 0, 0, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13 + }; + +/* Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted values. */ +typedef struct +{ + mz_uint16 m_key, m_sym_index; +} tdefl_sym_freq; +static tdefl_sym_freq *tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq *pSyms0, tdefl_sym_freq *pSyms1) +{ + mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2]; + tdefl_sym_freq *pCur_syms = pSyms0, *pNew_syms = pSyms1; + MZ_CLEAR_OBJ(hist); + for (i = 0; i < num_syms; i++) + { + mz_uint freq = pSyms0[i].m_key; + hist[freq & 0xFF]++; + hist[256 + ((freq >> 8) & 0xFF)]++; + } + while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) + total_passes--; + for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8) + { + const mz_uint32 *pHist = &hist[pass << 8]; + mz_uint offsets[256], cur_ofs = 0; + for (i = 0; i < 256; i++) + { + offsets[i] = cur_ofs; + cur_ofs += pHist[i]; + } + for (i = 0; i < num_syms; i++) + pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i]; + { + tdefl_sym_freq *t = pCur_syms; + pCur_syms = pNew_syms; + pNew_syms = t; + } + } + return pCur_syms; +} + +/* tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. */ +static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n) +{ + int root, leaf, next, avbl, used, dpth; + if (n == 0) + return; + else if (n == 1) + { + A[0].m_key = 1; + return; + } + A[0].m_key += A[1].m_key; + root = 0; + leaf = 2; + for (next = 1; next < n - 1; next++) + { + if (leaf >= n || A[root].m_key < A[leaf].m_key) + { + A[next].m_key = A[root].m_key; + A[root++].m_key = (mz_uint16)next; + } + else + A[next].m_key = A[leaf++].m_key; + if (leaf >= n || (root < next && A[root].m_key < A[leaf].m_key)) + { + A[next].m_key = (mz_uint16)(A[next].m_key + A[root].m_key); + A[root++].m_key = (mz_uint16)next; + } + else + A[next].m_key = (mz_uint16)(A[next].m_key + A[leaf++].m_key); + } + A[n - 2].m_key = 0; + for (next = n - 3; next >= 0; next--) + A[next].m_key = A[A[next].m_key].m_key + 1; + avbl = 1; + used = dpth = 0; + root = n - 2; + next = n - 1; + while (avbl > 0) + { + while (root >= 0 && (int)A[root].m_key == dpth) + { + used++; + root--; + } + while (avbl > used) + { + A[next--].m_key = (mz_uint16)(dpth); + avbl--; + } + avbl = 2 * used; + dpth++; + used = 0; + } +} + +/* Limits canonical Huffman code table's max code size. */ +enum +{ + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 +}; +static void tdefl_huffman_enforce_max_code_size(int *pNum_codes, int code_list_len, int max_code_size) +{ + int i; + mz_uint32 total = 0; + if (code_list_len <= 1) + return; + for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) + pNum_codes[max_code_size] += pNum_codes[i]; + for (i = max_code_size; i > 0; i--) + total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i)); + while (total != (1UL << max_code_size)) + { + pNum_codes[max_code_size]--; + for (i = max_code_size - 1; i > 0; i--) + if (pNum_codes[i]) + { + pNum_codes[i]--; + pNum_codes[i + 1] += 2; + break; + } + total--; + } +} + +static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int table_len, int code_size_limit, int static_table) +{ + int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE]; + mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; + MZ_CLEAR_OBJ(num_codes); + if (static_table) + { + for (i = 0; i < table_len; i++) + num_codes[d->m_huff_code_sizes[table_num][i]]++; + } + else + { + tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], *pSyms; + int num_used_syms = 0; + const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0]; + for (i = 0; i < table_len; i++) + if (pSym_count[i]) + { + syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i]; + syms0[num_used_syms++].m_sym_index = (mz_uint16)i; + } + + pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1); + tdefl_calculate_minimum_redundancy(pSyms, num_used_syms); + + for (i = 0; i < num_used_syms; i++) + num_codes[pSyms[i].m_key]++; + + tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit); + + MZ_CLEAR_OBJ(d->m_huff_code_sizes[table_num]); + MZ_CLEAR_OBJ(d->m_huff_codes[table_num]); + for (i = 1, j = num_used_syms; i <= code_size_limit; i++) + for (l = num_codes[i]; l > 0; l--) + d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i); + } + + next_code[1] = 0; + for (j = 0, i = 2; i <= code_size_limit; i++) + next_code[i] = j = ((j + num_codes[i - 1]) << 1); + + for (i = 0; i < table_len; i++) + { + mz_uint rev_code = 0, code, code_size; + if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) + continue; + code = next_code[code_size]++; + for (l = code_size; l > 0; l--, code >>= 1) + rev_code = (rev_code << 1) | (code & 1); + d->m_huff_codes[table_num][i] = (mz_uint16)rev_code; + } +} + +#define TDEFL_PUT_BITS(b, l) \ + do \ + { \ + mz_uint bits = b; \ + mz_uint len = l; \ + MZ_ASSERT(bits <= ((1U << len) - 1U)); \ + d->m_bit_buffer |= (bits << d->m_bits_in); \ + d->m_bits_in += len; \ + while (d->m_bits_in >= 8) \ + { \ + if (d->m_pOutput_buf < d->m_pOutput_buf_end) \ + *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \ + d->m_bit_buffer >>= 8; \ + d->m_bits_in -= 8; \ + } \ + } \ + MZ_MACRO_END + +#define TDEFL_RLE_PREV_CODE_SIZE() \ + { \ + if (rle_repeat_count) \ + { \ + if (rle_repeat_count < 3) \ + { \ + d->m_huff_count[2][prev_code_size] = (mz_uint16)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \ + while (rle_repeat_count--) \ + packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \ + } \ + else \ + { \ + d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1); \ + packed_code_sizes[num_packed_code_sizes++] = 16; \ + packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_repeat_count - 3); \ + } \ + rle_repeat_count = 0; \ + } \ + } + +#define TDEFL_RLE_ZERO_CODE_SIZE() \ + { \ + if (rle_z_count) \ + { \ + if (rle_z_count < 3) \ + { \ + d->m_huff_count[2][0] = (mz_uint16)(d->m_huff_count[2][0] + rle_z_count); \ + while (rle_z_count--) \ + packed_code_sizes[num_packed_code_sizes++] = 0; \ + } \ + else if (rle_z_count <= 10) \ + { \ + d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1); \ + packed_code_sizes[num_packed_code_sizes++] = 17; \ + packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 3); \ + } \ + else \ + { \ + d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1); \ + packed_code_sizes[num_packed_code_sizes++] = 18; \ + packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 11); \ + } \ + rle_z_count = 0; \ + } \ + } + +static mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + +static void tdefl_start_dynamic_block(tdefl_compressor *d) +{ + int num_lit_codes, num_dist_codes, num_bit_lengths; + mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index; + mz_uint8 code_sizes_to_pack[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], packed_code_sizes[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], prev_code_size = 0xFF; + + d->m_huff_count[0][256] = 1; + + tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE); + tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE); + + for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) + if (d->m_huff_code_sizes[0][num_lit_codes - 1]) + break; + for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) + if (d->m_huff_code_sizes[1][num_dist_codes - 1]) + break; + + memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes); + memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes); + total_code_sizes_to_pack = num_lit_codes + num_dist_codes; + num_packed_code_sizes = 0; + rle_z_count = 0; + rle_repeat_count = 0; + + memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2); + for (i = 0; i < total_code_sizes_to_pack; i++) + { + mz_uint8 code_size = code_sizes_to_pack[i]; + if (!code_size) + { + TDEFL_RLE_PREV_CODE_SIZE(); + if (++rle_z_count == 138) + { + TDEFL_RLE_ZERO_CODE_SIZE(); + } + } + else + { + TDEFL_RLE_ZERO_CODE_SIZE(); + if (code_size != prev_code_size) + { + TDEFL_RLE_PREV_CODE_SIZE(); + d->m_huff_count[2][code_size] = (mz_uint16)(d->m_huff_count[2][code_size] + 1); + packed_code_sizes[num_packed_code_sizes++] = code_size; + } + else if (++rle_repeat_count == 6) + { + TDEFL_RLE_PREV_CODE_SIZE(); + } + } + prev_code_size = code_size; + } + if (rle_repeat_count) + { + TDEFL_RLE_PREV_CODE_SIZE(); + } + else + { + TDEFL_RLE_ZERO_CODE_SIZE(); + } + + tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE); + + TDEFL_PUT_BITS(2, 2); + + TDEFL_PUT_BITS(num_lit_codes - 257, 5); + TDEFL_PUT_BITS(num_dist_codes - 1, 5); + + for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) + if (d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]]) + break; + num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1)); + TDEFL_PUT_BITS(num_bit_lengths - 4, 4); + for (i = 0; (int)i < num_bit_lengths; i++) + TDEFL_PUT_BITS(d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3); + + for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes;) + { + mz_uint code = packed_code_sizes[packed_code_sizes_index++]; + MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2); + TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]); + if (code >= 16) + TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]); + } +} + +static void tdefl_start_static_block(tdefl_compressor *d) +{ + mz_uint i; + mz_uint8 *p = &d->m_huff_code_sizes[0][0]; + + for (i = 0; i <= 143; ++i) + *p++ = 8; + for (; i <= 255; ++i) + *p++ = 9; + for (; i <= 279; ++i) + *p++ = 7; + for (; i <= 287; ++i) + *p++ = 8; + + memset(d->m_huff_code_sizes[1], 5, 32); + + tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE); + tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE); + + TDEFL_PUT_BITS(1, 2); +} + +static const mz_uint mz_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) +{ + mz_uint flags; + mz_uint8 *pLZ_codes; + mz_uint8 *pOutput_buf = d->m_pOutput_buf; + mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf; + mz_uint64 bit_buffer = d->m_bit_buffer; + mz_uint bits_in = d->m_bits_in; + +#define TDEFL_PUT_BITS_FAST(b, l) \ + { \ + bit_buffer |= (((mz_uint64)(b)) << bits_in); \ + bits_in += (l); \ + } + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; flags >>= 1) + { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + + if (flags & 1) + { + mz_uint s0, s1, n0, n1, sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], match_dist = *(const mz_uint16 *)(pLZ_codes + 1); + pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); + + /* This sequence coaxes MSVC into using cmov's vs. jmp's. */ + s0 = s_tdefl_small_dist_sym[match_dist & 511]; + n0 = s_tdefl_small_dist_extra[match_dist & 511]; + s1 = s_tdefl_large_dist_sym[match_dist >> 8]; + n1 = s_tdefl_large_dist_extra[match_dist >> 8]; + sym = (match_dist < 512) ? s0 : s1; + num_extra_bits = (match_dist < 512) ? n0 : n1; + + MZ_ASSERT(d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); + } + else + { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) + { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) + { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + } + } + } + + if (pOutput_buf >= d->m_pOutput_buf_end) + return MZ_FALSE; + + *(mz_uint64 *)pOutput_buf = bit_buffer; + pOutput_buf += (bits_in >> 3); + bit_buffer >>= (bits_in & ~7); + bits_in &= 7; + } + +#undef TDEFL_PUT_BITS_FAST + + d->m_pOutput_buf = pOutput_buf; + d->m_bits_in = 0; + d->m_bit_buffer = 0; + + while (bits_in) + { + mz_uint32 n = MZ_MIN(bits_in, 16); + TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n); + bit_buffer >>= n; + bits_in -= n; + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#else +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) +{ + mz_uint flags; + mz_uint8 *pLZ_codes; + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; flags >>= 1) + { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + if (flags & 1) + { + mz_uint sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); + pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); + + if (match_dist < 512) + { + sym = s_tdefl_small_dist_sym[match_dist]; + num_extra_bits = s_tdefl_small_dist_extra[match_dist]; + } + else + { + sym = s_tdefl_large_dist_sym[match_dist >> 8]; + num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8]; + } + MZ_ASSERT(d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); + } + else + { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + } + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS */ + +static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block) +{ + if (static_block) + tdefl_start_static_block(d); + else + tdefl_start_dynamic_block(d); + return tdefl_compress_lz_codes(d); +} + +static int tdefl_flush_block(tdefl_compressor *d, int flush) +{ + mz_uint saved_bit_buf, saved_bits_in; + mz_uint8 *pSaved_output_buf; + mz_bool comp_block_succeeded = MZ_FALSE; + int n, use_raw_block = ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size; + mz_uint8 *pOutput_buf_start = ((d->m_pPut_buf_func == NULL) && ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) ? ((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs) : d->m_output_buf; + + d->m_pOutput_buf = pOutput_buf_start; + d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16; + + MZ_ASSERT(!d->m_output_flush_remaining); + d->m_output_flush_ofs = 0; + d->m_output_flush_remaining = 0; + + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left); + d->m_pLZ_code_buf -= (d->m_num_flags_left == 8); + + if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index)) + { + TDEFL_PUT_BITS(0x78, 8); + TDEFL_PUT_BITS(0x01, 8); + } + + TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1); + + pSaved_output_buf = d->m_pOutput_buf; + saved_bit_buf = d->m_bit_buffer; + saved_bits_in = d->m_bits_in; + + if (!use_raw_block) + comp_block_succeeded = tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || (d->m_total_lz_bytes < 48)); + + /* If the block gets expanded, forget the current contents of the output buffer and send a raw block instead. */ + if (((use_raw_block) || ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= d->m_total_lz_bytes))) && + ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size)) + { + mz_uint i; + d->m_pOutput_buf = pSaved_output_buf; + d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + TDEFL_PUT_BITS(0, 2); + if (d->m_bits_in) + { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF) + { + TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16); + } + for (i = 0; i < d->m_total_lz_bytes; ++i) + { + TDEFL_PUT_BITS(d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], 8); + } + } + /* Check for the extremely unlikely (if not impossible) case of the compressed block not fitting into the output buffer when using dynamic codes. */ + else if (!comp_block_succeeded) + { + d->m_pOutput_buf = pSaved_output_buf; + d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + tdefl_compress_block(d, MZ_TRUE); + } + + if (flush) + { + if (flush == TDEFL_FINISH) + { + if (d->m_bits_in) + { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER) + { + mz_uint i, a = d->m_adler32; + for (i = 0; i < 4; i++) + { + TDEFL_PUT_BITS((a >> 24) & 0xFF, 8); + a <<= 8; + } + } + } + else + { + mz_uint i, z = 0; + TDEFL_PUT_BITS(0, 3); + if (d->m_bits_in) + { + TDEFL_PUT_BITS(0, 8 - d->m_bits_in); + } + for (i = 2; i; --i, z ^= 0xFFFF) + { + TDEFL_PUT_BITS(z & 0xFFFF, 16); + } + } + } + + MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end); + + memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; + d->m_pLZ_flags = d->m_lz_code_buf; + d->m_num_flags_left = 8; + d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes; + d->m_total_lz_bytes = 0; + d->m_block_index++; + + if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0) + { + if (d->m_pPut_buf_func) + { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user)) + return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED); + } + else if (pOutput_buf_start == d->m_output_buf) + { + int bytes_to_copy = (int)MZ_MIN((size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs)); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, bytes_to_copy); + d->m_out_buf_ofs += bytes_to_copy; + if ((n -= bytes_to_copy) != 0) + { + d->m_output_flush_ofs = bytes_to_copy; + d->m_output_flush_remaining = n; + } + } + else + { + d->m_out_buf_ofs += n; + } + } + + return d->m_output_flush_remaining; +} + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES +#ifdef MINIZ_UNALIGNED_USE_MEMCPY +static mz_uint16 TDEFL_READ_UNALIGNED_WORD(const mz_uint8* p) +{ + mz_uint16 ret; + memcpy(&ret, p, sizeof(mz_uint16)); + return ret; +} +static mz_uint16 TDEFL_READ_UNALIGNED_WORD2(const mz_uint16* p) +{ + mz_uint16 ret; + memcpy(&ret, p, sizeof(mz_uint16)); + return ret; +} +#else +#define TDEFL_READ_UNALIGNED_WORD(p) *(const mz_uint16 *)(p) +#define TDEFL_READ_UNALIGNED_WORD2(p) *(const mz_uint16 *)(p) +#endif +static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) +{ + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint16 *s = (const mz_uint16 *)(d->m_dict + pos), *p, *q; + mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), s01 = TDEFL_READ_UNALIGNED_WORD2(s); + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); + if (max_match_len <= match_len) + return; + for (;;) + { + for (;;) + { + if (--num_probes_left == 0) + return; +#define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ + return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01) \ + break; + TDEFL_PROBE; + TDEFL_PROBE; + TDEFL_PROBE; + } + if (!dist) + break; + q = (const mz_uint16 *)(d->m_dict + probe_pos); + if (TDEFL_READ_UNALIGNED_WORD2(q) != s01) + continue; + p = s; + probe_len = 32; + do + { + } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && + (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0)); + if (!probe_len) + { + *pMatch_dist = dist; + *pMatch_len = MZ_MIN(max_match_len, (mz_uint)TDEFL_MAX_MATCH_LEN); + break; + } + else if ((probe_len = ((mz_uint)(p - s) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q)) > match_len) + { + *pMatch_dist = dist; + if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == max_match_len) + break; + c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]); + } + } +} +#else +static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) +{ + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint8 *s = d->m_dict + pos, *p, *q; + mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1]; + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); + if (max_match_len <= match_len) + return; + for (;;) + { + for (;;) + { + if (--num_probes_left == 0) + return; +#define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) \ + return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if ((d->m_dict[probe_pos + match_len] == c0) && (d->m_dict[probe_pos + match_len - 1] == c1)) \ + break; + TDEFL_PROBE; + TDEFL_PROBE; + TDEFL_PROBE; + } + if (!dist) + break; + p = s; + q = d->m_dict + probe_pos; + for (probe_len = 0; probe_len < max_match_len; probe_len++) + if (*p++ != *q++) + break; + if (probe_len > match_len) + { + *pMatch_dist = dist; + if ((*pMatch_len = match_len = probe_len) == max_match_len) + return; + c0 = d->m_dict[pos + match_len]; + c1 = d->m_dict[pos + match_len - 1]; + } + } +} +#endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES */ + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN +#ifdef MINIZ_UNALIGNED_USE_MEMCPY +static mz_uint32 TDEFL_READ_UNALIGNED_WORD32(const mz_uint8* p) +{ + mz_uint32 ret; + memcpy(&ret, p, sizeof(mz_uint32)); + return ret; +} +#else +#define TDEFL_READ_UNALIGNED_WORD32(p) *(const mz_uint32 *)(p) +#endif +static mz_bool tdefl_compress_fast(tdefl_compressor *d) +{ + /* Faster, minimally featured LZRW1-style match+parse loop with better register utilization. Intended for applications where raw throughput is valued more highly than ratio. */ + mz_uint lookahead_pos = d->m_lookahead_pos, lookahead_size = d->m_lookahead_size, dict_size = d->m_dict_size, total_lz_bytes = d->m_total_lz_bytes, num_flags_left = d->m_num_flags_left; + mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags; + mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + + while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size))) + { + const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096; + mz_uint dst_pos = (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size); + d->m_src_buf_left -= num_bytes_to_process; + lookahead_size += num_bytes_to_process; + + while (num_bytes_to_process) + { + mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process); + memcpy(d->m_dict + dst_pos, d->m_pSrc, n); + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos)); + d->m_pSrc += n; + dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK; + num_bytes_to_process -= n; + } + + dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size); + if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE)) + break; + + while (lookahead_size >= 4) + { + mz_uint cur_match_dist, cur_match_len = 1; + mz_uint8 *pCur_dict = d->m_dict + cur_pos; + mz_uint first_trigram = TDEFL_READ_UNALIGNED_WORD32(pCur_dict) & 0xFFFFFF; + mz_uint hash = (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & TDEFL_LEVEL1_HASH_SIZE_MASK; + mz_uint probe_pos = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)lookahead_pos; + + if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= dict_size) && ((TDEFL_READ_UNALIGNED_WORD32(d->m_dict + (probe_pos &= TDEFL_LZ_DICT_SIZE_MASK)) & 0xFFFFFF) == first_trigram)) + { + const mz_uint16 *p = (const mz_uint16 *)pCur_dict; + const mz_uint16 *q = (const mz_uint16 *)(d->m_dict + probe_pos); + mz_uint32 probe_len = 32; + do + { + } while ((TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && + (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (TDEFL_READ_UNALIGNED_WORD2(++p) == TDEFL_READ_UNALIGNED_WORD2(++q)) && (--probe_len > 0)); + cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q); + if (!probe_len) + cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0; + + if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || ((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U))) + { + cur_match_len = 1; + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } + else + { + mz_uint32 s0, s1; + cur_match_len = MZ_MIN(cur_match_len, lookahead_size); + + MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 1) && (cur_match_dist <= TDEFL_LZ_DICT_SIZE)); + + cur_match_dist--; + + pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN); +#ifdef MINIZ_UNALIGNED_USE_MEMCPY + memcpy(&pLZ_code_buf[1], &cur_match_dist, sizeof(cur_match_dist)); +#else + *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist; +#endif + pLZ_code_buf += 3; + *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80); + + s0 = s_tdefl_small_dist_sym[cur_match_dist & 511]; + s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8]; + d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++; + + d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - TDEFL_MIN_MATCH_LEN]]++; + } + } + else + { + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } + + if (--num_flags_left == 0) + { + num_flags_left = 8; + pLZ_flags = pLZ_code_buf++; + } + + total_lz_bytes += cur_match_len; + lookahead_pos += cur_match_len; + dict_size = MZ_MIN(dict_size + cur_match_len, (mz_uint)TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK; + MZ_ASSERT(lookahead_size >= cur_match_len); + lookahead_size -= cur_match_len; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) + { + int n; + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; + pLZ_code_buf = d->m_pLZ_code_buf; + pLZ_flags = d->m_pLZ_flags; + num_flags_left = d->m_num_flags_left; + } + } + + while (lookahead_size) + { + mz_uint8 lit = d->m_dict[cur_pos]; + + total_lz_bytes++; + *pLZ_code_buf++ = lit; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + if (--num_flags_left == 0) + { + num_flags_left = 8; + pLZ_flags = pLZ_code_buf++; + } + + d->m_huff_count[0][lit]++; + + lookahead_pos++; + dict_size = MZ_MIN(dict_size + 1, (mz_uint)TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; + lookahead_size--; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) + { + int n; + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; + pLZ_code_buf = d->m_pLZ_code_buf; + pLZ_flags = d->m_pLZ_flags; + num_flags_left = d->m_num_flags_left; + } + } + } + + d->m_lookahead_pos = lookahead_pos; + d->m_lookahead_size = lookahead_size; + d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; + d->m_pLZ_code_buf = pLZ_code_buf; + d->m_pLZ_flags = pLZ_flags; + d->m_num_flags_left = num_flags_left; + return MZ_TRUE; +} +#endif /* MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */ + +static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, mz_uint8 lit) +{ + d->m_total_lz_bytes++; + *d->m_pLZ_code_buf++ = lit; + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1); + if (--d->m_num_flags_left == 0) + { + d->m_num_flags_left = 8; + d->m_pLZ_flags = d->m_pLZ_code_buf++; + } + d->m_huff_count[0][lit]++; +} + +static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist) +{ + mz_uint32 s0, s1; + + MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && (match_dist <= TDEFL_LZ_DICT_SIZE)); + + d->m_total_lz_bytes += match_len; + + d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN); + + match_dist -= 1; + d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF); + d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8); + d->m_pLZ_code_buf += 3; + + *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80); + if (--d->m_num_flags_left == 0) + { + d->m_num_flags_left = 8; + d->m_pLZ_flags = d->m_pLZ_code_buf++; + } + + s0 = s_tdefl_small_dist_sym[match_dist & 511]; + s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127]; + d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++; + d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; +} + +static mz_bool tdefl_compress_normal(tdefl_compressor *d) +{ + const mz_uint8 *pSrc = d->m_pSrc; + size_t src_buf_left = d->m_src_buf_left; + tdefl_flush flush = d->m_flush; + + while ((src_buf_left) || ((flush) && (d->m_lookahead_size))) + { + mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos; + /* Update dictionary and hash chains. Keeps the lookahead size equal to TDEFL_MAX_MATCH_LEN. */ + if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1)) + { + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK, ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2; + mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK]; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size); + const mz_uint8 *pSrc_end = pSrc + num_bytes_to_process; + src_buf_left -= num_bytes_to_process; + d->m_lookahead_size += num_bytes_to_process; + while (pSrc != pSrc_end) + { + mz_uint8 c = *pSrc++; + d->m_dict[dst_pos] = c; + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)(ins_pos); + dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; + ins_pos++; + } + } + else + { + while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + { + mz_uint8 c = *pSrc++; + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + src_buf_left--; + d->m_dict[dst_pos] = c; + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN) + { + mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2; + mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << (TDEFL_LZ_HASH_SHIFT * 2)) ^ (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)(ins_pos); + } + } + } + d->m_dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size); + if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + break; + + /* Simple lazy/greedy parsing state machine. */ + len_to_move = 1; + cur_match_dist = 0; + cur_match_len = d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1); + cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS)) + { + if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) + { + mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK]; + cur_match_len = 0; + while (cur_match_len < d->m_lookahead_size) + { + if (d->m_dict[cur_pos + cur_match_len] != c) + break; + cur_match_len++; + } + if (cur_match_len < TDEFL_MIN_MATCH_LEN) + cur_match_len = 0; + else + cur_match_dist = 1; + } + } + else + { + tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, d->m_lookahead_size, &cur_match_dist, &cur_match_len); + } + if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U * 1024U)) || (cur_pos == cur_match_dist) || ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5))) + { + cur_match_dist = cur_match_len = 0; + } + if (d->m_saved_match_len) + { + if (cur_match_len > d->m_saved_match_len) + { + tdefl_record_literal(d, (mz_uint8)d->m_saved_lit); + if (cur_match_len >= 128) + { + tdefl_record_match(d, cur_match_len, cur_match_dist); + d->m_saved_match_len = 0; + len_to_move = cur_match_len; + } + else + { + d->m_saved_lit = d->m_dict[cur_pos]; + d->m_saved_match_dist = cur_match_dist; + d->m_saved_match_len = cur_match_len; + } + } + else + { + tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist); + len_to_move = d->m_saved_match_len - 1; + d->m_saved_match_len = 0; + } + } + else if (!cur_match_dist) + tdefl_record_literal(d, d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]); + else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || (cur_match_len >= 128)) + { + tdefl_record_match(d, cur_match_len, cur_match_dist); + len_to_move = cur_match_len; + } + else + { + d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]; + d->m_saved_match_dist = cur_match_dist; + d->m_saved_match_len = cur_match_len; + } + /* Move the lookahead forward by len_to_move bytes. */ + d->m_lookahead_pos += len_to_move; + MZ_ASSERT(d->m_lookahead_size >= len_to_move); + d->m_lookahead_size -= len_to_move; + d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, (mz_uint)TDEFL_LZ_DICT_SIZE); + /* Check if it's time to flush the current LZ codes to the internal output buffer. */ + if ((d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) || + ((d->m_total_lz_bytes > 31 * 1024) && (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= d->m_total_lz_bytes) || (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS)))) + { + int n; + d->m_pSrc = pSrc; + d->m_src_buf_left = src_buf_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + } + } + + d->m_pSrc = pSrc; + d->m_src_buf_left = src_buf_left; + return MZ_TRUE; +} + +static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d) +{ + if (d->m_pIn_buf_size) + { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + } + + if (d->m_pOut_buf_size) + { + size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, d->m_output_flush_remaining); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf + d->m_output_flush_ofs, n); + d->m_output_flush_ofs += (mz_uint)n; + d->m_output_flush_remaining -= (mz_uint)n; + d->m_out_buf_ofs += n; + + *d->m_pOut_buf_size = d->m_out_buf_ofs; + } + + return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE : TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush) +{ + if (!d) + { + if (pIn_buf_size) + *pIn_buf_size = 0; + if (pOut_buf_size) + *pOut_buf_size = 0; + return TDEFL_STATUS_BAD_PARAM; + } + + d->m_pIn_buf = pIn_buf; + d->m_pIn_buf_size = pIn_buf_size; + d->m_pOut_buf = pOut_buf; + d->m_pOut_buf_size = pOut_buf_size; + d->m_pSrc = (const mz_uint8 *)(pIn_buf); + d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0; + d->m_out_buf_ofs = 0; + d->m_flush = flush; + + if (((d->m_pPut_buf_func != NULL) == ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || (d->m_prev_return_status != TDEFL_STATUS_OKAY) || + (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || (pIn_buf_size && *pIn_buf_size && !pIn_buf) || (pOut_buf_size && *pOut_buf_size && !pOut_buf)) + { + if (pIn_buf_size) + *pIn_buf_size = 0; + if (pOut_buf_size) + *pOut_buf_size = 0; + return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM); + } + d->m_wants_to_finish |= (flush == TDEFL_FINISH); + + if ((d->m_output_flush_remaining) || (d->m_finished)) + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) && + ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) && + ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | TDEFL_RLE_MATCHES)) == 0)) + { + if (!tdefl_compress_fast(d)) + return d->m_prev_return_status; + } + else +#endif /* #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN */ + { + if (!tdefl_compress_normal(d)) + return d->m_prev_return_status; + } + + if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && (pIn_buf)) + d->m_adler32 = (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, d->m_pSrc - (const mz_uint8 *)pIn_buf); + + if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && (!d->m_output_flush_remaining)) + { + if (tdefl_flush_block(d, flush) < 0) + return d->m_prev_return_status; + d->m_finished = (flush == TDEFL_FINISH); + if (flush == TDEFL_FULL_FLUSH) + { + MZ_CLEAR_OBJ(d->m_hash); + MZ_CLEAR_OBJ(d->m_next); + d->m_dict_size = 0; + } + } + + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); +} + +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush) +{ + MZ_ASSERT(d->m_pPut_buf_func); + return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush); +} + +tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + d->m_pPut_buf_func = pPut_buf_func; + d->m_pPut_buf_user = pPut_buf_user; + d->m_flags = (mz_uint)(flags); + d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3; + d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0; + d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3; + if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) + MZ_CLEAR_OBJ(d->m_hash); + d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0; + d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0; + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; + d->m_pLZ_flags = d->m_lz_code_buf; + *d->m_pLZ_flags = 0; + d->m_num_flags_left = 8; + d->m_pOutput_buf = d->m_output_buf; + d->m_pOutput_buf_end = d->m_output_buf; + d->m_prev_return_status = TDEFL_STATUS_OKAY; + d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0; + d->m_adler32 = 1; + d->m_pIn_buf = NULL; + d->m_pOut_buf = NULL; + d->m_pIn_buf_size = NULL; + d->m_pOut_buf_size = NULL; + d->m_flush = TDEFL_NO_FLUSH; + d->m_pSrc = NULL; + d->m_src_buf_left = 0; + d->m_out_buf_ofs = 0; + if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) + MZ_CLEAR_OBJ(d->m_dict); + memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + return TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d) +{ + return d->m_prev_return_status; +} + +mz_uint32 tdefl_get_adler32(tdefl_compressor *d) +{ + return d->m_adler32; +} + +mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + tdefl_compressor *pComp; + mz_bool succeeded; + if (((buf_len) && (!pBuf)) || (!pPut_buf_func)) + return MZ_FALSE; + pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); + if (!pComp) + return MZ_FALSE; + succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY); + succeeded = succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE); + MZ_FREE(pComp); + return succeeded; +} + +typedef struct +{ + size_t m_size, m_capacity; + mz_uint8 *m_pBuf; + mz_bool m_expandable; +} tdefl_output_buffer; + +static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, void *pUser) +{ + tdefl_output_buffer *p = (tdefl_output_buffer *)pUser; + size_t new_size = p->m_size + len; + if (new_size > p->m_capacity) + { + size_t new_capacity = p->m_capacity; + mz_uint8 *pNew_buf; + if (!p->m_expandable) + return MZ_FALSE; + do + { + new_capacity = MZ_MAX(128U, new_capacity << 1U); + } while (new_size > new_capacity); + pNew_buf = (mz_uint8 *)MZ_REALLOC(p->m_pBuf, new_capacity); + if (!pNew_buf) + return MZ_FALSE; + p->m_pBuf = pNew_buf; + p->m_capacity = new_capacity; + } + memcpy((mz_uint8 *)p->m_pBuf + p->m_size, pBuf, len); + p->m_size = new_size; + return MZ_TRUE; +} + +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) +{ + tdefl_output_buffer out_buf; + MZ_CLEAR_OBJ(out_buf); + if (!pOut_len) + return MZ_FALSE; + else + *pOut_len = 0; + out_buf.m_expandable = MZ_TRUE; + if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) + return NULL; + *pOut_len = out_buf.m_size; + return out_buf.m_pBuf; +} + +size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) +{ + tdefl_output_buffer out_buf; + MZ_CLEAR_OBJ(out_buf); + if (!pOut_buf) + return 0; + out_buf.m_pBuf = (mz_uint8 *)pOut_buf; + out_buf.m_capacity = out_buf_len; + if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) + return 0; + return out_buf.m_size; +} + +static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; + +/* level may actually range from [0,10] (10 is a "hidden" max level, where we want a bit more compression and it's fine if throughput to fall off a cliff on some files). */ +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy) +{ + mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0); + if (window_bits > 0) + comp_flags |= TDEFL_WRITE_ZLIB_HEADER; + + if (!level) + comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; + else if (strategy == MZ_FILTERED) + comp_flags |= TDEFL_FILTER_MATCHES; + else if (strategy == MZ_HUFFMAN_ONLY) + comp_flags &= ~TDEFL_MAX_PROBES_MASK; + else if (strategy == MZ_FIXED) + comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS; + else if (strategy == MZ_RLE) + comp_flags |= TDEFL_RLE_MATCHES; + + return comp_flags; +} + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4204) /* nonstandard extension used : non-constant aggregate initializer (also supported by GNU C and C99, so no big deal) */ +#endif + +/* Simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299, more context at + http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/. + This is actually a modification of Alex's original code so PNG files generated by this function pass pngcheck. */ +void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip) +{ + /* Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined. */ + static const mz_uint s_tdefl_png_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; + tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); + tdefl_output_buffer out_buf; + int i, bpl = w * num_chans, y, z; + mz_uint32 c; + *pLen_out = 0; + if (!pComp) + return NULL; + MZ_CLEAR_OBJ(out_buf); + out_buf.m_expandable = MZ_TRUE; + out_buf.m_capacity = 57 + MZ_MAX(64, (1 + bpl) * h); + if (NULL == (out_buf.m_pBuf = (mz_uint8 *)MZ_MALLOC(out_buf.m_capacity))) + { + MZ_FREE(pComp); + return NULL; + } + /* write dummy header */ + for (z = 41; z; --z) + tdefl_output_buffer_putter(&z, 1, &out_buf); + /* compress image data */ + tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER); + for (y = 0; y < h; ++y) + { + tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); + tdefl_compress_buffer(pComp, (mz_uint8 *)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH); + } + if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE) + { + MZ_FREE(pComp); + MZ_FREE(out_buf.m_pBuf); + return NULL; + } + /* write real header */ + *pLen_out = out_buf.m_size - 41; + { + static const mz_uint8 chans[] = { 0x00, 0x00, 0x04, 0x02, 0x06 }; + mz_uint8 pnghdr[41] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, + 0x0a, 0x1a, 0x0a, 0x00, 0x00, + 0x00, 0x0d, 0x49, 0x48, 0x44, + 0x52, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x49, 0x44, 0x41, + 0x54 }; + pnghdr[18] = (mz_uint8)(w >> 8); + pnghdr[19] = (mz_uint8)w; + pnghdr[22] = (mz_uint8)(h >> 8); + pnghdr[23] = (mz_uint8)h; + pnghdr[25] = chans[num_chans]; + pnghdr[33] = (mz_uint8)(*pLen_out >> 24); + pnghdr[34] = (mz_uint8)(*pLen_out >> 16); + pnghdr[35] = (mz_uint8)(*pLen_out >> 8); + pnghdr[36] = (mz_uint8)*pLen_out; + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, pnghdr + 12, 17); + for (i = 0; i < 4; ++i, c <<= 8) + ((mz_uint8 *)(pnghdr + 29))[i] = (mz_uint8)(c >> 24); + memcpy(out_buf.m_pBuf, pnghdr, 41); + } + /* write footer (IDAT CRC-32, followed by IEND chunk) */ + if (!tdefl_output_buffer_putter("\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf)) + { + *pLen_out = 0; + MZ_FREE(pComp); + MZ_FREE(out_buf.m_pBuf); + return NULL; + } + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT, out_buf.m_pBuf + 41 - 4, *pLen_out + 4); + for (i = 0; i < 4; ++i, c <<= 8) + (out_buf.m_pBuf + out_buf.m_size - 16)[i] = (mz_uint8)(c >> 24); + /* compute final size of file, grab compressed data buffer and return */ + *pLen_out += 57; + MZ_FREE(pComp); + return out_buf.m_pBuf; +} +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out) +{ + /* Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's where #defined out) */ + return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, pLen_out, 6, MZ_FALSE); +} + +#ifndef MINIZ_NO_MALLOC +/* Allocate the tdefl_compressor and tinfl_decompressor structures in C so that */ +/* non-C language bindings to tdefL_ and tinfl_ API don't need to worry about */ +/* structure size and allocation mechanism. */ +tdefl_compressor *tdefl_compressor_alloc() +{ + return (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); +} + +void tdefl_compressor_free(tdefl_compressor *pComp) +{ + MZ_FREE(pComp); +} +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#ifdef __cplusplus +} +#endif + /************************************************************************** + * + * Copyright 2013-2014 RAD Game Tools and Valve Software + * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + + + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- Low-level Decompression (completely independent from all compression API's) */ + +#define TINFL_MEMCPY(d, s, l) memcpy(d, s, l) +#define TINFL_MEMSET(p, c, l) memset(p, c, l) + +#define TINFL_CR_BEGIN \ + switch (r->m_state) \ + { \ + case 0: +#define TINFL_CR_RETURN(state_index, result) \ + do \ + { \ + status = result; \ + r->m_state = state_index; \ + goto common_exit; \ + case state_index:; \ + } \ + MZ_MACRO_END +#define TINFL_CR_RETURN_FOREVER(state_index, result) \ + do \ + { \ + for (;;) \ + { \ + TINFL_CR_RETURN(state_index, result); \ + } \ + } \ + MZ_MACRO_END +#define TINFL_CR_FINISH } + +#define TINFL_GET_BYTE(state_index, c) \ + do \ + { \ + while (pIn_buf_cur >= pIn_buf_end) \ + { \ + TINFL_CR_RETURN(state_index, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS); \ + } \ + c = *pIn_buf_cur++; \ + } \ + MZ_MACRO_END + +#define TINFL_NEED_BITS(state_index, n) \ + do \ + { \ + mz_uint c; \ + TINFL_GET_BYTE(state_index, c); \ + bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ + num_bits += 8; \ + } while (num_bits < (mz_uint)(n)) +#define TINFL_SKIP_BITS(state_index, n) \ + do \ + { \ + if (num_bits < (mz_uint)(n)) \ + { \ + TINFL_NEED_BITS(state_index, n); \ + } \ + bit_buf >>= (n); \ + num_bits -= (n); \ + } \ + MZ_MACRO_END +#define TINFL_GET_BITS(state_index, b, n) \ + do \ + { \ + if (num_bits < (mz_uint)(n)) \ + { \ + TINFL_NEED_BITS(state_index, n); \ + } \ + b = bit_buf & ((1 << (n)) - 1); \ + bit_buf >>= (n); \ + num_bits -= (n); \ + } \ + MZ_MACRO_END + +/* TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. */ +/* It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a */ +/* Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the */ +/* bit buffer contains >=15 bits (deflate's max. Huffman code size). */ +#define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \ + do \ + { \ + temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ + if (temp >= 0) \ + { \ + code_len = temp >> 9; \ + if ((code_len) && (num_bits >= code_len)) \ + break; \ + } \ + else if (num_bits > TINFL_FAST_LOOKUP_BITS) \ + { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do \ + { \ + temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while ((temp < 0) && (num_bits >= (code_len + 1))); \ + if (temp >= 0) \ + break; \ + } \ + TINFL_GET_BYTE(state_index, c); \ + bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); \ + num_bits += 8; \ + } while (num_bits < 15); + +/* TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read */ +/* beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully */ +/* decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32. */ +/* The slow path is only executed at the very end of the input buffer. */ +/* v1.16: The original macro handled the case at the very end of the passed-in input buffer, but we also need to handle the case where the user passes in 1+zillion bytes */ +/* following the deflate data and our non-conservative read-ahead path won't kick in here on this code. This is much trickier. */ +#define TINFL_HUFF_DECODE(state_index, sym, pHuff) \ + do \ + { \ + int temp; \ + mz_uint code_len, c; \ + if (num_bits < 15) \ + { \ + if ((pIn_buf_end - pIn_buf_cur) < 2) \ + { \ + TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \ + } \ + else \ + { \ + bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); \ + pIn_buf_cur += 2; \ + num_bits += 16; \ + } \ + } \ + if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ + code_len = temp >> 9, temp &= 511; \ + else \ + { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do \ + { \ + temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while (temp < 0); \ + } \ + sym = temp; \ + bit_buf >>= code_len; \ + num_bits -= code_len; \ + } \ + MZ_MACRO_END + +tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags) +{ + static const int s_length_base[31] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0 }; + static const int s_length_extra[31] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0 }; + static const int s_dist_base[32] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0 }; + static const int s_dist_extra[32] = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 }; + static const mz_uint8 s_length_dezigzag[19] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + static const int s_min_table_sizes[3] = { 257, 1, 4 }; + + tinfl_status status = TINFL_STATUS_FAILED; + mz_uint32 num_bits, dist, counter, num_extra; + tinfl_bit_buf_t bit_buf; + const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; + mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size; + size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start; + + /* Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). */ + if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) + { + *pIn_buf_size = *pOut_buf_size = 0; + return TINFL_STATUS_BAD_PARAM; + } + + num_bits = r->m_num_bits; + bit_buf = r->m_bit_buf; + dist = r->m_dist; + counter = r->m_counter; + num_extra = r->m_num_extra; + dist_from_out_buf_start = r->m_dist_from_out_buf_start; + TINFL_CR_BEGIN + + bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; + r->m_z_adler32 = r->m_check_adler32 = 1; + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) + { + TINFL_GET_BYTE(1, r->m_zhdr0); + TINFL_GET_BYTE(2, r->m_zhdr1); + counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); + if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4))))); + if (counter) + { + TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); + } + } + + do + { + TINFL_GET_BITS(3, r->m_final, 3); + r->m_type = r->m_final >> 1; + if (r->m_type == 0) + { + TINFL_SKIP_BITS(5, num_bits & 7); + for (counter = 0; counter < 4; ++counter) + { + if (num_bits) + TINFL_GET_BITS(6, r->m_raw_header[counter], 8); + else + TINFL_GET_BYTE(7, r->m_raw_header[counter]); + } + if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) + { + TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); + } + while ((counter) && (num_bits)) + { + TINFL_GET_BITS(51, dist, 8); + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = (mz_uint8)dist; + counter--; + } + while (counter) + { + size_t n; + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); + } + while (pIn_buf_cur >= pIn_buf_end) + { + TINFL_CR_RETURN(38, (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) ? TINFL_STATUS_NEEDS_MORE_INPUT : TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS); + } + n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter); + TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); + pIn_buf_cur += n; + pOut_buf_cur += n; + counter -= (mz_uint)n; + } + } + else if (r->m_type == 3) + { + TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED); + } + else + { + if (r->m_type == 1) + { + mz_uint8 *p = r->m_tables[0].m_code_size; + mz_uint i; + r->m_table_sizes[0] = 288; + r->m_table_sizes[1] = 32; + TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32); + for (i = 0; i <= 143; ++i) + *p++ = 8; + for (; i <= 255; ++i) + *p++ = 9; + for (; i <= 279; ++i) + *p++ = 7; + for (; i <= 287; ++i) + *p++ = 8; + } + else + { + for (counter = 0; counter < 3; counter++) + { + TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); + r->m_table_sizes[counter] += s_min_table_sizes[counter]; + } + MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); + for (counter = 0; counter < r->m_table_sizes[2]; counter++) + { + mz_uint s; + TINFL_GET_BITS(14, s, 3); + r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; + } + r->m_table_sizes[2] = 19; + } + for (; (int)r->m_type >= 0; r->m_type--) + { + int tree_next, tree_cur; + tinfl_huff_table *pTable; + mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; + pTable = &r->m_tables[r->m_type]; + MZ_CLEAR_OBJ(total_syms); + MZ_CLEAR_OBJ(pTable->m_look_up); + MZ_CLEAR_OBJ(pTable->m_tree); + for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) + total_syms[pTable->m_code_size[i]]++; + used_syms = 0, total = 0; + next_code[0] = next_code[1] = 0; + for (i = 1; i <= 15; ++i) + { + used_syms += total_syms[i]; + next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); + } + if ((65536 != total) && (used_syms > 1)) + { + TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED); + } + for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index) + { + mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index]; + if (!code_size) + continue; + cur_code = next_code[code_size]++; + for (l = code_size; l > 0; l--, cur_code >>= 1) + rev_code = (rev_code << 1) | (cur_code & 1); + if (code_size <= TINFL_FAST_LOOKUP_BITS) + { + mz_int16 k = (mz_int16)((code_size << 9) | sym_index); + while (rev_code < TINFL_FAST_LOOKUP_SIZE) + { + pTable->m_look_up[rev_code] = k; + rev_code += (1 << code_size); + } + continue; + } + if (0 == (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) + { + pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; + tree_cur = tree_next; + tree_next -= 2; + } + rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1); + for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) + { + tree_cur -= ((rev_code >>= 1) & 1); + if (!pTable->m_tree[-tree_cur - 1]) + { + pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; + tree_cur = tree_next; + tree_next -= 2; + } + else + tree_cur = pTable->m_tree[-tree_cur - 1]; + } + tree_cur -= ((rev_code >>= 1) & 1); + pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index; + } + if (r->m_type == 2) + { + for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]);) + { + mz_uint s; + TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); + if (dist < 16) + { + r->m_len_codes[counter++] = (mz_uint8)dist; + continue; + } + if ((dist == 16) && (!counter)) + { + TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED); + } + num_extra = "\02\03\07"[dist - 16]; + TINFL_GET_BITS(18, s, num_extra); + s += "\03\03\013"[dist - 16]; + TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); + counter += s; + } + if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) + { + TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); + } + TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]); + TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); + } + } + for (;;) + { + mz_uint8 *pSrc; + for (;;) + { + if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2)) + { + TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]); + if (counter >= 256) + break; + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = (mz_uint8)counter; + } + else + { + int sym2; + mz_uint code_len; +#if TINFL_USE_64BIT_BITBUF + if (num_bits < 30) + { + bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 4; + num_bits += 32; + } +#else + if (num_bits < 15) + { + bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 2; + num_bits += 16; + } +#endif + if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; + do + { + sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; + } while (sym2 < 0); + } + counter = sym2; + bit_buf >>= code_len; + num_bits -= code_len; + if (counter & 256) + break; + +#if !TINFL_USE_64BIT_BITBUF + if (num_bits < 15) + { + bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); + pIn_buf_cur += 2; + num_bits += 16; + } +#endif + if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; + do + { + sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; + } while (sym2 < 0); + } + bit_buf >>= code_len; + num_bits -= code_len; + + pOut_buf_cur[0] = (mz_uint8)counter; + if (sym2 & 256) + { + pOut_buf_cur++; + counter = sym2; + break; + } + pOut_buf_cur[1] = (mz_uint8)sym2; + pOut_buf_cur += 2; + } + } + if ((counter &= 511) == 256) + break; + + num_extra = s_length_extra[counter - 257]; + counter = s_length_base[counter - 257]; + if (num_extra) + { + mz_uint extra_bits; + TINFL_GET_BITS(25, extra_bits, num_extra); + counter += extra_bits; + } + + TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]); + num_extra = s_dist_extra[dist]; + dist = s_dist_base[dist]; + if (num_extra) + { + mz_uint extra_bits; + TINFL_GET_BITS(27, extra_bits, num_extra); + dist += extra_bits; + } + + dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; + if ((dist == 0 || dist > dist_from_out_buf_start || dist_from_out_buf_start == 0) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + { + TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); + } + + pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask); + + if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) + { + while (counter--) + { + while (pOut_buf_cur >= pOut_buf_end) + { + TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); + } + *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask]; + } + continue; + } +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES + else if ((counter >= 9) && (counter <= dist)) + { + const mz_uint8 *pSrc_end = pSrc + (counter & ~7); + do + { +#ifdef MINIZ_UNALIGNED_USE_MEMCPY + memcpy(pOut_buf_cur, pSrc, sizeof(mz_uint32)*2); +#else + ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0]; + ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1]; +#endif + pOut_buf_cur += 8; + } while ((pSrc += 8) < pSrc_end); + if ((counter &= 7) < 3) + { + if (counter) + { + pOut_buf_cur[0] = pSrc[0]; + if (counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + continue; + } + } +#endif + while(counter>2) + { + pOut_buf_cur[0] = pSrc[0]; + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur[2] = pSrc[2]; + pOut_buf_cur += 3; + pSrc += 3; + counter -= 3; + } + if (counter > 0) + { + pOut_buf_cur[0] = pSrc[0]; + if (counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + } + } + } while (!(r->m_final & 1)); + + /* Ensure byte alignment and put back any bytes from the bitbuf if we've looked ahead too far on gzip, or other Deflate streams followed by arbitrary data. */ + /* I'm being super conservative here. A number of simplifications can be made to the byte alignment part, and the Adler32 check shouldn't ever need to worry about reading from the bitbuf now. */ + TINFL_SKIP_BITS(32, num_bits & 7); + while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8)) + { + --pIn_buf_cur; + num_bits -= 8; + } + bit_buf &= (tinfl_bit_buf_t)((((mz_uint64)1) << num_bits) - (mz_uint64)1); + MZ_ASSERT(!num_bits); /* if this assert fires then we've read beyond the end of non-deflate/zlib streams with following data (such as gzip streams). */ + + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) + { + for (counter = 0; counter < 4; ++counter) + { + mz_uint s; + if (num_bits) + TINFL_GET_BITS(41, s, 8); + else + TINFL_GET_BYTE(42, s); + r->m_z_adler32 = (r->m_z_adler32 << 8) | s; + } + } + TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE); + + TINFL_CR_FINISH + +common_exit: + /* As long as we aren't telling the caller that we NEED more input to make forward progress: */ + /* Put back any bytes from the bitbuf in case we've looked ahead too far on gzip, or other Deflate streams followed by arbitrary data. */ + /* We need to be very careful here to NOT push back any bytes we definitely know we need to make forward progress, though, or we'll lock the caller up into an inf loop. */ + if ((status != TINFL_STATUS_NEEDS_MORE_INPUT) && (status != TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS)) + { + while ((pIn_buf_cur > pIn_buf_next) && (num_bits >= 8)) + { + --pIn_buf_cur; + num_bits -= 8; + } + } + r->m_num_bits = num_bits; + r->m_bit_buf = bit_buf & (tinfl_bit_buf_t)((((mz_uint64)1) << num_bits) - (mz_uint64)1); + r->m_dist = dist; + r->m_counter = counter; + r->m_num_extra = num_extra; + r->m_dist_from_out_buf_start = dist_from_out_buf_start; + *pIn_buf_size = pIn_buf_cur - pIn_buf_next; + *pOut_buf_size = pOut_buf_cur - pOut_buf_next; + if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0)) + { + const mz_uint8 *ptr = pOut_buf_next; + size_t buf_len = *pOut_buf_size; + mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; + size_t block_len = buf_len % 5552; + while (buf_len) + { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) + { + s1 += ptr[0], s2 += s1; + s1 += ptr[1], s2 += s1; + s1 += ptr[2], s2 += s1; + s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; + s1 += ptr[5], s2 += s1; + s1 += ptr[6], s2 += s1; + s1 += ptr[7], s2 += s1; + } + for (; i < block_len; ++i) + s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; + buf_len -= block_len; + block_len = 5552; + } + r->m_check_adler32 = (s2 << 16) + s1; + if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) + status = TINFL_STATUS_ADLER32_MISMATCH; + } + return status; +} + +/* Higher level helper functions. */ +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) +{ + tinfl_decompressor decomp; + void *pBuf = NULL, *pNew_buf; + size_t src_buf_ofs = 0, out_buf_capacity = 0; + *pOut_len = 0; + tinfl_init(&decomp); + for (;;) + { + size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity; + tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8 *)pBuf, pBuf ? (mz_uint8 *)pBuf + *pOut_len : NULL, &dst_buf_size, + (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT)) + { + MZ_FREE(pBuf); + *pOut_len = 0; + return NULL; + } + src_buf_ofs += src_buf_size; + *pOut_len += dst_buf_size; + if (status == TINFL_STATUS_DONE) + break; + new_out_buf_capacity = out_buf_capacity * 2; + if (new_out_buf_capacity < 128) + new_out_buf_capacity = 128; + pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity); + if (!pNew_buf) + { + MZ_FREE(pBuf); + *pOut_len = 0; + return NULL; + } + pBuf = pNew_buf; + out_buf_capacity = new_out_buf_capacity; + } + return pBuf; +} + +size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) +{ + tinfl_decompressor decomp; + tinfl_status status; + tinfl_init(&decomp); + status = tinfl_decompress(&decomp, (const mz_uint8 *)pSrc_buf, &src_buf_len, (mz_uint8 *)pOut_buf, (mz_uint8 *)pOut_buf, &out_buf_len, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len; +} + +int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + int result = 0; + tinfl_decompressor decomp; + mz_uint8 *pDict = (mz_uint8 *)MZ_MALLOC(TINFL_LZ_DICT_SIZE); + size_t in_buf_ofs = 0, dict_ofs = 0; + if (!pDict) + return TINFL_STATUS_FAILED; + tinfl_init(&decomp); + for (;;) + { + size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs; + tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8 *)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, + (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); + in_buf_ofs += in_buf_size; + if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) + break; + if (status != TINFL_STATUS_HAS_MORE_OUTPUT) + { + result = (status == TINFL_STATUS_DONE); + break; + } + dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1); + } + MZ_FREE(pDict); + *pIn_buf_size = in_buf_ofs; + return result; +} + +#ifndef MINIZ_NO_MALLOC +tinfl_decompressor *tinfl_decompressor_alloc() +{ + tinfl_decompressor *pDecomp = (tinfl_decompressor *)MZ_MALLOC(sizeof(tinfl_decompressor)); + if (pDecomp) + tinfl_init(pDecomp); + return pDecomp; +} + +void tinfl_decompressor_free(tinfl_decompressor *pDecomp) +{ + MZ_FREE(pDecomp); +} +#endif + +#ifdef __cplusplus +} +#endif + /************************************************************************** + * + * Copyright 2013-2014 RAD Game Tools and Valve Software + * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC + * Copyright 2016 Martin Raiber + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + **************************************************************************/ + + +#ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- .ZIP archive reading */ + +#ifdef MINIZ_NO_STDIO +#define MZ_FILE void * +#else +#include + +#if defined(_MSC_VER) || defined(__MINGW64__) +static FILE *mz_fopen(const char *pFilename, const char *pMode) +{ + FILE *pFile = NULL; + fopen_s(&pFile, pFilename, pMode); + return pFile; +} +static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) +{ + FILE *pFile = NULL; + if (freopen_s(&pFile, pPath, pMode, pStream)) + return NULL; + return pFile; +} +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN mz_fopen +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 _ftelli64 +#define MZ_FSEEK64 _fseeki64 +#define MZ_FILE_STAT_STRUCT _stat64 +#define MZ_FILE_STAT _stat64 +#define MZ_FFLUSH fflush +#define MZ_FREOPEN mz_freopen +#define MZ_DELETE_FILE remove +#elif defined(__MINGW32__) +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftello64 +#define MZ_FSEEK64 fseeko64 +#define MZ_FILE_STAT_STRUCT _stat +#define MZ_FILE_STAT _stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#elif defined(__TINYC__) +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftell +#define MZ_FSEEK64 fseek +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#elif defined(__USE_LARGEFILE64) /* gcc, clang */ +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN(f, m) fopen64(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftello64 +#define MZ_FSEEK64 fseeko64 +#define MZ_FILE_STAT_STRUCT stat64 +#define MZ_FILE_STAT stat64 +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(p, m, s) freopen64(p, m, s) +#define MZ_DELETE_FILE remove +#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#define MZ_FTELL64 ftello +#define MZ_FSEEK64 fseeko +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(p, m, s) freopen(p, m, s) +#define MZ_DELETE_FILE remove + +#else +#if !defined(__MSYS__) && !defined(__CYGWIN__) +#pragma message("Using fopen, ftello, fseeko, stat() etc. path for file I/O - this path may not support large files.") +#endif +#ifndef MINIZ_NO_TIME +#include +#endif +#define MZ_FOPEN(f, m) fopen(f, m) +#define MZ_FCLOSE fclose +#define MZ_FREAD fread +#define MZ_FWRITE fwrite +#ifdef __STRICT_ANSI__ +#define MZ_FTELL64 ftell +#define MZ_FSEEK64 fseek +#else +#define MZ_FTELL64 ftello +#define MZ_FSEEK64 fseeko +#endif +#define MZ_FILE_STAT_STRUCT stat +#define MZ_FILE_STAT stat +#define MZ_FFLUSH fflush +#define MZ_FREOPEN(f, m, s) freopen(f, m, s) +#define MZ_DELETE_FILE remove +#endif /* #ifdef _MSC_VER */ +#endif /* #ifdef MINIZ_NO_STDIO */ + +#define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c)) + +/* Various ZIP archive enums. To completely avoid cross platform compiler alignment and platform endian issues, miniz.c doesn't use structs for any of this stuff. */ +enum +{ + /* ZIP archive identifiers and record sizes */ + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, + MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, + + /* ZIP64 archive identifier and record sizes */ + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06064b50, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG = 0x07064b50, + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE = 56, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE = 20, + MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID = 0x0001, + MZ_ZIP_DATA_DESCRIPTOR_ID = 0x08074b50, + MZ_ZIP_DATA_DESCRIPTER_SIZE64 = 24, + MZ_ZIP_DATA_DESCRIPTER_SIZE32 = 16, + + /* Central directory header record offsets */ + MZ_ZIP_CDH_SIG_OFS = 0, + MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, + MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, + MZ_ZIP_CDH_BIT_FLAG_OFS = 8, + MZ_ZIP_CDH_METHOD_OFS = 10, + MZ_ZIP_CDH_FILE_TIME_OFS = 12, + MZ_ZIP_CDH_FILE_DATE_OFS = 14, + MZ_ZIP_CDH_CRC32_OFS = 16, + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, + MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, + MZ_ZIP_CDH_EXTRA_LEN_OFS = 30, + MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, + MZ_ZIP_CDH_DISK_START_OFS = 34, + MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, + MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42, + + /* Local directory header offsets */ + MZ_ZIP_LDH_SIG_OFS = 0, + MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, + MZ_ZIP_LDH_BIT_FLAG_OFS = 6, + MZ_ZIP_LDH_METHOD_OFS = 8, + MZ_ZIP_LDH_FILE_TIME_OFS = 10, + MZ_ZIP_LDH_FILE_DATE_OFS = 12, + MZ_ZIP_LDH_CRC32_OFS = 14, + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22, + MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, + MZ_ZIP_LDH_EXTRA_LEN_OFS = 28, + MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR = 1 << 3, + + /* End of central directory offsets */ + MZ_ZIP_ECDH_SIG_OFS = 0, + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8, + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, + MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, + MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, + MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, + + /* ZIP64 End of central directory locator offsets */ + MZ_ZIP64_ECDL_SIG_OFS = 0, /* 4 bytes */ + MZ_ZIP64_ECDL_NUM_DISK_CDIR_OFS = 4, /* 4 bytes */ + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS = 8, /* 8 bytes */ + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS = 16, /* 4 bytes */ + + /* ZIP64 End of central directory header offsets */ + MZ_ZIP64_ECDH_SIG_OFS = 0, /* 4 bytes */ + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS = 4, /* 8 bytes */ + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS = 12, /* 2 bytes */ + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS = 14, /* 2 bytes */ + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS = 16, /* 4 bytes */ + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS = 20, /* 4 bytes */ + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 24, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS = 32, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_SIZE_OFS = 40, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_OFS_OFS = 48, /* 8 bytes */ + MZ_ZIP_VERSION_MADE_BY_DOS_FILESYSTEM_ID = 0, + MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG = 0x10, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED = 1, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG = 32, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION = 64, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED = 8192, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8 = 1 << 11 +}; + +typedef struct +{ + void *m_p; + size_t m_size, m_capacity; + mz_uint m_element_size; +} mz_zip_array; + +struct mz_zip_internal_state_tag +{ + mz_zip_array m_central_dir; + mz_zip_array m_central_dir_offsets; + mz_zip_array m_sorted_central_dir_offsets; + + /* The flags passed in when the archive is initially opened. */ + uint32_t m_init_flags; + + /* MZ_TRUE if the archive has a zip64 end of central directory headers, etc. */ + mz_bool m_zip64; + + /* MZ_TRUE if we found zip64 extended info in the central directory (m_zip64 will also be slammed to true too, even if we didn't find a zip64 end of central dir header, etc.) */ + mz_bool m_zip64_has_extended_info_fields; + + /* These fields are used by the file, FILE, memory, and memory/heap read/write helpers. */ + MZ_FILE *m_pFile; + mz_uint64 m_file_archive_start_ofs; + + void *m_pMem; + size_t m_mem_size; + size_t m_mem_capacity; +}; + +#define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size + +#if defined(DEBUG) || defined(_DEBUG) +static MZ_FORCEINLINE mz_uint mz_zip_array_range_check(const mz_zip_array *pArray, mz_uint index) +{ + MZ_ASSERT(index < pArray->m_size); + return index; +} +#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[mz_zip_array_range_check(array_ptr, index)] +#else +#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index] +#endif + +static MZ_FORCEINLINE void mz_zip_array_init(mz_zip_array *pArray, mz_uint32 element_size) +{ + memset(pArray, 0, sizeof(mz_zip_array)); + pArray->m_element_size = element_size; +} + +static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, mz_zip_array *pArray) +{ + pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p); + memset(pArray, 0, sizeof(mz_zip_array)); +} + +static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, mz_zip_array *pArray, size_t min_new_capacity, mz_uint growing) +{ + void *pNew_p; + size_t new_capacity = min_new_capacity; + MZ_ASSERT(pArray->m_element_size); + if (pArray->m_capacity >= min_new_capacity) + return MZ_TRUE; + if (growing) + { + new_capacity = MZ_MAX(1, pArray->m_capacity); + while (new_capacity < min_new_capacity) + new_capacity *= 2; + } + if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity))) + return MZ_FALSE; + pArray->m_p = pNew_p; + pArray->m_capacity = new_capacity; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_capacity, mz_uint growing) +{ + if (new_capacity > pArray->m_capacity) + { + if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing)) + return MZ_FALSE; + } + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_size, mz_uint growing) +{ + if (new_size > pArray->m_capacity) + { + if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) + return MZ_FALSE; + } + pArray->m_size = new_size; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, mz_zip_array *pArray, size_t n) +{ + return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE); +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, mz_zip_array *pArray, const void *pElements, size_t n) +{ + size_t orig_size = pArray->m_size; + if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE)) + return MZ_FALSE; + if (n > 0) + memcpy((mz_uint8 *)pArray->m_p + orig_size * pArray->m_element_size, pElements, n * pArray->m_element_size); + return MZ_TRUE; +} + +#ifndef MINIZ_NO_TIME +static MZ_TIME_T mz_zip_dos_to_time_t(int dos_time, int dos_date) +{ + struct tm tm; + memset(&tm, 0, sizeof(tm)); + tm.tm_isdst = -1; + tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900; + tm.tm_mon = ((dos_date >> 5) & 15) - 1; + tm.tm_mday = dos_date & 31; + tm.tm_hour = (dos_time >> 11) & 31; + tm.tm_min = (dos_time >> 5) & 63; + tm.tm_sec = (dos_time << 1) & 62; + return mktime(&tm); +} + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS +static void mz_zip_time_t_to_dos_time(MZ_TIME_T time, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) +{ +#ifdef _MSC_VER + struct tm tm_struct; + struct tm *tm = &tm_struct; + errno_t err = localtime_s(tm, &time); + if (err) + { + *pDOS_date = 0; + *pDOS_time = 0; + return; + } +#else + struct tm *tm = localtime(&time); +#endif /* #ifdef _MSC_VER */ + + *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1)); + *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday); +} +#endif /* MINIZ_NO_ARCHIVE_WRITING_APIS */ + +#ifndef MINIZ_NO_STDIO +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS +static mz_bool mz_zip_get_file_modified_time(const char *pFilename, MZ_TIME_T *pTime) +{ + struct MZ_FILE_STAT_STRUCT file_stat; + + /* On Linux with x86 glibc, this call will fail on large files (I think >= 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh. */ + if (MZ_FILE_STAT(pFilename, &file_stat) != 0) + return MZ_FALSE; + + *pTime = file_stat.st_mtime; + + return MZ_TRUE; +} +#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS*/ + +static mz_bool mz_zip_set_file_times(const char *pFilename, MZ_TIME_T access_time, MZ_TIME_T modified_time) +{ + struct utimbuf t; + + memset(&t, 0, sizeof(t)); + t.actime = access_time; + t.modtime = modified_time; + + return !utime(pFilename, &t); +} +#endif /* #ifndef MINIZ_NO_STDIO */ +#endif /* #ifndef MINIZ_NO_TIME */ + +static MZ_FORCEINLINE mz_bool mz_zip_set_error(mz_zip_archive *pZip, mz_zip_error err_num) +{ + if (pZip) + pZip->m_last_error = err_num; + return MZ_FALSE; +} + +static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint flags) +{ + (void)flags; + if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!pZip->m_pAlloc) + pZip->m_pAlloc = miniz_def_alloc_func; + if (!pZip->m_pFree) + pZip->m_pFree = miniz_def_free_func; + if (!pZip->m_pRealloc) + pZip->m_pRealloc = miniz_def_realloc_func; + + pZip->m_archive_size = 0; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + pZip->m_last_error = MZ_ZIP_NO_ERROR; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); + pZip->m_pState->m_init_flags = flags; + pZip->m_pState->m_zip64 = MZ_FALSE; + pZip->m_pState->m_zip64_has_extended_info_fields = MZ_FALSE; + + pZip->m_zip_mode = MZ_ZIP_MODE_READING; + + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, mz_uint r_index) +{ + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; + const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index)); + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; + pR++; + } + return (pL == pE) ? (l_len < r_len) : (l < r); +} + +#define MZ_SWAP_UINT32(a, b) \ + do \ + { \ + mz_uint32 t = a; \ + a = b; \ + b = t; \ + } \ + MZ_MACRO_END + +/* Heap sort of lowercased filenames, used to help accelerate plain central directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), but it could allocate memory.) */ +static void mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices; + mz_uint32 start, end; + const mz_uint32 size = pZip->m_total_files; + + if (size <= 1U) + return; + + pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + + start = (size - 2U) >> 1U; + for (;;) + { + mz_uint64 child, root = start; + for (;;) + { + if ((child = (root << 1U) + 1U) >= size) + break; + child += (((child + 1U) < size) && (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U]))); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); + root = child; + } + if (!start) + break; + start--; + } + + end = size - 1; + while (end > 0) + { + mz_uint64 child, root = 0; + MZ_SWAP_UINT32(pIndices[end], pIndices[0]); + for (;;) + { + if ((child = (root << 1U) + 1U) >= end) + break; + child += (((child + 1U) < end) && mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1U])); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); + root = child; + } + end--; + } +} + +static mz_bool mz_zip_reader_locate_header_sig(mz_zip_archive *pZip, mz_uint32 record_sig, mz_uint32 record_size, mz_int64 *pOfs) +{ + mz_int64 cur_file_ofs; + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; + mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + + /* Basic sanity checks - reject files which are too small */ + if (pZip->m_archive_size < record_size) + return MZ_FALSE; + + /* Find the record by scanning the file from the end towards the beginning. */ + cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0); + for (;;) + { + int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs); + + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n) + return MZ_FALSE; + + for (i = n - 4; i >= 0; --i) + { + mz_uint s = MZ_READ_LE32(pBuf + i); + if (s == record_sig) + { + if ((pZip->m_archive_size - (cur_file_ofs + i)) >= record_size) + break; + } + } + + if (i >= 0) + { + cur_file_ofs += i; + break; + } + + /* Give up if we've searched the entire file, or we've gone back "too far" (~64kb) */ + if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= (MZ_UINT16_MAX + record_size))) + return MZ_FALSE; + + cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); + } + + *pOfs = cur_file_ofs; + return MZ_TRUE; +} + +static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint flags) +{ + mz_uint cdir_size = 0, cdir_entries_on_this_disk = 0, num_this_disk = 0, cdir_disk_index = 0; + mz_uint64 cdir_ofs = 0; + mz_int64 cur_file_ofs = 0; + const mz_uint8 *p; + + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; + mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + mz_bool sort_central_dir = ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); + mz_uint32 zip64_end_of_central_dir_locator_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pZip64_locator = (mz_uint8 *)zip64_end_of_central_dir_locator_u32; + + mz_uint32 zip64_end_of_central_dir_header_u32[(MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pZip64_end_of_central_dir = (mz_uint8 *)zip64_end_of_central_dir_header_u32; + + mz_uint64 zip64_end_of_central_dir_ofs = 0; + + /* Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there. */ + if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (!mz_zip_reader_locate_header_sig(pZip, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE, &cur_file_ofs)) + return mz_zip_set_error(pZip, MZ_ZIP_FAILED_FINDING_CENTRAL_DIR); + + /* Read and verify the end of central directory record. */ + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (cur_file_ofs >= (MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) + { + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs - MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE, pZip64_locator, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) + { + if (MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG) + { + zip64_end_of_central_dir_ofs = MZ_READ_LE64(pZip64_locator + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS); + if (zip64_end_of_central_dir_ofs > (pZip->m_archive_size - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (pZip->m_pRead(pZip->m_pIO_opaque, zip64_end_of_central_dir_ofs, pZip64_end_of_central_dir, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) + { + if (MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIG_OFS) == MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG) + { + pZip->m_pState->m_zip64 = MZ_TRUE; + } + } + } + } + } + + pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS); + cdir_entries_on_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); + num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS); + cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS); + cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS); + cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); + + if (pZip->m_pState->m_zip64) + { + mz_uint32 zip64_total_num_of_disks = MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS); + mz_uint64 zip64_cdir_total_entries = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS); + mz_uint64 zip64_cdir_total_entries_on_this_disk = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); + mz_uint64 zip64_size_of_end_of_central_dir_record = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS); + mz_uint64 zip64_size_of_central_directory = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_SIZE_OFS); + + if (zip64_size_of_end_of_central_dir_record < (MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - 12)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (zip64_total_num_of_disks != 1U) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + /* Check for miniz's practical limits */ + if (zip64_cdir_total_entries > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + pZip->m_total_files = (mz_uint32)zip64_cdir_total_entries; + + if (zip64_cdir_total_entries_on_this_disk > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + cdir_entries_on_this_disk = (mz_uint32)zip64_cdir_total_entries_on_this_disk; + + /* Check for miniz's current practical limits (sorry, this should be enough for millions of files) */ + if (zip64_size_of_central_directory > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + cdir_size = (mz_uint32)zip64_size_of_central_directory; + + num_this_disk = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS); + + cdir_disk_index = MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS); + + cdir_ofs = MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_OFS_OFS); + } + + if (pZip->m_total_files != cdir_entries_on_this_disk) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1))) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (cdir_size < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pZip->m_central_directory_file_ofs = cdir_ofs; + + if (pZip->m_total_files) + { + mz_uint i, n; + /* Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and possibly another to hold the sorted indices. */ + if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) || + (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (sort_central_dir) + { + if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + /* Now create an index into the central directory file records, do some basic sanity checking on each record */ + p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p; + for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) + { + mz_uint total_header_size, disk_index, bit_flags, filename_size, ext_data_size; + mz_uint64 comp_size, decomp_size, local_header_ofs; + + if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p); + + if (sort_central_dir) + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i; + + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + filename_size = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + ext_data_size = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); + + if ((!pZip->m_pState->m_zip64_has_extended_info_fields) && + (ext_data_size) && + (MZ_MAX(MZ_MAX(comp_size, decomp_size), local_header_ofs) == MZ_UINT32_MAX)) + { + /* Attempt to find zip64 extended information field in the entry's extra data */ + mz_uint32 extra_size_remaining = ext_data_size; + + if (extra_size_remaining) + { + const mz_uint8 *pExtra_data; + void* buf = NULL; + + if (MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + ext_data_size > n) + { + buf = MZ_MALLOC(ext_data_size); + if(buf==NULL) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size, buf, ext_data_size) != ext_data_size) + { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + pExtra_data = (mz_uint8*)buf; + } + else + { + pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size; + } + + do + { + mz_uint32 field_id; + mz_uint32 field_data_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + + if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining) + { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + /* Ok, the archive didn't have any zip64 headers but it uses a zip64 extended information field so mark it as zip64 anyway (this can occur with infozip's zip util when it reads compresses files from stdin). */ + pZip->m_pState->m_zip64 = MZ_TRUE; + pZip->m_pState->m_zip64_has_extended_info_fields = MZ_TRUE; + break; + } + + pExtra_data += sizeof(mz_uint16) * 2 + field_data_size; + extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size; + } while (extra_size_remaining); + + MZ_FREE(buf); + } + } + + /* I've seen archives that aren't marked as zip64 that uses zip64 ext data, argh */ + if ((comp_size != MZ_UINT32_MAX) && (decomp_size != MZ_UINT32_MAX)) + { + if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS); + if ((disk_index == MZ_UINT16_MAX) || ((disk_index != num_this_disk) && (disk_index != 1))) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (comp_size != MZ_UINT32_MAX) + { + if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + bit_flags = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + if (bit_flags & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + n -= total_header_size; + p += total_header_size; + } + } + + if (sort_central_dir) + mz_zip_reader_sort_central_dir_offsets_by_filename(pZip); + + return MZ_TRUE; +} + +void mz_zip_zero_struct(mz_zip_archive *pZip) +{ + if (pZip) + MZ_CLEAR_OBJ(*pZip); +} + +static mz_bool mz_zip_reader_end_internal(mz_zip_archive *pZip, mz_bool set_last_error) +{ + mz_bool status = MZ_TRUE; + + if (!pZip) + return MZ_FALSE; + + if ((!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + { + if (set_last_error) + pZip->m_last_error = MZ_ZIP_INVALID_PARAMETER; + + return MZ_FALSE; + } + + if (pZip->m_pState) + { + mz_zip_internal_state *pState = pZip->m_pState; + pZip->m_pState = NULL; + + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) + { + if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) + { + if (MZ_FCLOSE(pState->m_pFile) == EOF) + { + if (set_last_error) + pZip->m_last_error = MZ_ZIP_FILE_CLOSE_FAILED; + status = MZ_FALSE; + } + } + pState->m_pFile = NULL; + } +#endif /* #ifndef MINIZ_NO_STDIO */ + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + } + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + + return status; +} + +mz_bool mz_zip_reader_end(mz_zip_archive *pZip) +{ + return mz_zip_reader_end_internal(pZip, MZ_TRUE); +} +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags) +{ + if ((!pZip) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + + pZip->m_zip_type = MZ_ZIP_TYPE_USER; + pZip->m_archive_size = size; + + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +static size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + size_t s = (file_ofs >= pZip->m_archive_size) ? 0 : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n); + memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s); + return s; +} + +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags) +{ + if (!pMem) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + + pZip->m_zip_type = MZ_ZIP_TYPE_MEMORY; + pZip->m_archive_size = size; + pZip->m_pRead = mz_zip_mem_read_func; + pZip->m_pIO_opaque = pZip; + pZip->m_pNeeds_keepalive = NULL; + +#ifdef __cplusplus + pZip->m_pState->m_pMem = const_cast(pMem); +#else + pZip->m_pState->m_pMem = (void *)pMem; +#endif + + pZip->m_pState->m_mem_size = size; + + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + + file_ofs += pZip->m_pState->m_file_archive_start_ofs; + + if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + + return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags) +{ + return mz_zip_reader_init_file_v2(pZip, pFilename, flags, 0, 0); +} + +mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size) +{ + mz_uint64 file_size; + MZ_FILE *pFile; + + if ((!pZip) || (!pFilename) || ((archive_size) && (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE))) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pFile = MZ_FOPEN(pFilename, "rb"); + if (!pFile) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + + file_size = archive_size; + if (!file_size) + { + if (MZ_FSEEK64(pFile, 0, SEEK_END)) + { + MZ_FCLOSE(pFile); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); + } + + file_size = MZ_FTELL64(pFile); + } + + /* TODO: Better sanity check archive_size and the # of actual remaining bytes */ + + if (file_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + { + MZ_FCLOSE(pFile); + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + } + + if (!mz_zip_reader_init_internal(pZip, flags)) + { + MZ_FCLOSE(pFile); + return MZ_FALSE; + } + + pZip->m_zip_type = MZ_ZIP_TYPE_FILE; + pZip->m_pRead = mz_zip_file_read_func; + pZip->m_pIO_opaque = pZip; + pZip->m_pState->m_pFile = pFile; + pZip->m_archive_size = file_size; + pZip->m_pState->m_file_archive_start_ofs = file_start_ofs; + + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags) +{ + mz_uint64 cur_file_ofs; + + if ((!pZip) || (!pFile)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + + cur_file_ofs = MZ_FTELL64(pFile); + + if (!archive_size) + { + if (MZ_FSEEK64(pFile, 0, SEEK_END)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); + + archive_size = MZ_FTELL64(pFile) - cur_file_ofs; + + if (archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + } + + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + + pZip->m_zip_type = MZ_ZIP_TYPE_CFILE; + pZip->m_pRead = mz_zip_file_read_func; + + pZip->m_pIO_opaque = pZip; + pZip->m_pState->m_pFile = pFile; + pZip->m_archive_size = archive_size; + pZip->m_pState->m_file_archive_start_ofs = cur_file_ofs; + + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +#endif /* #ifndef MINIZ_NO_STDIO */ + +static MZ_FORCEINLINE const mz_uint8 *mz_zip_get_cdh(mz_zip_archive *pZip, mz_uint file_index) +{ + if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files)) + return NULL; + return &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); +} + +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint m_bit_flag; + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return MZ_FALSE; + } + + m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + return (m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION)) != 0; +} + +mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint bit_flag; + mz_uint method; + + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return MZ_FALSE; + } + + method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); + bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + + if ((method != 0) && (method != MZ_DEFLATED)) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + return MZ_FALSE; + } + + if (bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION)) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + return MZ_FALSE; + } + + if (bit_flag & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint filename_len, attribute_mapping_id, external_attr; + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return MZ_FALSE; + } + + filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_len) + { + if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/') + return MZ_TRUE; + } + + /* Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct. */ + /* Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field. */ + /* FIXME: Remove this check? Is it necessary - we already check the filename. */ + attribute_mapping_id = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS) >> 8; + (void)attribute_mapping_id; + + external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + if ((external_attr & MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG) != 0) + { + return MZ_TRUE; + } + + return MZ_FALSE; +} + +static mz_bool mz_zip_file_stat_internal(mz_zip_archive *pZip, mz_uint file_index, const mz_uint8 *pCentral_dir_header, mz_zip_archive_file_stat *pStat, mz_bool *pFound_zip64_extra_data) +{ + mz_uint n; + const mz_uint8 *p = pCentral_dir_header; + + if (pFound_zip64_extra_data) + *pFound_zip64_extra_data = MZ_FALSE; + + if ((!p) || (!pStat)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* Extract fields from the central directory record. */ + pStat->m_file_index = file_index; + pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index); + pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS); + pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS); + pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); +#ifndef MINIZ_NO_TIME + pStat->m_time = mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS)); +#endif + pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS); + pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS); + pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + + /* Copy as much of the filename and comment as possible. */ + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1); + memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pStat->m_filename[n] = '\0'; + + n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS); + n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1); + pStat->m_comment_size = n; + memcpy(pStat->m_comment, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), n); + pStat->m_comment[n] = '\0'; + + /* Set some flags for convienance */ + pStat->m_is_directory = mz_zip_reader_is_file_a_directory(pZip, file_index); + pStat->m_is_encrypted = mz_zip_reader_is_file_encrypted(pZip, file_index); + pStat->m_is_supported = mz_zip_reader_is_file_supported(pZip, file_index); + + /* See if we need to read any zip64 extended information fields. */ + /* Confusingly, these zip64 fields can be present even on non-zip64 archives (Debian zip on a huge files from stdin piped to stdout creates them). */ + if (MZ_MAX(MZ_MAX(pStat->m_comp_size, pStat->m_uncomp_size), pStat->m_local_header_ofs) == MZ_UINT32_MAX) + { + /* Attempt to find zip64 extended information field in the entry's extra data */ + mz_uint32 extra_size_remaining = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); + + if (extra_size_remaining) + { + const mz_uint8 *pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + + do + { + mz_uint32 field_id; + mz_uint32 field_data_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + + if ((field_data_size + sizeof(mz_uint16) * 2) > extra_size_remaining) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + const mz_uint8 *pField_data = pExtra_data + sizeof(mz_uint16) * 2; + mz_uint32 field_data_remaining = field_data_size; + + if (pFound_zip64_extra_data) + *pFound_zip64_extra_data = MZ_TRUE; + + if (pStat->m_uncomp_size == MZ_UINT32_MAX) + { + if (field_data_remaining < sizeof(mz_uint64)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pStat->m_uncomp_size = MZ_READ_LE64(pField_data); + pField_data += sizeof(mz_uint64); + field_data_remaining -= sizeof(mz_uint64); + } + + if (pStat->m_comp_size == MZ_UINT32_MAX) + { + if (field_data_remaining < sizeof(mz_uint64)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pStat->m_comp_size = MZ_READ_LE64(pField_data); + pField_data += sizeof(mz_uint64); + field_data_remaining -= sizeof(mz_uint64); + } + + if (pStat->m_local_header_ofs == MZ_UINT32_MAX) + { + if (field_data_remaining < sizeof(mz_uint64)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + pStat->m_local_header_ofs = MZ_READ_LE64(pField_data); + pField_data += sizeof(mz_uint64); + field_data_remaining -= sizeof(mz_uint64); + } + + break; + } + + pExtra_data += sizeof(mz_uint16) * 2 + field_data_size; + extra_size_remaining = extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size; + } while (extra_size_remaining); + } + } + + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_string_equal(const char *pA, const char *pB, mz_uint len, mz_uint flags) +{ + mz_uint i; + if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE) + return 0 == memcmp(pA, pB, len); + for (i = 0; i < len; ++i) + if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i])) + return MZ_FALSE; + return MZ_TRUE; +} + +static MZ_FORCEINLINE int mz_zip_filename_compare(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, const char *pR, mz_uint r_len) +{ + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; + pR++; + } + return (pL == pE) ? (int)(l_len - r_len) : (l - r); +} + +static mz_bool mz_zip_locate_file_binary_search(mz_zip_archive *pZip, const char *pFilename, mz_uint32 *pIndex) +{ + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + const uint32_t size = pZip->m_total_files; + const mz_uint filename_len = (mz_uint)strlen(pFilename); + + if (pIndex) + *pIndex = 0; + + if (size) + { + /* yes I could use uint32_t's, but then we would have to add some special case checks in the loop, argh, and */ + /* honestly the major expense here on 32-bit CPU's will still be the filename compare */ + mz_int64 l = 0, h = (mz_int64)size - 1; + + while (l <= h) + { + mz_int64 m = l + ((h - l) >> 1); + uint32_t file_index = pIndices[(uint32_t)m]; + + int comp = mz_zip_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len); + if (!comp) + { + if (pIndex) + *pIndex = file_index; + return MZ_TRUE; + } + else if (comp < 0) + l = m + 1; + else + h = m - 1; + } + } + + return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND); +} + +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags) +{ + mz_uint32 index; + if (!mz_zip_reader_locate_file_v2(pZip, pName, pComment, flags, &index)) + return -1; + else + return (int)index; +} + +mz_bool mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *pIndex) +{ + mz_uint file_index; + size_t name_len, comment_len; + + if (pIndex) + *pIndex = 0; + + if ((!pZip) || (!pZip->m_pState) || (!pName)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* See if we can use a binary search */ + if (((pZip->m_pState->m_init_flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0) && + (pZip->m_zip_mode == MZ_ZIP_MODE_READING) && + ((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size)) + { + return mz_zip_locate_file_binary_search(pZip, pName, pIndex); + } + + /* Locate the entry by scanning the entire central directory */ + name_len = strlen(pName); + if (name_len > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + comment_len = pComment ? strlen(pComment) : 0; + if (comment_len > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + for (file_index = 0; file_index < pZip->m_total_files; file_index++) + { + const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); + mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); + const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + if (filename_len < name_len) + continue; + if (comment_len) + { + mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), file_comment_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS); + const char *pFile_comment = pFilename + filename_len + file_extra_len; + if ((file_comment_len != comment_len) || (!mz_zip_string_equal(pComment, pFile_comment, file_comment_len, flags))) + continue; + } + if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len)) + { + int ofs = filename_len - 1; + do + { + if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':')) + break; + } while (--ofs >= 0); + ofs++; + pFilename += ofs; + filename_len -= ofs; + } + if ((filename_len == name_len) && (mz_zip_string_equal(pName, pFilename, filename_len, flags))) + { + if (pIndex) + *pIndex = file_index; + return MZ_TRUE; + } + } + + return mz_zip_set_error(pZip, MZ_ZIP_FILE_NOT_FOUND); +} + +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) +{ + int status = TINFL_STATUS_DONE; + mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail; + mz_zip_archive_file_stat file_stat; + void *pRead_buf; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + tinfl_decompressor inflator; + + if ((!pZip) || (!pZip->m_pState) || ((buf_size) && (!pBuf)) || ((user_read_buf_size) && (!pUser_read_buf)) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + /* A directory or zero length file */ + if ((file_stat.m_is_directory) || (!file_stat.m_comp_size)) + return MZ_TRUE; + + /* Encryption and patch files are not supported. */ + if (file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + /* This function only supports decompressing stored and deflate. */ + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + + /* Ensure supplied output buffer is large enough. */ + needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size; + if (buf_size < needed_size) + return mz_zip_set_error(pZip, MZ_ZIP_BUF_TOO_SMALL); + + /* Read and parse the local directory entry. */ + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) + { + /* The file is stored or the caller has requested the compressed data. */ + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) == 0) + { + if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32) + return mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED); + } +#endif + + return MZ_TRUE; + } + + /* Decompress the file either directly from memory or from a file input buffer. */ + tinfl_init(&inflator); + + if (pZip->m_pState->m_pMem) + { + /* Read directly from the archive in memory. */ + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } + else if (pUser_read_buf) + { + /* Use a user provided read buffer. */ + if (!user_read_buf_size) + return MZ_FALSE; + pRead_buf = (mz_uint8 *)pUser_read_buf; + read_buf_size = user_read_buf_size; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + else + { + /* Temporarily allocate a read buffer. */ + read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); + if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + do + { + /* The size_t cast here should be OK because we've verified that the output buffer is >= file_stat.m_uncomp_size above */ + size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress(&inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0)); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + out_buf_ofs += out_buf_size; + } while (status == TINFL_STATUS_NEEDS_MORE_INPUT); + + if (status == TINFL_STATUS_DONE) + { + /* Make sure the entire file was decompressed, and check its CRC. */ + if (out_buf_ofs != file_stat.m_uncomp_size) + { + mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); + status = TINFL_STATUS_FAILED; + } +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + else if (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32) + { + mz_zip_set_error(pZip, MZ_ZIP_CRC_CHECK_FAILED); + status = TINFL_STATUS_FAILED; + } +#endif + } + + if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf)) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) + return MZ_FALSE; + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size); +} + +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags) +{ + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, NULL, 0); +} + +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags) +{ + return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, 0); +} + +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags) +{ + mz_uint64 comp_size, uncomp_size, alloc_size; + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + void *pBuf; + + if (pSize) + *pSize = 0; + + if (!p) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return NULL; + } + + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + + alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? comp_size : uncomp_size; + if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) + { + mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + return NULL; + } + + if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + return NULL; + } + + if (!mz_zip_reader_extract_to_mem(pZip, file_index, pBuf, (size_t)alloc_size, flags)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return NULL; + } + + if (pSize) + *pSize = (size_t)alloc_size; + return pBuf; +} + +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) + { + if (pSize) + *pSize = 0; + return MZ_FALSE; + } + return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags); +} + +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) +{ + int status = TINFL_STATUS_DONE; +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + mz_uint file_crc32 = MZ_CRC32_INIT; +#endif + mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, out_buf_ofs = 0, cur_file_ofs; + mz_zip_archive_file_stat file_stat; + void *pRead_buf = NULL; + void *pWrite_buf = NULL; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + + if ((!pZip) || (!pZip->m_pState) || (!pCallback) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + /* A directory or zero length file */ + if ((file_stat.m_is_directory) || (!file_stat.m_comp_size)) + return MZ_TRUE; + + /* Encryption and patch files are not supported. */ + if (file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + /* This function only supports decompressing stored and deflate. */ + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + + /* Read and do some minimal validation of the local directory entry (this doesn't crack the zip64 stuff, which we already have from the central dir) */ + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + /* Decompress the file either directly from memory or from a file input buffer. */ + if (pZip->m_pState->m_pMem) + { + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } + else + { + read_buf_size = MZ_MIN(file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) + { + /* The file is stored or the caller has requested the compressed data. */ + if (pZip->m_pState->m_pMem) + { + if (((sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)file_stat.m_comp_size) != file_stat.m_comp_size) + { + mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); + status = TINFL_STATUS_FAILED; + } + else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)file_stat.m_comp_size); +#endif + } + + cur_file_ofs += file_stat.m_comp_size; + out_buf_ofs += file_stat.m_comp_size; + comp_remaining = 0; + } + else + { + while (comp_remaining) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { + file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail); + } +#endif + + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + + cur_file_ofs += read_buf_avail; + out_buf_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + } + } + } + else + { + tinfl_decompressor inflator; + tinfl_init(&inflator); + + if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + status = TINFL_STATUS_FAILED; + } + else + { + do + { + mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress(&inflator, (const mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pWrite_buf, pWrite_buf_cur, &out_buf_size, comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + + if (out_buf_size) + { + if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != out_buf_size) + { + mz_zip_set_error(pZip, MZ_ZIP_WRITE_CALLBACK_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + file_crc32 = (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size); +#endif + if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size) + { + mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); + status = TINFL_STATUS_FAILED; + break; + } + } + } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || (status == TINFL_STATUS_HAS_MORE_OUTPUT)); + } + } + + if ((status == TINFL_STATUS_DONE) && (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) + { + /* Make sure the entire file was decompressed, and check its CRC. */ + if (out_buf_ofs != file_stat.m_uncomp_size) + { + mz_zip_set_error(pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); + status = TINFL_STATUS_FAILED; + } +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + else if (file_crc32 != file_stat.m_crc32) + { + mz_zip_set_error(pZip, MZ_ZIP_DECOMPRESSION_FAILED); + status = TINFL_STATUS_FAILED; + } +#endif + } + + if (!pZip->m_pState->m_pMem) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + + if (pWrite_buf) + pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) + return MZ_FALSE; + + return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, flags); +} + +mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags) +{ + mz_zip_reader_extract_iter_state *pState; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + + /* Argument sanity check */ + if ((!pZip) || (!pZip->m_pState)) + return NULL; + + /* Allocate an iterator status structure */ + pState = (mz_zip_reader_extract_iter_state*)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_reader_extract_iter_state)); + if (!pState) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + return NULL; + } + + /* Fetch file details */ + if (!mz_zip_reader_file_stat(pZip, file_index, &pState->file_stat)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + /* Encryption and patch files are not supported. */ + if (pState->file_stat.m_bit_flag & (MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION | MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG)) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + /* This function only supports decompressing stored and deflate. */ + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (pState->file_stat.m_method != 0) && (pState->file_stat.m_method != MZ_DEFLATED)) + { + mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + /* Init state - save args */ + pState->pZip = pZip; + pState->flags = flags; + + /* Init state - reset variables to defaults */ + pState->status = TINFL_STATUS_DONE; +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + pState->file_crc32 = MZ_CRC32_INIT; +#endif + pState->read_buf_ofs = 0; + pState->out_buf_ofs = 0; + pState->pRead_buf = NULL; + pState->pWrite_buf = NULL; + pState->out_blk_remain = 0; + + /* Read and parse the local directory entry. */ + pState->cur_file_ofs = pState->file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, pState->cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + pState->cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((pState->cur_file_ofs + pState->file_stat.m_comp_size) > pZip->m_archive_size) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + + /* Decompress the file either directly from memory or from a file input buffer. */ + if (pZip->m_pState->m_pMem) + { + pState->pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + pState->cur_file_ofs; + pState->read_buf_size = pState->read_buf_avail = pState->file_stat.m_comp_size; + pState->comp_remaining = pState->file_stat.m_comp_size; + } + else + { + if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method))) + { + /* Decompression required, therefore intermediate read buffer required */ + pState->read_buf_size = MZ_MIN(pState->file_stat.m_comp_size, (mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE); + if (NULL == (pState->pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)pState->read_buf_size))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + } + else + { + /* Decompression not required - we will be reading directly into user buffer, no temp buf required */ + pState->read_buf_size = 0; + } + pState->read_buf_avail = 0; + pState->comp_remaining = pState->file_stat.m_comp_size; + } + + if (!((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method))) + { + /* Decompression required, init decompressor */ + tinfl_init( &pState->inflator ); + + /* Allocate write buffer */ + if (NULL == (pState->pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + if (pState->pRead_buf) + pZip->m_pFree(pZip->m_pAlloc_opaque, pState->pRead_buf); + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + return NULL; + } + } + + return pState; +} + +mz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags) +{ + mz_uint32 file_index; + + /* Locate file index by name */ + if (!mz_zip_reader_locate_file_v2(pZip, pFilename, NULL, flags, &file_index)) + return NULL; + + /* Construct iterator */ + return mz_zip_reader_extract_iter_new(pZip, file_index, flags); +} + +size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size) +{ + size_t copied_to_caller = 0; + + /* Argument sanity check */ + if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState) || (!pvBuf)) + return 0; + + if ((pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!pState->file_stat.m_method)) + { + /* The file is stored or the caller has requested the compressed data, calc amount to return. */ + copied_to_caller = (size_t)MZ_MIN( buf_size, pState->comp_remaining ); + + /* Zip is in memory....or requires reading from a file? */ + if (pState->pZip->m_pState->m_pMem) + { + /* Copy data to caller's buffer */ + memcpy( pvBuf, pState->pRead_buf, copied_to_caller ); + pState->pRead_buf = ((mz_uint8*)pState->pRead_buf) + copied_to_caller; + } + else + { + /* Read directly into caller's buffer */ + if (pState->pZip->m_pRead(pState->pZip->m_pIO_opaque, pState->cur_file_ofs, pvBuf, copied_to_caller) != copied_to_caller) + { + /* Failed to read all that was asked for, flag failure and alert user */ + mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED); + pState->status = TINFL_STATUS_FAILED; + copied_to_caller = 0; + } + } + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + /* Compute CRC if not returning compressed data only */ + if (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, (const mz_uint8 *)pvBuf, copied_to_caller); +#endif + + /* Advance offsets, dec counters */ + pState->cur_file_ofs += copied_to_caller; + pState->out_buf_ofs += copied_to_caller; + pState->comp_remaining -= copied_to_caller; + } + else + { + do + { + /* Calc ptr to write buffer - given current output pos and block size */ + mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pState->pWrite_buf + (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + + /* Calc max output size - given current output pos and block size */ + size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (pState->out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + + if (!pState->out_blk_remain) + { + /* Read more data from file if none available (and reading from file) */ + if ((!pState->read_buf_avail) && (!pState->pZip->m_pState->m_pMem)) + { + /* Calc read size */ + pState->read_buf_avail = MZ_MIN(pState->read_buf_size, pState->comp_remaining); + if (pState->pZip->m_pRead(pState->pZip->m_pIO_opaque, pState->cur_file_ofs, pState->pRead_buf, (size_t)pState->read_buf_avail) != pState->read_buf_avail) + { + mz_zip_set_error(pState->pZip, MZ_ZIP_FILE_READ_FAILED); + pState->status = TINFL_STATUS_FAILED; + break; + } + + /* Advance offsets, dec counters */ + pState->cur_file_ofs += pState->read_buf_avail; + pState->comp_remaining -= pState->read_buf_avail; + pState->read_buf_ofs = 0; + } + + /* Perform decompression */ + in_buf_size = (size_t)pState->read_buf_avail; + pState->status = tinfl_decompress(&pState->inflator, (const mz_uint8 *)pState->pRead_buf + pState->read_buf_ofs, &in_buf_size, (mz_uint8 *)pState->pWrite_buf, pWrite_buf_cur, &out_buf_size, pState->comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); + pState->read_buf_avail -= in_buf_size; + pState->read_buf_ofs += in_buf_size; + + /* Update current output block size remaining */ + pState->out_blk_remain = out_buf_size; + } + + if (pState->out_blk_remain) + { + /* Calc amount to return. */ + size_t to_copy = MZ_MIN( (buf_size - copied_to_caller), pState->out_blk_remain ); + + /* Copy data to caller's buffer */ + memcpy( (uint8_t*)pvBuf + copied_to_caller, pWrite_buf_cur, to_copy ); + +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + /* Perform CRC */ + pState->file_crc32 = (mz_uint32)mz_crc32(pState->file_crc32, pWrite_buf_cur, to_copy); +#endif + + /* Decrement data consumed from block */ + pState->out_blk_remain -= to_copy; + + /* Inc output offset, while performing sanity check */ + if ((pState->out_buf_ofs += to_copy) > pState->file_stat.m_uncomp_size) + { + mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED); + pState->status = TINFL_STATUS_FAILED; + break; + } + + /* Increment counter of data copied to caller */ + copied_to_caller += to_copy; + } + } while ( (copied_to_caller < buf_size) && ((pState->status == TINFL_STATUS_NEEDS_MORE_INPUT) || (pState->status == TINFL_STATUS_HAS_MORE_OUTPUT)) ); + } + + /* Return how many bytes were copied into user buffer */ + return copied_to_caller; +} + +mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState) +{ + int status; + + /* Argument sanity check */ + if ((!pState) || (!pState->pZip) || (!pState->pZip->m_pState)) + return MZ_FALSE; + + /* Was decompression completed and requested? */ + if ((pState->status == TINFL_STATUS_DONE) && (!(pState->flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) + { + /* Make sure the entire file was decompressed, and check its CRC. */ + if (pState->out_buf_ofs != pState->file_stat.m_uncomp_size) + { + mz_zip_set_error(pState->pZip, MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE); + pState->status = TINFL_STATUS_FAILED; + } +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + else if (pState->file_crc32 != pState->file_stat.m_crc32) + { + mz_zip_set_error(pState->pZip, MZ_ZIP_DECOMPRESSION_FAILED); + pState->status = TINFL_STATUS_FAILED; + } +#endif + } + + /* Free buffers */ + if (!pState->pZip->m_pState->m_pMem) + pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pRead_buf); + if (pState->pWrite_buf) + pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState->pWrite_buf); + + /* Save status */ + status = pState->status; + + /* Free context */ + pState->pZip->m_pFree(pState->pZip->m_pAlloc_opaque, pState); + + return status == TINFL_STATUS_DONE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, const void *pBuf, size_t n) +{ + (void)ofs; + + return MZ_FWRITE(pBuf, 1, n, (MZ_FILE *)pOpaque); +} + +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags) +{ + mz_bool status; + mz_zip_archive_file_stat file_stat; + MZ_FILE *pFile; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + if ((file_stat.m_is_directory) || (!file_stat.m_is_supported)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); + + pFile = MZ_FOPEN(pDst_filename, "wb"); + if (!pFile) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + + status = mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); + + if (MZ_FCLOSE(pFile) == EOF) + { + if (status) + mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); + + status = MZ_FALSE; + } + +#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO) + if (status) + mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time); +#endif + + return status; +} + +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index)) + return MZ_FALSE; + + return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags); +} + +mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *pFile, mz_uint flags) +{ + mz_zip_archive_file_stat file_stat; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + if ((file_stat.m_is_directory) || (!file_stat.m_is_supported)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); + + return mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); +} + +mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags) +{ + mz_uint32 file_index; + if (!mz_zip_reader_locate_file_v2(pZip, pArchive_filename, NULL, flags, &file_index)) + return MZ_FALSE; + + return mz_zip_reader_extract_to_cfile(pZip, file_index, pFile, flags); +} +#endif /* #ifndef MINIZ_NO_STDIO */ + +static size_t mz_zip_compute_crc32_callback(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_uint32 *p = (mz_uint32 *)pOpaque; + (void)file_ofs; + *p = (mz_uint32)mz_crc32(*p, (const mz_uint8 *)pBuf, n); + return n; +} + +mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags) +{ + mz_zip_archive_file_stat file_stat; + mz_zip_internal_state *pState; + const mz_uint8 *pCentral_dir_header; + mz_bool found_zip64_ext_data_in_cdir = MZ_FALSE; + mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + mz_uint64 local_header_ofs = 0; + mz_uint32 local_header_filename_len, local_header_extra_len, local_header_crc32; + mz_uint64 local_header_comp_size, local_header_uncomp_size; + mz_uint32 uncomp_crc32 = MZ_CRC32_INIT; + mz_bool has_data_descriptor; + mz_uint32 local_header_bit_flags; + + mz_zip_array file_data_array; + mz_zip_array_init(&file_data_array, 1); + + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (file_index > pZip->m_total_files) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + pCentral_dir_header = mz_zip_get_cdh(pZip, file_index); + + if (!mz_zip_file_stat_internal(pZip, file_index, pCentral_dir_header, &file_stat, &found_zip64_ext_data_in_cdir)) + return MZ_FALSE; + + /* A directory or zero length file */ + if ((file_stat.m_is_directory) || (!file_stat.m_uncomp_size)) + return MZ_TRUE; + + /* Encryption and patch files are not supported. */ + if (file_stat.m_is_encrypted) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + + /* This function only supports stored and deflate. */ + if ((file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_METHOD); + + if (!file_stat.m_is_supported) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_FEATURE); + + /* Read and parse the local directory entry. */ + local_header_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + local_header_filename_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS); + local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS); + local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS); + local_header_crc32 = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_CRC32_OFS); + local_header_bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); + has_data_descriptor = (local_header_bit_flags & 8) != 0; + + if (local_header_filename_len != strlen(file_stat.m_filename)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if ((local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len + local_header_extra_len + file_stat.m_comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (!mz_zip_array_resize(pZip, &file_data_array, MZ_MAX(local_header_filename_len, local_header_extra_len), MZ_FALSE)) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + goto handle_failure; + } + + if (local_header_filename_len) + { + if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE, file_data_array.m_p, local_header_filename_len) != local_header_filename_len) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + goto handle_failure; + } + + /* I've seen 1 archive that had the same pathname, but used backslashes in the local dir and forward slashes in the central dir. Do we care about this? For now, this case will fail validation. */ + if (memcmp(file_stat.m_filename, file_data_array.m_p, local_header_filename_len) != 0) + { + mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + goto handle_failure; + } + } + + if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX))) + { + mz_uint32 extra_size_remaining = local_header_extra_len; + const mz_uint8 *pExtra_data = (const mz_uint8 *)file_data_array.m_p; + + if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len, file_data_array.m_p, local_header_extra_len) != local_header_extra_len) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + goto handle_failure; + } + + do + { + mz_uint32 field_id, field_data_size, field_total_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + goto handle_failure; + } + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + field_total_size = field_data_size + sizeof(mz_uint16) * 2; + + if (field_total_size > extra_size_remaining) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + goto handle_failure; + } + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32); + + if (field_data_size < sizeof(mz_uint64) * 2) + { + mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + goto handle_failure; + } + + local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data); + local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64)); + + found_zip64_ext_data_in_ldir = MZ_TRUE; + break; + } + + pExtra_data += field_total_size; + extra_size_remaining -= field_total_size; + } while (extra_size_remaining); + } + + /* TODO: parse local header extra data when local_header_comp_size is 0xFFFFFFFF! (big_descriptor.zip) */ + /* I've seen zips in the wild with the data descriptor bit set, but proper local header values and bogus data descriptors */ + if ((has_data_descriptor) && (!local_header_comp_size) && (!local_header_crc32)) + { + mz_uint8 descriptor_buf[32]; + mz_bool has_id; + const mz_uint8 *pSrc; + mz_uint32 file_crc32; + mz_uint64 comp_size = 0, uncomp_size = 0; + + mz_uint32 num_descriptor_uint32s = ((pState->m_zip64) || (found_zip64_ext_data_in_ldir)) ? 6 : 4; + + if (pZip->m_pRead(pZip->m_pIO_opaque, local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_len + local_header_extra_len + file_stat.m_comp_size, descriptor_buf, sizeof(mz_uint32) * num_descriptor_uint32s) != (sizeof(mz_uint32) * num_descriptor_uint32s)) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + goto handle_failure; + } + + has_id = (MZ_READ_LE32(descriptor_buf) == MZ_ZIP_DATA_DESCRIPTOR_ID); + pSrc = has_id ? (descriptor_buf + sizeof(mz_uint32)) : descriptor_buf; + + file_crc32 = MZ_READ_LE32(pSrc); + + if ((pState->m_zip64) || (found_zip64_ext_data_in_ldir)) + { + comp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32)); + uncomp_size = MZ_READ_LE64(pSrc + sizeof(mz_uint32) + sizeof(mz_uint64)); + } + else + { + comp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32)); + uncomp_size = MZ_READ_LE32(pSrc + sizeof(mz_uint32) + sizeof(mz_uint32)); + } + + if ((file_crc32 != file_stat.m_crc32) || (comp_size != file_stat.m_comp_size) || (uncomp_size != file_stat.m_uncomp_size)) + { + mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + goto handle_failure; + } + } + else + { + if ((local_header_crc32 != file_stat.m_crc32) || (local_header_comp_size != file_stat.m_comp_size) || (local_header_uncomp_size != file_stat.m_uncomp_size)) + { + mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + goto handle_failure; + } + } + + mz_zip_array_clear(pZip, &file_data_array); + + if ((flags & MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY) == 0) + { + if (!mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_compute_crc32_callback, &uncomp_crc32, 0)) + return MZ_FALSE; + + /* 1 more check to be sure, although the extract checks too. */ + if (uncomp_crc32 != file_stat.m_crc32) + { + mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + return MZ_FALSE; + } + } + + return MZ_TRUE; + +handle_failure: + mz_zip_array_clear(pZip, &file_data_array); + return MZ_FALSE; +} + +mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags) +{ + mz_zip_internal_state *pState; + uint32_t i; + + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + /* Basic sanity checks */ + if (!pState->m_zip64) + { + if (pZip->m_total_files > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + if (pZip->m_archive_size > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + } + else + { + if (pZip->m_total_files >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + if (pState->m_central_dir.m_size >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + } + + for (i = 0; i < pZip->m_total_files; i++) + { + if (MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG & flags) + { + mz_uint32 found_index; + mz_zip_archive_file_stat stat; + + if (!mz_zip_reader_file_stat(pZip, i, &stat)) + return MZ_FALSE; + + if (!mz_zip_reader_locate_file_v2(pZip, stat.m_filename, NULL, 0, &found_index)) + return MZ_FALSE; + + /* This check can fail if there are duplicate filenames in the archive (which we don't check for when writing - that's up to the user) */ + if (found_index != i) + return mz_zip_set_error(pZip, MZ_ZIP_VALIDATION_FAILED); + } + + if (!mz_zip_validate_file(pZip, i, flags)) + return MZ_FALSE; + } + + return MZ_TRUE; +} + +mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr) +{ + mz_bool success = MZ_TRUE; + mz_zip_archive zip; + mz_zip_error actual_err = MZ_ZIP_NO_ERROR; + + if ((!pMem) || (!size)) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_PARAMETER; + return MZ_FALSE; + } + + mz_zip_zero_struct(&zip); + + if (!mz_zip_reader_init_mem(&zip, pMem, size, flags)) + { + if (pErr) + *pErr = zip.m_last_error; + return MZ_FALSE; + } + + if (!mz_zip_validate_archive(&zip, flags)) + { + actual_err = zip.m_last_error; + success = MZ_FALSE; + } + + if (!mz_zip_reader_end_internal(&zip, success)) + { + if (!actual_err) + actual_err = zip.m_last_error; + success = MZ_FALSE; + } + + if (pErr) + *pErr = actual_err; + + return success; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr) +{ + mz_bool success = MZ_TRUE; + mz_zip_archive zip; + mz_zip_error actual_err = MZ_ZIP_NO_ERROR; + + if (!pFilename) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_PARAMETER; + return MZ_FALSE; + } + + mz_zip_zero_struct(&zip); + + if (!mz_zip_reader_init_file_v2(&zip, pFilename, flags, 0, 0)) + { + if (pErr) + *pErr = zip.m_last_error; + return MZ_FALSE; + } + + if (!mz_zip_validate_archive(&zip, flags)) + { + actual_err = zip.m_last_error; + success = MZ_FALSE; + } + + if (!mz_zip_reader_end_internal(&zip, success)) + { + if (!actual_err) + actual_err = zip.m_last_error; + success = MZ_FALSE; + } + + if (pErr) + *pErr = actual_err; + + return success; +} +#endif /* #ifndef MINIZ_NO_STDIO */ + +/* ------------------- .ZIP archive writing */ + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +static MZ_FORCEINLINE void mz_write_le16(mz_uint8 *p, mz_uint16 v) +{ + p[0] = (mz_uint8)v; + p[1] = (mz_uint8)(v >> 8); +} +static MZ_FORCEINLINE void mz_write_le32(mz_uint8 *p, mz_uint32 v) +{ + p[0] = (mz_uint8)v; + p[1] = (mz_uint8)(v >> 8); + p[2] = (mz_uint8)(v >> 16); + p[3] = (mz_uint8)(v >> 24); +} +static MZ_FORCEINLINE void mz_write_le64(mz_uint8 *p, mz_uint64 v) +{ + mz_write_le32(p, (mz_uint32)v); + mz_write_le32(p + sizeof(mz_uint32), (mz_uint32)(v >> 32)); +} + +#define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v)) +#define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v)) +#define MZ_WRITE_LE64(p, v) mz_write_le64((mz_uint8 *)(p), (mz_uint64)(v)) + +static size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size); + + if (!n) + return 0; + + /* An allocation this big is likely to just fail on 32-bit systems, so don't even go there. */ + if ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF)) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); + return 0; + } + + if (new_size > pState->m_mem_capacity) + { + void *pNew_block; + size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity); + + while (new_capacity < new_size) + new_capacity *= 2; + + if (NULL == (pNew_block = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity))) + { + mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + return 0; + } + + pState->m_pMem = pNew_block; + pState->m_mem_capacity = new_capacity; + } + memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n); + pState->m_mem_size = (size_t)new_size; + return n; +} + +static mz_bool mz_zip_writer_end_internal(mz_zip_archive *pZip, mz_bool set_last_error) +{ + mz_zip_internal_state *pState; + mz_bool status = MZ_TRUE; + + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED))) + { + if (set_last_error) + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return MZ_FALSE; + } + + pState = pZip->m_pState; + pZip->m_pState = NULL; + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) + { + if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) + { + if (MZ_FCLOSE(pState->m_pFile) == EOF) + { + if (set_last_error) + mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); + status = MZ_FALSE; + } + } + + pState->m_pFile = NULL; + } +#endif /* #ifndef MINIZ_NO_STDIO */ + + if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem); + pState->m_pMem = NULL; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + return status; +} + +mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags) +{ + mz_bool zip64 = (flags & MZ_ZIP_FLAG_WRITE_ZIP64) != 0; + + if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) + { + if (!pZip->m_pRead) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + } + + if (pZip->m_file_offset_alignment) + { + /* Ensure user specified file offset alignment is a power of 2. */ + if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + } + + if (!pZip->m_pAlloc) + pZip->m_pAlloc = miniz_def_alloc_func; + if (!pZip->m_pFree) + pZip->m_pFree = miniz_def_free_func; + if (!pZip->m_pRealloc) + pZip->m_pRealloc = miniz_def_realloc_func; + + pZip->m_archive_size = existing_size; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); + + pZip->m_pState->m_zip64 = zip64; + pZip->m_pState->m_zip64_has_extended_info_fields = zip64; + + pZip->m_zip_type = MZ_ZIP_TYPE_USER; + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size) +{ + return mz_zip_writer_init_v2(pZip, existing_size, 0); +} + +mz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags) +{ + pZip->m_pWrite = mz_zip_heap_write_func; + pZip->m_pNeeds_keepalive = NULL; + + if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) + pZip->m_pRead = mz_zip_mem_read_func; + + pZip->m_pIO_opaque = pZip; + + if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags)) + return MZ_FALSE; + + pZip->m_zip_type = MZ_ZIP_TYPE_HEAP; + + if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, size_to_reserve_at_beginning))) + { + if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, initial_allocation_size))) + { + mz_zip_writer_end_internal(pZip, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + pZip->m_pState->m_mem_capacity = initial_allocation_size; + } + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size) +{ + return mz_zip_writer_init_heap_v2(pZip, size_to_reserve_at_beginning, initial_allocation_size, 0); +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + + file_ofs += pZip->m_pState->m_file_archive_start_ofs; + + if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_SEEK_FAILED); + return 0; + } + + return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning) +{ + return mz_zip_writer_init_file_v2(pZip, pFilename, size_to_reserve_at_beginning, 0); +} + +mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags) +{ + MZ_FILE *pFile; + + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pNeeds_keepalive = NULL; + + if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) + pZip->m_pRead = mz_zip_file_read_func; + + pZip->m_pIO_opaque = pZip; + + if (!mz_zip_writer_init_v2(pZip, size_to_reserve_at_beginning, flags)) + return MZ_FALSE; + + if (NULL == (pFile = MZ_FOPEN(pFilename, (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) ? "w+b" : "wb"))) + { + mz_zip_writer_end(pZip); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + } + + pZip->m_pState->m_pFile = pFile; + pZip->m_zip_type = MZ_ZIP_TYPE_FILE; + + if (size_to_reserve_at_beginning) + { + mz_uint64 cur_ofs = 0; + char buf[4096]; + + MZ_CLEAR_OBJ(buf); + + do + { + size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n) + { + mz_zip_writer_end(pZip); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + cur_ofs += n; + size_to_reserve_at_beginning -= n; + } while (size_to_reserve_at_beginning); + } + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags) +{ + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pNeeds_keepalive = NULL; + + if (flags & MZ_ZIP_FLAG_WRITE_ALLOW_READING) + pZip->m_pRead = mz_zip_file_read_func; + + pZip->m_pIO_opaque = pZip; + + if (!mz_zip_writer_init_v2(pZip, 0, flags)) + return MZ_FALSE; + + pZip->m_pState->m_pFile = pFile; + pZip->m_pState->m_file_archive_start_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + pZip->m_zip_type = MZ_ZIP_TYPE_CFILE; + + return MZ_TRUE; +} +#endif /* #ifndef MINIZ_NO_STDIO */ + +mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags) +{ + mz_zip_internal_state *pState; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (flags & MZ_ZIP_FLAG_WRITE_ZIP64) + { + /* We don't support converting a non-zip64 file to zip64 - this seems like more trouble than it's worth. (What about the existing 32-bit data descriptors that could follow the compressed data?) */ + if (!pZip->m_pState->m_zip64) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + } + + /* No sense in trying to write to an archive that's already at the support max size */ + if (pZip->m_pState->m_zip64) + { + if (pZip->m_total_files == MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + if (pZip->m_total_files == MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + if ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); + } + + pState = pZip->m_pState; + + if (pState->m_pFile) + { +#ifdef MINIZ_NO_STDIO + (void)pFilename; + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); +#else + if (pZip->m_pIO_opaque != pZip) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (pZip->m_zip_type == MZ_ZIP_TYPE_FILE) + { + if (!pFilename) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* Archive is being read from stdio and was originally opened only for reading. Try to reopen as writable. */ + if (NULL == (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile))) + { + /* The mz_zip_archive is now in a bogus state because pState->m_pFile is NULL, so just close it. */ + mz_zip_reader_end_internal(pZip, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + } + } + + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pNeeds_keepalive = NULL; +#endif /* #ifdef MINIZ_NO_STDIO */ + } + else if (pState->m_pMem) + { + /* Archive lives in a memory block. Assume it's from the heap that we can resize using the realloc callback. */ + if (pZip->m_pIO_opaque != pZip) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState->m_mem_capacity = pState->m_mem_size; + pZip->m_pWrite = mz_zip_heap_write_func; + pZip->m_pNeeds_keepalive = NULL; + } + /* Archive is being read via a user provided read function - make sure the user has specified a write function too. */ + else if (!pZip->m_pWrite) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* Start writing new files at the archive's current central directory location. */ + /* TODO: We could add a flag that lets the user start writing immediately AFTER the existing central dir - this would be safer. */ + pZip->m_archive_size = pZip->m_central_directory_file_ofs; + pZip->m_central_directory_file_ofs = 0; + + /* Clear the sorted central dir offsets, they aren't useful or maintained now. */ + /* Even though we're now in write mode, files can still be extracted and verified, but file locates will be slow. */ + /* TODO: We could easily maintain the sorted central directory offsets. */ + mz_zip_array_clear(pZip, &pZip->m_pState->m_sorted_central_dir_offsets); + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename) +{ + return mz_zip_writer_init_from_reader_v2(pZip, pFilename, 0); +} + +/* TODO: pArchive_name is a terrible name here! */ +mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags) +{ + return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, level_and_flags, 0, 0); +} + +typedef struct +{ + mz_zip_archive *m_pZip; + mz_uint64 m_cur_archive_file_ofs; + mz_uint64 m_comp_size; +} mz_zip_writer_add_state; + +static mz_bool mz_zip_writer_add_put_buf_callback(const void *pBuf, int len, void *pUser) +{ + mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser; + if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, pState->m_cur_archive_file_ofs, pBuf, len) != len) + return MZ_FALSE; + + pState->m_cur_archive_file_ofs += len; + pState->m_comp_size += len; + return MZ_TRUE; +} + +#define MZ_ZIP64_MAX_LOCAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 2) +#define MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE (sizeof(mz_uint16) * 2 + sizeof(mz_uint64) * 3) +static mz_uint32 mz_zip_writer_create_zip64_extra_data(mz_uint8 *pBuf, mz_uint64 *pUncomp_size, mz_uint64 *pComp_size, mz_uint64 *pLocal_header_ofs) +{ + mz_uint8 *pDst = pBuf; + mz_uint32 field_size = 0; + + MZ_WRITE_LE16(pDst + 0, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID); + MZ_WRITE_LE16(pDst + 2, 0); + pDst += sizeof(mz_uint16) * 2; + + if (pUncomp_size) + { + MZ_WRITE_LE64(pDst, *pUncomp_size); + pDst += sizeof(mz_uint64); + field_size += sizeof(mz_uint64); + } + + if (pComp_size) + { + MZ_WRITE_LE64(pDst, *pComp_size); + pDst += sizeof(mz_uint64); + field_size += sizeof(mz_uint64); + } + + if (pLocal_header_ofs) + { + MZ_WRITE_LE64(pDst, *pLocal_header_ofs); + pDst += sizeof(mz_uint64); + field_size += sizeof(mz_uint64); + } + + MZ_WRITE_LE16(pBuf + 2, field_size); + + return (mz_uint32)(pDst - pBuf); +} + +static mz_bool mz_zip_writer_create_local_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date) +{ + (void)pZip; + memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX)); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX)); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_create_central_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, + mz_uint16 filename_size, mz_uint16 extra_size, mz_uint16 comment_size, + mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, + mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, + mz_uint64 local_header_ofs, mz_uint32 ext_attributes) +{ + (void)pZip; + memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_MIN(comp_size, MZ_UINT32_MAX)); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_MIN(uncomp_size, MZ_UINT32_MAX)); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_MIN(local_header_ofs, MZ_UINT32_MAX)); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_add_to_central_dir(mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size, + const void *pExtra, mz_uint16 extra_size, const void *pComment, mz_uint16 comment_size, + mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, + mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, + mz_uint64 local_header_ofs, mz_uint32 ext_attributes, + const char *user_extra_data, mz_uint user_extra_data_len) +{ + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size; + size_t orig_central_dir_size = pState->m_central_dir.m_size; + mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + + if (!pZip->m_pState->m_zip64) + { + if (local_header_ofs > 0xFFFFFFFF) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_TOO_LARGE); + } + + /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ + if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + extra_size + user_extra_data_len + comment_size) >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + if (!mz_zip_writer_create_central_dir_header(pZip, central_dir_header, filename_size, (mz_uint16)(extra_size + user_extra_data_len), comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_header_ofs, ext_attributes)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, filename_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, extra_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, user_extra_data, user_extra_data_len)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, comment_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, ¢ral_dir_ofs, 1))) + { + /* Try to resize the central directory array back into its original state. */ + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name) +{ + /* Basic ZIP archive filename validity checks: Valid filenames cannot start with a forward slash, cannot contain a drive letter, and cannot use DOS-style backward slashes. */ + if (*pArchive_name == '/') + return MZ_FALSE; + + /* Making sure the name does not contain drive letters or DOS style backward slashes is the responsibility of the program using miniz*/ + + return MZ_TRUE; +} + +static mz_uint mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip) +{ + mz_uint32 n; + if (!pZip->m_file_offset_alignment) + return 0; + n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1)); + return (mz_uint)((pZip->m_file_offset_alignment - n) & (pZip->m_file_offset_alignment - 1)); +} + +static mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, mz_uint64 cur_file_ofs, mz_uint32 n) +{ + char buf[4096]; + memset(buf, 0, MZ_MIN(sizeof(buf), n)); + while (n) + { + mz_uint32 s = MZ_MIN(sizeof(buf), n); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_file_ofs += s; + n -= s; + } + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, + mz_uint64 uncomp_size, mz_uint32 uncomp_crc32) +{ + return mz_zip_writer_add_mem_ex_v2(pZip, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, uncomp_size, uncomp_crc32, NULL, NULL, 0, NULL, 0); +} + +mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, + mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified, + const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) +{ + mz_uint16 method = 0, dos_time = 0, dos_date = 0; + mz_uint level, ext_attributes = 0, num_alignment_padding_bytes; + mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + tdefl_compressor *pComp = NULL; + mz_bool store_data_uncompressed; + mz_zip_internal_state *pState; + mz_uint8 *pExtra_data = NULL; + mz_uint32 extra_size = 0; + mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE]; + mz_uint16 bit_flags = 0; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + + if (uncomp_size || (buf_size && !(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) + bit_flags |= MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR; + + if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME)) + bit_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8; + + level = level_and_flags & 0xF; + store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)); + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + if (pState->m_zip64) + { + if (pZip->m_total_files == MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + if (pZip->m_total_files == MZ_UINT16_MAX) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */ + } + if ((buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF)) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ + } + } + + if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); + +#ifndef MINIZ_NO_TIME + if (last_modified != NULL) + { + mz_zip_time_t_to_dos_time(*last_modified, &dos_time, &dos_date); + } + else + { + MZ_TIME_T cur_time; + time(&cur_time); + mz_zip_time_t_to_dos_time(cur_time, &dos_time, &dos_date); + } +#endif /* #ifndef MINIZ_NO_TIME */ + + if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { + uncomp_crc32 = (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, buf_size); + uncomp_size = buf_size; + if (uncomp_size <= 3) + { + level = 0; + store_data_uncompressed = MZ_TRUE; + } + } + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ + if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE + comment_size) >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + if (!pState->m_zip64) + { + /* Bail early if the archive would obviously become too large */ + if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size + + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + user_extra_data_len + + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + user_extra_data_central_len + + MZ_ZIP_DATA_DESCRIPTER_SIZE32) > 0xFFFFFFFF) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ + } + } + + if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/')) + { + /* Set DOS Subdirectory attribute bit. */ + ext_attributes |= MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG; + + /* Subdirectories cannot contain data. */ + if ((buf_size) || (uncomp_size)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + } + + /* Try to do any allocations before writing to the archive, so if an allocation fails the file remains unmodified. (A good idea if we're doing an in-place modification.) */ + if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size + (pState->m_zip64 ? MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE : 0))) || (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if ((!store_data_uncompressed) && (buf_size)) + { + if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + + local_dir_header_ofs += num_alignment_padding_bytes; + if (pZip->m_file_offset_alignment) + { + MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); + } + cur_archive_file_ofs += num_alignment_padding_bytes; + + MZ_CLEAR_OBJ(local_dir_header); + + if (!store_data_uncompressed || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { + method = MZ_DEFLATED; + } + + if (pState->m_zip64) + { + if (uncomp_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX) + { + pExtra_data = extra_data; + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len), 0, 0, 0, method, bit_flags, dos_time, dos_date)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + cur_archive_file_ofs += archive_name_size; + + if (pExtra_data != NULL) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += extra_size; + } + } + else + { + if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)user_extra_data_len, 0, 0, 0, method, bit_flags, dos_time, dos_date)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + cur_archive_file_ofs += archive_name_size; + } + + if (user_extra_data_len > 0) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += user_extra_data_len; + } + + if (store_data_uncompressed) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, buf_size) != buf_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_file_ofs += buf_size; + comp_size = buf_size; + } + else if (buf_size) + { + mz_zip_writer_add_state state; + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) || + (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != TDEFL_STATUS_DONE)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED); + } + + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pComp = NULL; + + if (uncomp_size) + { + mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64]; + mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32; + + MZ_ASSERT(bit_flags & MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR); + + MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID); + MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32); + if (pExtra_data == NULL) + { + if (comp_size > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + MZ_WRITE_LE32(local_dir_footer + 8, comp_size); + MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size); + } + else + { + MZ_WRITE_LE64(local_dir_footer + 8, comp_size); + MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size); + local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64; + } + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size) + return MZ_FALSE; + + cur_archive_file_ofs += local_dir_footer_size; + } + + if (pExtra_data != NULL) + { + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, pExtra_data, (mz_uint16)extra_size, pComment, + comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes, + user_extra_data_central, user_extra_data_central_len)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pArchive_name, mz_file_read_func read_callback, void* callback_opaque, mz_uint64 max_size, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, + const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) +{ + mz_uint16 gen_flags = (level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE) ? 0 : MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR; + mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes; + mz_uint16 method = 0, dos_time = 0, dos_date = 0, ext_attributes = 0; + mz_uint64 local_dir_header_ofs, cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = 0, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + mz_uint8 *pExtra_data = NULL; + mz_uint32 extra_size = 0; + mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE]; + mz_zip_internal_state *pState; + mz_uint64 file_ofs = 0, cur_archive_header_file_ofs; + + if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME)) + gen_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + level = level_and_flags & 0xF; + + /* Sanity checks */ + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + if ((!pState->m_zip64) && (max_size > MZ_UINT32_MAX)) + { + /* Source file is too large for non-zip64 */ + /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ + pState->m_zip64 = MZ_TRUE; + } + + /* We could support this, but why? */ + if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); + + if (pState->m_zip64) + { + if (pZip->m_total_files == MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + if (pZip->m_total_files == MZ_UINT16_MAX) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); */ + } + } + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_FILENAME); + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + /* miniz doesn't support central dirs >= MZ_UINT32_MAX bytes yet */ + if (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE + comment_size) >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + if (!pState->m_zip64) + { + /* Bail early if the archive would obviously become too large */ + if ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + archive_name_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + archive_name_size + comment_size + user_extra_data_len + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + 1024 + + MZ_ZIP_DATA_DESCRIPTER_SIZE32 + user_extra_data_central_len) > 0xFFFFFFFF) + { + pState->m_zip64 = MZ_TRUE; + /*return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); */ + } + } + +#ifndef MINIZ_NO_TIME + if (pFile_time) + { + mz_zip_time_t_to_dos_time(*pFile_time, &dos_time, &dos_date); + } +#endif + + if (max_size <= 3) + level = 0; + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes)) + { + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_file_ofs += num_alignment_padding_bytes; + local_dir_header_ofs = cur_archive_file_ofs; + + if (pZip->m_file_offset_alignment) + { + MZ_ASSERT((cur_archive_file_ofs & (pZip->m_file_offset_alignment - 1)) == 0); + } + + if (max_size && level) + { + method = MZ_DEFLATED; + } + + MZ_CLEAR_OBJ(local_dir_header); + if (pState->m_zip64) + { + if (max_size >= MZ_UINT32_MAX || local_dir_header_ofs >= MZ_UINT32_MAX) + { + pExtra_data = extra_data; + if (level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE) + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (max_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (max_size >= MZ_UINT32_MAX) ? &comp_size : NULL, + (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + else + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, NULL, + NULL, + (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len), 0, 0, 0, method, gen_flags, dos_time, dos_date)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_file_ofs += archive_name_size; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, extra_data, extra_size) != extra_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += extra_size; + } + else + { + if ((comp_size > MZ_UINT32_MAX) || (cur_archive_file_ofs > MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, (mz_uint16)user_extra_data_len, 0, 0, 0, method, gen_flags, dos_time, dos_date)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_file_ofs += archive_name_size; + } + + if (user_extra_data_len > 0) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, user_extra_data, user_extra_data_len) != user_extra_data_len) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_file_ofs += user_extra_data_len; + } + + if (max_size) + { + void *pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE); + if (!pRead_buf) + { + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!level) + { + while (1) + { + size_t n = read_callback(callback_opaque, file_ofs, pRead_buf, MZ_ZIP_MAX_IO_BUF_SIZE); + if (n == 0) + break; + + if ((n > MZ_ZIP_MAX_IO_BUF_SIZE) || (file_ofs + n > max_size)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + file_ofs += n; + uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); + cur_archive_file_ofs += n; + } + uncomp_size = file_ofs; + comp_size = uncomp_size; + } + else + { + mz_bool result = MZ_FALSE; + mz_zip_writer_add_state state; + tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + } + + for (;;) + { + tdefl_status status; + tdefl_flush flush = TDEFL_NO_FLUSH; + + size_t n = read_callback(callback_opaque, file_ofs, pRead_buf, MZ_ZIP_MAX_IO_BUF_SIZE); + if ((n > MZ_ZIP_MAX_IO_BUF_SIZE) || (file_ofs + n > max_size)) + { + mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + break; + } + + file_ofs += n; + uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); + + if (pZip->m_pNeeds_keepalive != NULL && pZip->m_pNeeds_keepalive(pZip->m_pIO_opaque)) + flush = TDEFL_FULL_FLUSH; + + if (n == 0) + flush = TDEFL_FINISH; + + status = tdefl_compress_buffer(pComp, pRead_buf, n, flush); + if (status == TDEFL_STATUS_DONE) + { + result = MZ_TRUE; + break; + } + else if (status != TDEFL_STATUS_OKAY) + { + mz_zip_set_error(pZip, MZ_ZIP_COMPRESSION_FAILED); + break; + } + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + + if (!result) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + return MZ_FALSE; + } + + uncomp_size = file_ofs; + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + } + + if (!(level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE)) + { + mz_uint8 local_dir_footer[MZ_ZIP_DATA_DESCRIPTER_SIZE64]; + mz_uint32 local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE32; + + MZ_WRITE_LE32(local_dir_footer + 0, MZ_ZIP_DATA_DESCRIPTOR_ID); + MZ_WRITE_LE32(local_dir_footer + 4, uncomp_crc32); + if (pExtra_data == NULL) + { + if (comp_size > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + MZ_WRITE_LE32(local_dir_footer + 8, comp_size); + MZ_WRITE_LE32(local_dir_footer + 12, uncomp_size); + } + else + { + MZ_WRITE_LE64(local_dir_footer + 8, comp_size); + MZ_WRITE_LE64(local_dir_footer + 16, uncomp_size); + local_dir_footer_size = MZ_ZIP_DATA_DESCRIPTER_SIZE64; + } + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, local_dir_footer, local_dir_footer_size) != local_dir_footer_size) + return MZ_FALSE; + + cur_archive_file_ofs += local_dir_footer_size; + } + + if (level_and_flags & MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE) + { + if (pExtra_data != NULL) + { + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (max_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (max_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, + (mz_uint16)archive_name_size, (mz_uint16)(extra_size + user_extra_data_len), + (max_size >= MZ_UINT32_MAX) ? MZ_UINT32_MAX : uncomp_size, + (max_size >= MZ_UINT32_MAX) ? MZ_UINT32_MAX : comp_size, + uncomp_crc32, method, gen_flags, dos_time, dos_date)) + return mz_zip_set_error(pZip, MZ_ZIP_INTERNAL_ERROR); + + cur_archive_header_file_ofs = local_dir_header_ofs; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_header_file_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + if (pExtra_data != NULL) + { + cur_archive_header_file_ofs += sizeof(local_dir_header); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_header_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_archive_header_file_ofs += archive_name_size; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_header_file_ofs, extra_data, extra_size) != extra_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_archive_header_file_ofs += extra_size; + } + } + + if (pExtra_data != NULL) + { + extra_size = mz_zip_writer_create_zip64_extra_data(extra_data, (uncomp_size >= MZ_UINT32_MAX) ? &uncomp_size : NULL, + (uncomp_size >= MZ_UINT32_MAX) ? &comp_size : NULL, (local_dir_header_ofs >= MZ_UINT32_MAX) ? &local_dir_header_ofs : NULL); + } + + if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, pExtra_data, (mz_uint16)extra_size, pComment, comment_size, + uncomp_size, comp_size, uncomp_crc32, method, gen_flags, dos_time, dos_date, local_dir_header_ofs, ext_attributes, + user_extra_data_central, user_extra_data_central_len)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO + +static size_t mz_file_read_func_stdio(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + MZ_FILE *pSrc_file = (MZ_FILE *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pSrc_file); + + if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pSrc_file, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + + return MZ_FREAD(pBuf, 1, n, pSrc_file); +} + +mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 max_size, const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, + const char *user_extra_data, mz_uint user_extra_data_len, const char *user_extra_data_central, mz_uint user_extra_data_central_len) +{ + return mz_zip_writer_add_read_buf_callback(pZip, pArchive_name, mz_file_read_func_stdio, pSrc_file, max_size, pFile_time, pComment, comment_size, level_and_flags, + user_extra_data, user_extra_data_len, user_extra_data_central, user_extra_data_central_len); +} + +mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) +{ + MZ_FILE *pSrc_file = NULL; + mz_uint64 uncomp_size = 0; + MZ_TIME_T file_modified_time; + MZ_TIME_T *pFile_time = NULL; + mz_bool status; + + memset(&file_modified_time, 0, sizeof(file_modified_time)); + +#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_STDIO) + pFile_time = &file_modified_time; + if (!mz_zip_get_file_modified_time(pSrc_filename, &file_modified_time)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_STAT_FAILED); +#endif + + pSrc_file = MZ_FOPEN(pSrc_filename, "rb"); + if (!pSrc_file) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_OPEN_FAILED); + + MZ_FSEEK64(pSrc_file, 0, SEEK_END); + uncomp_size = MZ_FTELL64(pSrc_file); + MZ_FSEEK64(pSrc_file, 0, SEEK_SET); + + status = mz_zip_writer_add_cfile(pZip, pArchive_name, pSrc_file, uncomp_size, pFile_time, pComment, comment_size, level_and_flags, NULL, 0, NULL, 0); + + MZ_FCLOSE(pSrc_file); + + return status; +} +#endif /* #ifndef MINIZ_NO_STDIO */ + +static mz_bool mz_zip_writer_update_zip64_extension_block(mz_zip_array *pNew_ext, mz_zip_archive *pZip, const mz_uint8 *pExt, uint32_t ext_len, mz_uint64 *pComp_size, mz_uint64 *pUncomp_size, mz_uint64 *pLocal_header_ofs, mz_uint32 *pDisk_start) +{ + /* + 64 should be enough for any new zip64 data */ + if (!mz_zip_array_reserve(pZip, pNew_ext, ext_len + 64, MZ_FALSE)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + mz_zip_array_resize(pZip, pNew_ext, 0, MZ_FALSE); + + if ((pUncomp_size) || (pComp_size) || (pLocal_header_ofs) || (pDisk_start)) + { + mz_uint8 new_ext_block[64]; + mz_uint8 *pDst = new_ext_block; + mz_write_le16(pDst, MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID); + mz_write_le16(pDst + sizeof(mz_uint16), 0); + pDst += sizeof(mz_uint16) * 2; + + if (pUncomp_size) + { + mz_write_le64(pDst, *pUncomp_size); + pDst += sizeof(mz_uint64); + } + + if (pComp_size) + { + mz_write_le64(pDst, *pComp_size); + pDst += sizeof(mz_uint64); + } + + if (pLocal_header_ofs) + { + mz_write_le64(pDst, *pLocal_header_ofs); + pDst += sizeof(mz_uint64); + } + + if (pDisk_start) + { + mz_write_le32(pDst, *pDisk_start); + pDst += sizeof(mz_uint32); + } + + mz_write_le16(new_ext_block + sizeof(mz_uint16), (mz_uint16)((pDst - new_ext_block) - sizeof(mz_uint16) * 2)); + + if (!mz_zip_array_push_back(pZip, pNew_ext, new_ext_block, pDst - new_ext_block)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if ((pExt) && (ext_len)) + { + mz_uint32 extra_size_remaining = ext_len; + const mz_uint8 *pExtra_data = pExt; + + do + { + mz_uint32 field_id, field_data_size, field_total_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + field_total_size = field_data_size + sizeof(mz_uint16) * 2; + + if (field_total_size > extra_size_remaining) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (field_id != MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + if (!mz_zip_array_push_back(pZip, pNew_ext, pExtra_data, field_total_size)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + pExtra_data += field_total_size; + extra_size_remaining -= field_total_size; + } while (extra_size_remaining); + } + + return MZ_TRUE; +} + +/* TODO: This func is now pretty freakin complex due to zip64, split it up? */ +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index) +{ + mz_uint n, bit_flags, num_alignment_padding_bytes, src_central_dir_following_data_size; + mz_uint64 src_archive_bytes_remaining, local_dir_header_ofs; + mz_uint64 cur_src_file_ofs, cur_dst_file_ofs; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; + mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + mz_uint8 new_central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + size_t orig_central_dir_size; + mz_zip_internal_state *pState; + void *pBuf; + const mz_uint8 *pSrc_central_header; + mz_zip_archive_file_stat src_file_stat; + mz_uint32 src_filename_len, src_comment_len, src_ext_len; + mz_uint32 local_header_filename_size, local_header_extra_len; + mz_uint64 local_header_comp_size, local_header_uncomp_size; + mz_bool found_zip64_ext_data_in_ldir = MZ_FALSE; + + /* Sanity checks */ + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pSource_zip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + /* Don't support copying files from zip64 archives to non-zip64, even though in some cases this is possible */ + if ((pSource_zip->m_pState->m_zip64) && (!pZip->m_pState->m_zip64)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + /* Get pointer to the source central dir header and crack it */ + if (NULL == (pSrc_central_header = mz_zip_get_cdh(pSource_zip, src_file_index))) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_SIG_OFS) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + src_filename_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS); + src_comment_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS); + src_ext_len = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS); + src_central_dir_following_data_size = src_filename_len + src_ext_len + src_comment_len; + + /* TODO: We don't support central dir's >= MZ_UINT32_MAX bytes right now (+32 fudge factor in case we need to add more extra data) */ + if ((pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_central_dir_following_data_size + 32) >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + if (!pState->m_zip64) + { + if (pZip->m_total_files == MZ_UINT16_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + /* TODO: Our zip64 support still has some 32-bit limits that may not be worth fixing. */ + if (pZip->m_total_files == MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + + if (!mz_zip_file_stat_internal(pSource_zip, src_file_index, pSrc_central_header, &src_file_stat, NULL)) + return MZ_FALSE; + + cur_src_file_ofs = src_file_stat.m_local_header_ofs; + cur_dst_file_ofs = pZip->m_archive_size; + + /* Read the source archive's local dir header */ + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + /* Compute the total size we need to copy (filename+extra data+compressed data) */ + local_header_filename_size = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS); + local_header_extra_len = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + local_header_comp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS); + local_header_uncomp_size = MZ_READ_LE32(pLocal_header + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS); + src_archive_bytes_remaining = local_header_filename_size + local_header_extra_len + src_file_stat.m_comp_size; + + /* Try to find a zip64 extended information field */ + if ((local_header_extra_len) && ((local_header_comp_size == MZ_UINT32_MAX) || (local_header_uncomp_size == MZ_UINT32_MAX))) + { + mz_zip_array file_data_array; + const mz_uint8 *pExtra_data; + mz_uint32 extra_size_remaining = local_header_extra_len; + + mz_zip_array_init(&file_data_array, 1); + if (!mz_zip_array_resize(pZip, &file_data_array, local_header_extra_len, MZ_FALSE)) + { + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, src_file_stat.m_local_header_ofs + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + local_header_filename_size, file_data_array.m_p, local_header_extra_len) != local_header_extra_len) + { + mz_zip_array_clear(pZip, &file_data_array); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + pExtra_data = (const mz_uint8 *)file_data_array.m_p; + + do + { + mz_uint32 field_id, field_data_size, field_total_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) + { + mz_zip_array_clear(pZip, &file_data_array); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + field_total_size = field_data_size + sizeof(mz_uint16) * 2; + + if (field_total_size > extra_size_remaining) + { + mz_zip_array_clear(pZip, &file_data_array); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) + { + const mz_uint8 *pSrc_field_data = pExtra_data + sizeof(mz_uint32); + + if (field_data_size < sizeof(mz_uint64) * 2) + { + mz_zip_array_clear(pZip, &file_data_array); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + local_header_uncomp_size = MZ_READ_LE64(pSrc_field_data); + local_header_comp_size = MZ_READ_LE64(pSrc_field_data + sizeof(mz_uint64)); /* may be 0 if there's a descriptor */ + + found_zip64_ext_data_in_ldir = MZ_TRUE; + break; + } + + pExtra_data += field_total_size; + extra_size_remaining -= field_total_size; + } while (extra_size_remaining); + + mz_zip_array_clear(pZip, &file_data_array); + } + + if (!pState->m_zip64) + { + /* Try to detect if the new archive will most likely wind up too big and bail early (+(sizeof(mz_uint32) * 4) is for the optional descriptor which could be present, +64 is a fudge factor). */ + /* We also check when the archive is finalized so this doesn't need to be perfect. */ + mz_uint64 approx_new_archive_size = cur_dst_file_ofs + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + src_archive_bytes_remaining + (sizeof(mz_uint32) * 4) + + pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_central_dir_following_data_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE + 64; + + if (approx_new_archive_size >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + } + + /* Write dest archive padding */ + if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, num_alignment_padding_bytes)) + return MZ_FALSE; + + cur_dst_file_ofs += num_alignment_padding_bytes; + + local_dir_header_ofs = cur_dst_file_ofs; + if (pZip->m_file_offset_alignment) + { + MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); + } + + /* The original zip's local header+ext block doesn't change, even with zip64, so we can just copy it over to the dest zip */ + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + /* Copy over the source archive bytes to the dest archive, also ensure we have enough buf space to handle optional data descriptor */ + if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)MZ_MAX(32U, MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, src_archive_bytes_remaining))))) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + while (src_archive_bytes_remaining) + { + n = (mz_uint)MZ_MIN((mz_uint64)MZ_ZIP_MAX_IO_BUF_SIZE, src_archive_bytes_remaining); + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + cur_src_file_ofs += n; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + cur_dst_file_ofs += n; + + src_archive_bytes_remaining -= n; + } + + /* Now deal with the optional data descriptor */ + bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); + if (bit_flags & 8) + { + /* Copy data descriptor */ + if ((pSource_zip->m_pState->m_zip64) || (found_zip64_ext_data_in_ldir)) + { + /* src is zip64, dest must be zip64 */ + + /* name uint32_t's */ + /* id 1 (optional in zip64?) */ + /* crc 1 */ + /* comp_size 2 */ + /* uncomp_size 2 */ + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, (sizeof(mz_uint32) * 6)) != (sizeof(mz_uint32) * 6)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID) ? 6 : 5); + } + else + { + /* src is NOT zip64 */ + mz_bool has_id; + + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + has_id = (MZ_READ_LE32(pBuf) == MZ_ZIP_DATA_DESCRIPTOR_ID); + + if (pZip->m_pState->m_zip64) + { + /* dest is zip64, so upgrade the data descriptor */ + const mz_uint32 *pSrc_descriptor = (const mz_uint32 *)((const mz_uint8 *)pBuf + (has_id ? sizeof(mz_uint32) : 0)); + const mz_uint32 src_crc32 = pSrc_descriptor[0]; + const mz_uint64 src_comp_size = pSrc_descriptor[1]; + const mz_uint64 src_uncomp_size = pSrc_descriptor[2]; + + mz_write_le32((mz_uint8 *)pBuf, MZ_ZIP_DATA_DESCRIPTOR_ID); + mz_write_le32((mz_uint8 *)pBuf + sizeof(mz_uint32) * 1, src_crc32); + mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 2, src_comp_size); + mz_write_le64((mz_uint8 *)pBuf + sizeof(mz_uint32) * 4, src_uncomp_size); + + n = sizeof(mz_uint32) * 6; + } + else + { + /* dest is NOT zip64, just copy it as-is */ + n = sizeof(mz_uint32) * (has_id ? 4 : 3); + } + } + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + } + + cur_src_file_ofs += n; + cur_dst_file_ofs += n; + } + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + + /* Finally, add the new central dir header */ + orig_central_dir_size = pState->m_central_dir.m_size; + + memcpy(new_central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + + if (pState->m_zip64) + { + /* This is the painful part: We need to write a new central dir header + ext block with updated zip64 fields, and ensure the old fields (if any) are not included. */ + const mz_uint8 *pSrc_ext = pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_filename_len; + mz_zip_array new_ext_block; + + mz_zip_array_init(&new_ext_block, sizeof(mz_uint8)); + + MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, MZ_UINT32_MAX); + MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, MZ_UINT32_MAX); + MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, MZ_UINT32_MAX); + + if (!mz_zip_writer_update_zip64_extension_block(&new_ext_block, pZip, pSrc_ext, src_ext_len, &src_file_stat.m_comp_size, &src_file_stat.m_uncomp_size, &local_dir_header_ofs, NULL)) + { + mz_zip_array_clear(pZip, &new_ext_block); + return MZ_FALSE; + } + + MZ_WRITE_LE16(new_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS, new_ext_block.m_size); + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) + { + mz_zip_array_clear(pZip, &new_ext_block); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, src_filename_len)) + { + mz_zip_array_clear(pZip, &new_ext_block); + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_ext_block.m_p, new_ext_block.m_size)) + { + mz_zip_array_clear(pZip, &new_ext_block); + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + src_filename_len + src_ext_len, src_comment_len)) + { + mz_zip_array_clear(pZip, &new_ext_block); + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + mz_zip_array_clear(pZip, &new_ext_block); + } + else + { + /* sanity checks */ + if (cur_dst_file_ofs > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + if (local_dir_header_ofs >= MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_ARCHIVE_TOO_LARGE); + + MZ_WRITE_LE32(new_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_dir_header_ofs); + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, new_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, src_central_dir_following_data_size)) + { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + } + + /* This shouldn't trigger unless we screwed up during the initial sanity checks */ + if (pState->m_central_dir.m_size >= MZ_UINT32_MAX) + { + /* TODO: Support central dirs >= 32-bits in size */ + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + } + + n = (mz_uint32)orig_central_dir_size; + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1)) + { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + } + + pZip->m_total_files++; + pZip->m_archive_size = cur_dst_file_ofs; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState; + mz_uint64 central_dir_ofs, central_dir_size; + mz_uint8 hdr[256]; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + pState = pZip->m_pState; + + if (pState->m_zip64) + { + if ((pZip->m_total_files > MZ_UINT32_MAX) || (pState->m_central_dir.m_size >= MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + else + { + if ((pZip->m_total_files > MZ_UINT16_MAX) || ((pZip->m_archive_size + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > MZ_UINT32_MAX)) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + } + + central_dir_ofs = 0; + central_dir_size = 0; + if (pZip->m_total_files) + { + /* Write central directory */ + central_dir_ofs = pZip->m_archive_size; + central_dir_size = pState->m_central_dir.m_size; + pZip->m_central_directory_file_ofs = central_dir_ofs; + if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, pState->m_central_dir.m_p, (size_t)central_dir_size) != central_dir_size) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + pZip->m_archive_size += central_dir_size; + } + + if (pState->m_zip64) + { + /* Write zip64 end of central directory header */ + mz_uint64 rel_ofs_to_zip64_ecdr = pZip->m_archive_size; + + MZ_CLEAR_OBJ(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDH_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - sizeof(mz_uint32) - sizeof(mz_uint64)); + MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS, 0x031E); /* TODO: always Unix */ + MZ_WRITE_LE16(hdr + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS, 0x002D); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, pZip->m_total_files); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_SIZE_OFS, central_dir_size); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDH_CDIR_OFS_OFS, central_dir_ofs); + if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE; + + /* Write zip64 end of central directory locator */ + MZ_CLEAR_OBJ(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_SIG_OFS, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG); + MZ_WRITE_LE64(hdr + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS, rel_ofs_to_zip64_ecdr); + MZ_WRITE_LE32(hdr + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS, 1); + if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) != MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + + pZip->m_archive_size += MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE; + } + + /* Write end of central directory record */ + MZ_CLEAR_OBJ(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files)); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, MZ_MIN(MZ_UINT16_MAX, pZip->m_total_files)); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_size)); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, MZ_MIN(MZ_UINT32_MAX, central_dir_ofs)); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_WRITE_FAILED); + +#ifndef MINIZ_NO_STDIO + if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF)) + return mz_zip_set_error(pZip, MZ_ZIP_FILE_CLOSE_FAILED); +#endif /* #ifndef MINIZ_NO_STDIO */ + + pZip->m_archive_size += MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE; + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED; + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize) +{ + if ((!ppBuf) || (!pSize)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + *ppBuf = NULL; + *pSize = 0; + + if ((!pZip) || (!pZip->m_pState)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (pZip->m_pWrite != mz_zip_heap_write_func) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + if (!mz_zip_writer_finalize_archive(pZip)) + return MZ_FALSE; + + *ppBuf = pZip->m_pState->m_pMem; + *pSize = pZip->m_pState->m_mem_size; + pZip->m_pState->m_pMem = NULL; + pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_end(mz_zip_archive *pZip) +{ + return mz_zip_writer_end_internal(pZip, MZ_TRUE); +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) +{ + return mz_zip_add_mem_to_archive_file_in_place_v2(pZip_filename, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, NULL); +} + +mz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr) +{ + mz_bool status, created_new_archive = MZ_FALSE; + mz_zip_archive zip_archive; + struct MZ_FILE_STAT_STRUCT file_stat; + mz_zip_error actual_err = MZ_ZIP_NO_ERROR; + + mz_zip_zero_struct(&zip_archive); + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + + if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || ((comment_size) && (!pComment)) || ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION)) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_PARAMETER; + return MZ_FALSE; + } + + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_FILENAME; + return MZ_FALSE; + } + + /* Important: The regular non-64 bit version of stat() can fail here if the file is very large, which could cause the archive to be overwritten. */ + /* So be sure to compile with _LARGEFILE64_SOURCE 1 */ + if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0) + { + /* Create a new archive. */ + if (!mz_zip_writer_init_file_v2(&zip_archive, pZip_filename, 0, level_and_flags)) + { + if (pErr) + *pErr = zip_archive.m_last_error; + return MZ_FALSE; + } + + created_new_archive = MZ_TRUE; + } + else + { + /* Append to an existing archive. */ + if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, level_and_flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0)) + { + if (pErr) + *pErr = zip_archive.m_last_error; + return MZ_FALSE; + } + + if (!mz_zip_writer_init_from_reader_v2(&zip_archive, pZip_filename, level_and_flags)) + { + if (pErr) + *pErr = zip_archive.m_last_error; + + mz_zip_reader_end_internal(&zip_archive, MZ_FALSE); + + return MZ_FALSE; + } + } + + status = mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, 0, 0); + actual_err = zip_archive.m_last_error; + + /* Always finalize, even if adding failed for some reason, so we have a valid central directory. (This may not always succeed, but we can try.) */ + if (!mz_zip_writer_finalize_archive(&zip_archive)) + { + if (!actual_err) + actual_err = zip_archive.m_last_error; + + status = MZ_FALSE; + } + + if (!mz_zip_writer_end_internal(&zip_archive, status)) + { + if (!actual_err) + actual_err = zip_archive.m_last_error; + + status = MZ_FALSE; + } + + if ((!status) && (created_new_archive)) + { + /* It's a new archive and something went wrong, so just delete it. */ + int ignoredStatus = MZ_DELETE_FILE(pZip_filename); + (void)ignoredStatus; + } + + if (pErr) + *pErr = actual_err; + + return status; +} + +void *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr) +{ + mz_uint32 file_index; + mz_zip_archive zip_archive; + void *p = NULL; + + if (pSize) + *pSize = 0; + + if ((!pZip_filename) || (!pArchive_name)) + { + if (pErr) + *pErr = MZ_ZIP_INVALID_PARAMETER; + + return NULL; + } + + mz_zip_zero_struct(&zip_archive); + if (!mz_zip_reader_init_file_v2(&zip_archive, pZip_filename, flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY, 0, 0)) + { + if (pErr) + *pErr = zip_archive.m_last_error; + + return NULL; + } + + if (mz_zip_reader_locate_file_v2(&zip_archive, pArchive_name, pComment, flags, &file_index)) + { + p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags); + } + + mz_zip_reader_end_internal(&zip_archive, p != NULL); + + if (pErr) + *pErr = zip_archive.m_last_error; + + return p; +} + +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags) +{ + return mz_zip_extract_archive_file_to_heap_v2(pZip_filename, pArchive_name, NULL, pSize, flags, NULL); +} + +#endif /* #ifndef MINIZ_NO_STDIO */ + +#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */ + +/* ------------------- Misc utils */ + +mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip) +{ + return pZip ? pZip->m_zip_mode : MZ_ZIP_MODE_INVALID; +} + +mz_zip_type mz_zip_get_type(mz_zip_archive *pZip) +{ + return pZip ? pZip->m_zip_type : MZ_ZIP_TYPE_INVALID; +} + +mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num) +{ + mz_zip_error prev_err; + + if (!pZip) + return MZ_ZIP_INVALID_PARAMETER; + + prev_err = pZip->m_last_error; + + pZip->m_last_error = err_num; + return prev_err; +} + +mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip) +{ + if (!pZip) + return MZ_ZIP_INVALID_PARAMETER; + + return pZip->m_last_error; +} + +mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip) +{ + return mz_zip_set_last_error(pZip, MZ_ZIP_NO_ERROR); +} + +mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip) +{ + mz_zip_error prev_err; + + if (!pZip) + return MZ_ZIP_INVALID_PARAMETER; + + prev_err = pZip->m_last_error; + + pZip->m_last_error = MZ_ZIP_NO_ERROR; + return prev_err; +} + +const char *mz_zip_get_error_string(mz_zip_error mz_err) +{ + switch (mz_err) + { + case MZ_ZIP_NO_ERROR: + return "no error"; + case MZ_ZIP_UNDEFINED_ERROR: + return "undefined error"; + case MZ_ZIP_TOO_MANY_FILES: + return "too many files"; + case MZ_ZIP_FILE_TOO_LARGE: + return "file too large"; + case MZ_ZIP_UNSUPPORTED_METHOD: + return "unsupported method"; + case MZ_ZIP_UNSUPPORTED_ENCRYPTION: + return "unsupported encryption"; + case MZ_ZIP_UNSUPPORTED_FEATURE: + return "unsupported feature"; + case MZ_ZIP_FAILED_FINDING_CENTRAL_DIR: + return "failed finding central directory"; + case MZ_ZIP_NOT_AN_ARCHIVE: + return "not a ZIP archive"; + case MZ_ZIP_INVALID_HEADER_OR_CORRUPTED: + return "invalid header or archive is corrupted"; + case MZ_ZIP_UNSUPPORTED_MULTIDISK: + return "unsupported multidisk archive"; + case MZ_ZIP_DECOMPRESSION_FAILED: + return "decompression failed or archive is corrupted"; + case MZ_ZIP_COMPRESSION_FAILED: + return "compression failed"; + case MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE: + return "unexpected decompressed size"; + case MZ_ZIP_CRC_CHECK_FAILED: + return "CRC-32 check failed"; + case MZ_ZIP_UNSUPPORTED_CDIR_SIZE: + return "unsupported central directory size"; + case MZ_ZIP_ALLOC_FAILED: + return "allocation failed"; + case MZ_ZIP_FILE_OPEN_FAILED: + return "file open failed"; + case MZ_ZIP_FILE_CREATE_FAILED: + return "file create failed"; + case MZ_ZIP_FILE_WRITE_FAILED: + return "file write failed"; + case MZ_ZIP_FILE_READ_FAILED: + return "file read failed"; + case MZ_ZIP_FILE_CLOSE_FAILED: + return "file close failed"; + case MZ_ZIP_FILE_SEEK_FAILED: + return "file seek failed"; + case MZ_ZIP_FILE_STAT_FAILED: + return "file stat failed"; + case MZ_ZIP_INVALID_PARAMETER: + return "invalid parameter"; + case MZ_ZIP_INVALID_FILENAME: + return "invalid filename"; + case MZ_ZIP_BUF_TOO_SMALL: + return "buffer too small"; + case MZ_ZIP_INTERNAL_ERROR: + return "internal error"; + case MZ_ZIP_FILE_NOT_FOUND: + return "file not found"; + case MZ_ZIP_ARCHIVE_TOO_LARGE: + return "archive is too large"; + case MZ_ZIP_VALIDATION_FAILED: + return "validation failed"; + case MZ_ZIP_WRITE_CALLBACK_FAILED: + return "write calledback failed"; + default: + break; + } + + return "unknown error"; +} + +/* Note: Just because the archive is not zip64 doesn't necessarily mean it doesn't have Zip64 extended information extra field, argh. */ +mz_bool mz_zip_is_zip64(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState)) + return MZ_FALSE; + + return pZip->m_pState->m_zip64; +} + +size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState)) + return 0; + + return pZip->m_pState->m_central_dir.m_size; +} + +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip) +{ + return pZip ? pZip->m_total_files : 0; +} + +mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip) +{ + if (!pZip) + return 0; + return pZip->m_archive_size; +} + +mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState)) + return 0; + return pZip->m_pState->m_file_archive_start_ofs; +} + +MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState)) + return 0; + return pZip->m_pState->m_pFile; +} + +size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pZip->m_pRead)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + + return pZip->m_pRead(pZip->m_pIO_opaque, file_ofs, pBuf, n); +} + +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size) +{ + mz_uint n; + const mz_uint8 *p = mz_zip_get_cdh(pZip, file_index); + if (!p) + { + if (filename_buf_size) + pFilename[0] = '\0'; + mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER); + return 0; + } + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_buf_size) + { + n = MZ_MIN(n, filename_buf_size - 1); + memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pFilename[n] = '\0'; + } + return n + 1; +} + +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat) +{ + return mz_zip_file_stat_internal(pZip, file_index, mz_zip_get_cdh(pZip, file_index), pStat, NULL); +} + +mz_bool mz_zip_end(mz_zip_archive *pZip) +{ + if (!pZip) + return MZ_FALSE; + + if (pZip->m_zip_mode == MZ_ZIP_MODE_READING) + return mz_zip_reader_end(pZip); +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + else if ((pZip->m_zip_mode == MZ_ZIP_MODE_WRITING) || (pZip->m_zip_mode == MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED)) + return mz_zip_writer_end(pZip); +#endif + + return MZ_FALSE; +} + +#ifdef __cplusplus +} +#endif + +#endif /*#ifndef MINIZ_NO_ARCHIVE_APIS*/ diff --git a/luaclib/src/lz/miniz.h b/luaclib/src/lz/miniz.h new file mode 100644 index 00000000..4191145c --- /dev/null +++ b/luaclib/src/lz/miniz.h @@ -0,0 +1,1346 @@ +#define MINIZ_EXPORT +/* miniz.c 2.2.0 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing + See "unlicense" statement at the end of this file. + Rich Geldreich , last updated Oct. 13, 2013 + Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt + + Most API's defined in miniz.c are optional. For example, to disable the archive related functions just define + MINIZ_NO_ARCHIVE_APIS, or to get rid of all stdio usage define MINIZ_NO_STDIO (see the list below for more macros). + + * Low-level Deflate/Inflate implementation notes: + + Compression: Use the "tdefl" API's. The compressor supports raw, static, and dynamic blocks, lazy or + greedy parsing, match length filtering, RLE-only, and Huffman-only streams. It performs and compresses + approximately as well as zlib. + + Decompression: Use the "tinfl" API's. The entire decompressor is implemented as a single function + coroutine: see tinfl_decompress(). It supports decompression into a 32KB (or larger power of 2) wrapping buffer, or into a memory + block large enough to hold the entire file. + + The low-level tdefl/tinfl API's do not make any use of dynamic memory allocation. + + * zlib-style API notes: + + miniz.c implements a fairly large subset of zlib. There's enough functionality present for it to be a drop-in + zlib replacement in many apps: + The z_stream struct, optional memory allocation callbacks + deflateInit/deflateInit2/deflate/deflateReset/deflateEnd/deflateBound + inflateInit/inflateInit2/inflate/inflateReset/inflateEnd + compress, compress2, compressBound, uncompress + CRC-32, Adler-32 - Using modern, minimal code size, CPU cache friendly routines. + Supports raw deflate streams or standard zlib streams with adler-32 checking. + + Limitations: + The callback API's are not implemented yet. No support for gzip headers or zlib static dictionaries. + I've tried to closely emulate zlib's various flavors of stream flushing and return status codes, but + there are no guarantees that miniz.c pulls this off perfectly. + + * PNG writing: See the tdefl_write_image_to_png_file_in_memory() function, originally written by + Alex Evans. Supports 1-4 bytes/pixel images. + + * ZIP archive API notes: + + The ZIP archive API's where designed with simplicity and efficiency in mind, with just enough abstraction to + get the job done with minimal fuss. There are simple API's to retrieve file information, read files from + existing archives, create new archives, append new files to existing archives, or clone archive data from + one archive to another. It supports archives located in memory or the heap, on disk (using stdio.h), + or you can specify custom file read/write callbacks. + + - Archive reading: Just call this function to read a single file from a disk archive: + + void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, + size_t *pSize, mz_uint zip_flags); + + For more complex cases, use the "mz_zip_reader" functions. Upon opening an archive, the entire central + directory is located and read as-is into memory, and subsequent file access only occurs when reading individual files. + + - Archives file scanning: The simple way is to use this function to scan a loaded archive for a specific file: + + int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); + + The locate operation can optionally check file comments too, which (as one example) can be used to identify + multiple versions of the same file in an archive. This function uses a simple linear search through the central + directory, so it's not very fast. + + Alternately, you can iterate through all the files in an archive (using mz_zip_reader_get_num_files()) and + retrieve detailed info on each file by calling mz_zip_reader_file_stat(). + + - Archive creation: Use the "mz_zip_writer" functions. The ZIP writer immediately writes compressed file data + to disk and builds an exact image of the central directory in memory. The central directory image is written + all at once at the end of the archive file when the archive is finalized. + + The archive writer can optionally align each file's local header and file data to any power of 2 alignment, + which can be useful when the archive will be read from optical media. Also, the writer supports placing + arbitrary data blobs at the very beginning of ZIP archives. Archives written using either feature are still + readable by any ZIP tool. + + - Archive appending: The simple way to add a single file to an archive is to call this function: + + mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, + const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); + + The archive will be created if it doesn't already exist, otherwise it'll be appended to. + Note the appending is done in-place and is not an atomic operation, so if something goes wrong + during the operation it's possible the archive could be left without a central directory (although the local + file headers and file data will be fine, so the archive will be recoverable). + + For more complex archive modification scenarios: + 1. The safest way is to use a mz_zip_reader to read the existing archive, cloning only those bits you want to + preserve into a new archive using using the mz_zip_writer_add_from_zip_reader() function (which compiles the + compressed file data as-is). When you're done, delete the old archive and rename the newly written archive, and + you're done. This is safe but requires a bunch of temporary disk space or heap memory. + + 2. Or, you can convert an mz_zip_reader in-place to an mz_zip_writer using mz_zip_writer_init_from_reader(), + append new files as needed, then finalize the archive which will write an updated central directory to the + original archive. (This is basically what mz_zip_add_mem_to_archive_file_in_place() does.) There's a + possibility that the archive's central directory could be lost with this method if anything goes wrong, though. + + - ZIP archive support limitations: + No spanning support. Extraction functions can only handle unencrypted, stored or deflated files. + Requires streams capable of seeking. + + * This is a header file library, like stb_image.c. To get only a header file, either cut and paste the + below header, or create miniz.h, #define MINIZ_HEADER_FILE_ONLY, and then include miniz.c from it. + + * Important: For best perf. be sure to customize the below macros for your target platform: + #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 + #define MINIZ_LITTLE_ENDIAN 1 + #define MINIZ_HAS_64BIT_REGISTERS 1 + + * On platforms using glibc, Be sure to "#define _LARGEFILE64_SOURCE 1" before including miniz.c to ensure miniz + uses the 64-bit variants: fopen64(), stat64(), etc. Otherwise you won't be able to process large files + (i.e. 32-bit stat() fails for me on files > 0x7FFFFFFF bytes). +*/ +#pragma once + + + +/* Defines to completely disable specific portions of miniz.c: + If all macros here are defined the only functionality remaining will be CRC-32, adler-32, tinfl, and tdefl. */ + +/* Define MINIZ_NO_STDIO to disable all usage and any functions which rely on stdio for file I/O. */ +/*#define MINIZ_NO_STDIO */ + +/* If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able to get the current time, or */ +/* get/set file times, and the C run-time funcs that get/set times won't be called. */ +/* The current downside is the times written to your archives will be from 1979. */ +/*#define MINIZ_NO_TIME */ + +/* Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. */ +/*#define MINIZ_NO_ARCHIVE_APIS */ + +/* Define MINIZ_NO_ARCHIVE_WRITING_APIS to disable all writing related ZIP archive API's. */ +/*#define MINIZ_NO_ARCHIVE_WRITING_APIS */ + +/* Define MINIZ_NO_ZLIB_APIS to remove all ZLIB-style compression/decompression API's. */ +/*#define MINIZ_NO_ZLIB_APIS */ + +/* Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent conflicts against stock zlib. */ +/*#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES */ + +/* Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. + Note if MINIZ_NO_MALLOC is defined then the user must always provide custom user alloc/free/realloc + callbacks to the zlib and archive API's, and a few stand-alone helper API's which don't provide custom user + functions (such as tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. */ +/*#define MINIZ_NO_MALLOC */ + +#if defined(__TINYC__) && (defined(__linux) || defined(__linux__)) +/* TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux */ +#define MINIZ_NO_TIME +#endif + +#include + +#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS) +#include +#endif + +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__) +/* MINIZ_X86_OR_X64_CPU is only used to help set the below macros. */ +#define MINIZ_X86_OR_X64_CPU 1 +#else +#define MINIZ_X86_OR_X64_CPU 0 +#endif + +#if (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU +/* Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. */ +#define MINIZ_LITTLE_ENDIAN 1 +#else +#define MINIZ_LITTLE_ENDIAN 0 +#endif + +/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES only if not set */ +#if !defined(MINIZ_USE_UNALIGNED_LOADS_AND_STORES) +#if MINIZ_X86_OR_X64_CPU +/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. */ +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 +#define MINIZ_UNALIGNED_USE_MEMCPY +#else +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0 +#endif +#endif + +#if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__) +/* Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions). */ +#define MINIZ_HAS_64BIT_REGISTERS 1 +#else +#define MINIZ_HAS_64BIT_REGISTERS 0 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* ------------------- zlib-style API Definitions. */ + +/* For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits! */ +typedef unsigned long mz_ulong; + +/* mz_free() internally uses the MZ_FREE() macro (which by default calls free() unless you've modified the MZ_MALLOC macro) to release a block allocated from the heap. */ +MINIZ_EXPORT void mz_free(void *p); + +#define MZ_ADLER32_INIT (1) +/* mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL. */ +MINIZ_EXPORT mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); + +#define MZ_CRC32_INIT (0) +/* mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. */ +MINIZ_EXPORT mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); + +/* Compression strategies. */ +enum +{ + MZ_DEFAULT_STRATEGY = 0, + MZ_FILTERED = 1, + MZ_HUFFMAN_ONLY = 2, + MZ_RLE = 3, + MZ_FIXED = 4 +}; + +/* Method */ +#define MZ_DEFLATED 8 + +/* Heap allocation callbacks. +Note that mz_alloc_func parameter types purposely differ from zlib's: items/size is size_t, not unsigned long. */ +typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size); +typedef void (*mz_free_func)(void *opaque, void *address); +typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size); + +/* Compression levels: 0-9 are the standard zlib-style levels, 10 is best possible compression (not zlib compatible, and may be very slow), MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL. */ +enum +{ + MZ_NO_COMPRESSION = 0, + MZ_BEST_SPEED = 1, + MZ_BEST_COMPRESSION = 9, + MZ_UBER_COMPRESSION = 10, + MZ_DEFAULT_LEVEL = 6, + MZ_DEFAULT_COMPRESSION = -1 +}; + +#define MZ_VERSION "10.2.0" +#define MZ_VERNUM 0xA100 +#define MZ_VER_MAJOR 10 +#define MZ_VER_MINOR 2 +#define MZ_VER_REVISION 0 +#define MZ_VER_SUBREVISION 0 + +#ifndef MINIZ_NO_ZLIB_APIS + +/* Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other values are for advanced use (refer to the zlib docs). */ +enum +{ + MZ_NO_FLUSH = 0, + MZ_PARTIAL_FLUSH = 1, + MZ_SYNC_FLUSH = 2, + MZ_FULL_FLUSH = 3, + MZ_FINISH = 4, + MZ_BLOCK = 5 +}; + +/* Return status codes. MZ_PARAM_ERROR is non-standard. */ +enum +{ + MZ_OK = 0, + MZ_STREAM_END = 1, + MZ_NEED_DICT = 2, + MZ_ERRNO = -1, + MZ_STREAM_ERROR = -2, + MZ_DATA_ERROR = -3, + MZ_MEM_ERROR = -4, + MZ_BUF_ERROR = -5, + MZ_VERSION_ERROR = -6, + MZ_PARAM_ERROR = -10000 +}; + +/* Window bits */ +#define MZ_DEFAULT_WINDOW_BITS 15 + +struct mz_internal_state; + +/* Compression/decompression stream struct. */ +typedef struct mz_stream_s +{ + const unsigned char *next_in; /* pointer to next byte to read */ + unsigned int avail_in; /* number of bytes available at next_in */ + mz_ulong total_in; /* total number of bytes consumed so far */ + + unsigned char *next_out; /* pointer to next byte to write */ + unsigned int avail_out; /* number of bytes that can be written to next_out */ + mz_ulong total_out; /* total number of bytes produced so far */ + + char *msg; /* error msg (unused) */ + struct mz_internal_state *state; /* internal state, allocated by zalloc/zfree */ + + mz_alloc_func zalloc; /* optional heap allocation function (defaults to malloc) */ + mz_free_func zfree; /* optional heap free function (defaults to free) */ + void *opaque; /* heap alloc function user pointer */ + + int data_type; /* data_type (unused) */ + mz_ulong adler; /* adler32 of the source or uncompressed data */ + mz_ulong reserved; /* not used */ +} mz_stream; + +typedef mz_stream *mz_streamp; + +/* Returns the version string of miniz.c. */ +MINIZ_EXPORT const char *mz_version(void); + +/* mz_deflateInit() initializes a compressor with default options: */ +/* Parameters: */ +/* pStream must point to an initialized mz_stream struct. */ +/* level must be between [MZ_NO_COMPRESSION, MZ_BEST_COMPRESSION]. */ +/* level 1 enables a specially optimized compression function that's been optimized purely for performance, not ratio. */ +/* (This special func. is currently only enabled when MINIZ_USE_UNALIGNED_LOADS_AND_STORES and MINIZ_LITTLE_ENDIAN are defined.) */ +/* Return values: */ +/* MZ_OK on success. */ +/* MZ_STREAM_ERROR if the stream is bogus. */ +/* MZ_PARAM_ERROR if the input parameters are bogus. */ +/* MZ_MEM_ERROR on out of memory. */ +MINIZ_EXPORT int mz_deflateInit(mz_streamp pStream, int level); + +/* mz_deflateInit2() is like mz_deflate(), except with more control: */ +/* Additional parameters: */ +/* method must be MZ_DEFLATED */ +/* window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no header or footer) */ +/* mem_level must be between [1, 9] (it's checked but ignored by miniz.c) */ +MINIZ_EXPORT int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy); + +/* Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). */ +MINIZ_EXPORT int mz_deflateReset(mz_streamp pStream); + +/* mz_deflate() compresses the input to output, consuming as much of the input and producing as much output as possible. */ +/* Parameters: */ +/* pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. */ +/* flush may be MZ_NO_FLUSH, MZ_PARTIAL_FLUSH/MZ_SYNC_FLUSH, MZ_FULL_FLUSH, or MZ_FINISH. */ +/* Return values: */ +/* MZ_OK on success (when flushing, or if more input is needed but not available, and/or there's more output to be written but the output buffer is full). */ +/* MZ_STREAM_END if all input has been consumed and all output bytes have been written. Don't call mz_deflate() on the stream anymore. */ +/* MZ_STREAM_ERROR if the stream is bogus. */ +/* MZ_PARAM_ERROR if one of the parameters is invalid. */ +/* MZ_BUF_ERROR if no forward progress is possible because the input and/or output buffers are empty. (Fill up the input buffer or free up some output space and try again.) */ +MINIZ_EXPORT int mz_deflate(mz_streamp pStream, int flush); + +/* mz_deflateEnd() deinitializes a compressor: */ +/* Return values: */ +/* MZ_OK on success. */ +/* MZ_STREAM_ERROR if the stream is bogus. */ +MINIZ_EXPORT int mz_deflateEnd(mz_streamp pStream); + +/* mz_deflateBound() returns a (very) conservative upper bound on the amount of data that could be generated by deflate(), assuming flush is set to only MZ_NO_FLUSH or MZ_FINISH. */ +MINIZ_EXPORT mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); + +/* Single-call compression functions mz_compress() and mz_compress2(): */ +/* Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure. */ +MINIZ_EXPORT int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); +MINIZ_EXPORT int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level); + +/* mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress(). */ +MINIZ_EXPORT mz_ulong mz_compressBound(mz_ulong source_len); + +/* Initializes a decompressor. */ +MINIZ_EXPORT int mz_inflateInit(mz_streamp pStream); + +/* mz_inflateInit2() is like mz_inflateInit() with an additional option that controls the window size and whether or not the stream has been wrapped with a zlib header/footer: */ +/* window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). */ +MINIZ_EXPORT int mz_inflateInit2(mz_streamp pStream, int window_bits); + +/* Quickly resets a compressor without having to reallocate anything. Same as calling mz_inflateEnd() followed by mz_inflateInit()/mz_inflateInit2(). */ +MINIZ_EXPORT int mz_inflateReset(mz_streamp pStream); + +/* Decompresses the input stream to the output, consuming only as much of the input as needed, and writing as much to the output as possible. */ +/* Parameters: */ +/* pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. */ +/* flush may be MZ_NO_FLUSH, MZ_SYNC_FLUSH, or MZ_FINISH. */ +/* On the first call, if flush is MZ_FINISH it's assumed the input and output buffers are both sized large enough to decompress the entire stream in a single call (this is slightly faster). */ +/* MZ_FINISH implies that there are no more source bytes available beside what's already in the input buffer, and that the output buffer is large enough to hold the rest of the decompressed data. */ +/* Return values: */ +/* MZ_OK on success. Either more input is needed but not available, and/or there's more output to be written but the output buffer is full. */ +/* MZ_STREAM_END if all needed input has been consumed and all output bytes have been written. For zlib streams, the adler-32 of the decompressed data has also been verified. */ +/* MZ_STREAM_ERROR if the stream is bogus. */ +/* MZ_DATA_ERROR if the deflate stream is invalid. */ +/* MZ_PARAM_ERROR if one of the parameters is invalid. */ +/* MZ_BUF_ERROR if no forward progress is possible because the input buffer is empty but the inflater needs more input to continue, or if the output buffer is not large enough. Call mz_inflate() again */ +/* with more input data, or with more room in the output buffer (except when using single call decompression, described above). */ +MINIZ_EXPORT int mz_inflate(mz_streamp pStream, int flush); + +/* Deinitializes a decompressor. */ +MINIZ_EXPORT int mz_inflateEnd(mz_streamp pStream); + +/* Single-call decompression. */ +/* Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure. */ +MINIZ_EXPORT int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); +MINIZ_EXPORT int mz_uncompress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong *pSource_len); + +/* Returns a string description of the specified error code, or NULL if the error code is invalid. */ +MINIZ_EXPORT const char *mz_error(int err); + +/* Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used as a drop-in replacement for the subset of zlib that miniz.c supports. */ +/* Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project. */ +#ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES +typedef unsigned char Byte; +typedef unsigned int uInt; +typedef mz_ulong uLong; +typedef Byte Bytef; +typedef uInt uIntf; +typedef char charf; +typedef int intf; +typedef void *voidpf; +typedef uLong uLongf; +typedef void *voidp; +typedef void *const voidpc; +#define Z_NULL 0 +#define Z_NO_FLUSH MZ_NO_FLUSH +#define Z_PARTIAL_FLUSH MZ_PARTIAL_FLUSH +#define Z_SYNC_FLUSH MZ_SYNC_FLUSH +#define Z_FULL_FLUSH MZ_FULL_FLUSH +#define Z_FINISH MZ_FINISH +#define Z_BLOCK MZ_BLOCK +#define Z_OK MZ_OK +#define Z_STREAM_END MZ_STREAM_END +#define Z_NEED_DICT MZ_NEED_DICT +#define Z_ERRNO MZ_ERRNO +#define Z_STREAM_ERROR MZ_STREAM_ERROR +#define Z_DATA_ERROR MZ_DATA_ERROR +#define Z_MEM_ERROR MZ_MEM_ERROR +#define Z_BUF_ERROR MZ_BUF_ERROR +#define Z_VERSION_ERROR MZ_VERSION_ERROR +#define Z_PARAM_ERROR MZ_PARAM_ERROR +#define Z_NO_COMPRESSION MZ_NO_COMPRESSION +#define Z_BEST_SPEED MZ_BEST_SPEED +#define Z_BEST_COMPRESSION MZ_BEST_COMPRESSION +#define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION +#define Z_DEFAULT_STRATEGY MZ_DEFAULT_STRATEGY +#define Z_FILTERED MZ_FILTERED +#define Z_HUFFMAN_ONLY MZ_HUFFMAN_ONLY +#define Z_RLE MZ_RLE +#define Z_FIXED MZ_FIXED +#define Z_DEFLATED MZ_DEFLATED +#define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS +#define alloc_func mz_alloc_func +#define free_func mz_free_func +#define internal_state mz_internal_state +#define z_stream mz_stream +#define deflateInit mz_deflateInit +#define deflateInit2 mz_deflateInit2 +#define deflateReset mz_deflateReset +#define deflate mz_deflate +#define deflateEnd mz_deflateEnd +#define deflateBound mz_deflateBound +#define compress mz_compress +#define compress2 mz_compress2 +#define compressBound mz_compressBound +#define inflateInit mz_inflateInit +#define inflateInit2 mz_inflateInit2 +#define inflateReset mz_inflateReset +#define inflate mz_inflate +#define inflateEnd mz_inflateEnd +#define uncompress mz_uncompress +#define uncompress2 mz_uncompress2 +#define crc32 mz_crc32 +#define adler32 mz_adler32 +#define MAX_WBITS 15 +#define MAX_MEM_LEVEL 9 +#define zError mz_error +#define ZLIB_VERSION MZ_VERSION +#define ZLIB_VERNUM MZ_VERNUM +#define ZLIB_VER_MAJOR MZ_VER_MAJOR +#define ZLIB_VER_MINOR MZ_VER_MINOR +#define ZLIB_VER_REVISION MZ_VER_REVISION +#define ZLIB_VER_SUBREVISION MZ_VER_SUBREVISION +#define zlibVersion mz_version +#define zlib_version mz_version() +#endif /* #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES */ + +#endif /* MINIZ_NO_ZLIB_APIS */ + +#ifdef __cplusplus +} +#endif + + + + + +#pragma once +#include +#include +#include +#include + + + +/* ------------------- Types and macros */ +typedef unsigned char mz_uint8; +typedef signed short mz_int16; +typedef unsigned short mz_uint16; +typedef unsigned int mz_uint32; +typedef unsigned int mz_uint; +typedef int64_t mz_int64; +typedef uint64_t mz_uint64; +typedef int mz_bool; + +#define MZ_FALSE (0) +#define MZ_TRUE (1) + +/* Works around MSVC's spammy "warning C4127: conditional expression is constant" message. */ +#ifdef _MSC_VER +#define MZ_MACRO_END while (0, 0) +#else +#define MZ_MACRO_END while (0) +#endif + +#ifdef MINIZ_NO_STDIO +#define MZ_FILE void * +#else +#include +#define MZ_FILE FILE +#endif /* #ifdef MINIZ_NO_STDIO */ + +#ifdef MINIZ_NO_TIME +typedef struct mz_dummy_time_t_tag +{ + int m_dummy; +} mz_dummy_time_t; +#define MZ_TIME_T mz_dummy_time_t +#else +#define MZ_TIME_T time_t +#endif + +#define MZ_ASSERT(x) assert(x) + +#if !defined(MINIZ_NO_MALLOC) +#define MZ_MALLOC(x) malloc(x) +#define MZ_FREE(x) free(x) +#define MZ_REALLOC(p, x) realloc(p, x) +#endif + +#define MZ_MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define MZ_MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN +#define MZ_READ_LE16(p) *((const mz_uint16 *)(p)) +#define MZ_READ_LE32(p) *((const mz_uint32 *)(p)) +#else +#define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U)) +#define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U)) +#endif + +#define MZ_READ_LE64(p) (((mz_uint64)MZ_READ_LE32(p)) | (((mz_uint64)MZ_READ_LE32((const mz_uint8 *)(p) + sizeof(mz_uint32))) << 32U)) + +#ifdef _MSC_VER +#define MZ_FORCEINLINE __forceinline +#elif defined(__GNUC__) +#define MZ_FORCEINLINE __inline__ __attribute__((__always_inline__)) +#else +#define MZ_FORCEINLINE inline +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +extern MINIZ_EXPORT void *miniz_def_alloc_func(void *opaque, size_t items, size_t size); +extern MINIZ_EXPORT void miniz_def_free_func(void *opaque, void *address); +extern MINIZ_EXPORT void *miniz_def_realloc_func(void *opaque, void *address, size_t items, size_t size); + +#define MZ_UINT16_MAX (0xFFFFU) +#define MZ_UINT32_MAX (0xFFFFFFFFU) + +#ifdef __cplusplus +} +#endif + #pragma once + + +#ifdef __cplusplus +extern "C" { +#endif +/* ------------------- Low-level Compression API Definitions */ + +/* Set TDEFL_LESS_MEMORY to 1 to use less memory (compression will be slightly slower, and raw/dynamic blocks will be output more frequently). */ +#define TDEFL_LESS_MEMORY 0 + +/* tdefl_init() compression flags logically OR'd together (low 12 bits contain the max. number of probes per dictionary search): */ +/* TDEFL_DEFAULT_MAX_PROBES: The compressor defaults to 128 dictionary probes per dictionary search. 0=Huffman only, 1=Huffman+LZ (fastest/crap compression), 4095=Huffman+LZ (slowest/best compression). */ +enum +{ + TDEFL_HUFFMAN_ONLY = 0, + TDEFL_DEFAULT_MAX_PROBES = 128, + TDEFL_MAX_PROBES_MASK = 0xFFF +}; + +/* TDEFL_WRITE_ZLIB_HEADER: If set, the compressor outputs a zlib header before the deflate data, and the Adler-32 of the source data at the end. Otherwise, you'll get raw deflate data. */ +/* TDEFL_COMPUTE_ADLER32: Always compute the adler-32 of the input data (even when not writing zlib headers). */ +/* TDEFL_GREEDY_PARSING_FLAG: Set to use faster greedy parsing, instead of more efficient lazy parsing. */ +/* TDEFL_NONDETERMINISTIC_PARSING_FLAG: Enable to decrease the compressor's initialization time to the minimum, but the output may vary from run to run given the same input (depending on the contents of memory). */ +/* TDEFL_RLE_MATCHES: Only look for RLE matches (matches with a distance of 1) */ +/* TDEFL_FILTER_MATCHES: Discards matches <= 5 chars if enabled. */ +/* TDEFL_FORCE_ALL_STATIC_BLOCKS: Disable usage of optimized Huffman tables. */ +/* TDEFL_FORCE_ALL_RAW_BLOCKS: Only use raw (uncompressed) deflate blocks. */ +/* The low 12 bits are reserved to control the max # of hash probes per dictionary lookup (see TDEFL_MAX_PROBES_MASK). */ +enum +{ + TDEFL_WRITE_ZLIB_HEADER = 0x01000, + TDEFL_COMPUTE_ADLER32 = 0x02000, + TDEFL_GREEDY_PARSING_FLAG = 0x04000, + TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000, + TDEFL_RLE_MATCHES = 0x10000, + TDEFL_FILTER_MATCHES = 0x20000, + TDEFL_FORCE_ALL_STATIC_BLOCKS = 0x40000, + TDEFL_FORCE_ALL_RAW_BLOCKS = 0x80000 +}; + +/* High level compression functions: */ +/* tdefl_compress_mem_to_heap() compresses a block in memory to a heap block allocated via malloc(). */ +/* On entry: */ +/* pSrc_buf, src_buf_len: Pointer and size of source block to compress. */ +/* flags: The max match finder probes (default is 128) logically OR'd against the above flags. Higher probes are slower but improve compression. */ +/* On return: */ +/* Function returns a pointer to the compressed data, or NULL on failure. */ +/* *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on uncompressible data. */ +/* The caller must free() the returned block when it's no longer needed. */ +MINIZ_EXPORT void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); + +/* tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory. */ +/* Returns 0 on failure. */ +MINIZ_EXPORT size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); + +/* Compresses an image to a compressed PNG file in memory. */ +/* On entry: */ +/* pImage, w, h, and num_chans describe the image to compress. num_chans may be 1, 2, 3, or 4. */ +/* The image pitch in bytes per scanline will be w*num_chans. The leftmost pixel on the top scanline is stored first in memory. */ +/* level may range from [0,10], use MZ_NO_COMPRESSION, MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc. or a decent default is MZ_DEFAULT_LEVEL */ +/* If flip is true, the image will be flipped on the Y axis (useful for OpenGL apps). */ +/* On return: */ +/* Function returns a pointer to the compressed data, or NULL on failure. */ +/* *pLen_out will be set to the size of the PNG image file. */ +/* The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed. */ +MINIZ_EXPORT void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip); +MINIZ_EXPORT void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out); + +/* Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. */ +typedef mz_bool (*tdefl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); + +/* tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally. */ +MINIZ_EXPORT mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +enum +{ + TDEFL_MAX_HUFF_TABLES = 3, + TDEFL_MAX_HUFF_SYMBOLS_0 = 288, + TDEFL_MAX_HUFF_SYMBOLS_1 = 32, + TDEFL_MAX_HUFF_SYMBOLS_2 = 19, + TDEFL_LZ_DICT_SIZE = 32768, + TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1, + TDEFL_MIN_MATCH_LEN = 3, + TDEFL_MAX_MATCH_LEN = 258 +}; + +/* TDEFL_OUT_BUF_SIZE MUST be large enough to hold a single entire compressed output block (using static/fixed Huffman codes). */ +#if TDEFL_LESS_MEMORY +enum +{ + TDEFL_LZ_CODE_BUF_SIZE = 24 * 1024, + TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10, + TDEFL_MAX_HUFF_SYMBOLS = 288, + TDEFL_LZ_HASH_BITS = 12, + TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, + TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, + TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS +}; +#else +enum +{ + TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024, + TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13) / 10, + TDEFL_MAX_HUFF_SYMBOLS = 288, + TDEFL_LZ_HASH_BITS = 15, + TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, + TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, + TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS +}; +#endif + +/* The low-level tdefl functions below may be used directly if the above helper functions aren't flexible enough. The low-level functions don't make any heap allocations, unlike the above helper functions. */ +typedef enum { + TDEFL_STATUS_BAD_PARAM = -2, + TDEFL_STATUS_PUT_BUF_FAILED = -1, + TDEFL_STATUS_OKAY = 0, + TDEFL_STATUS_DONE = 1 +} tdefl_status; + +/* Must map to MZ_NO_FLUSH, MZ_SYNC_FLUSH, etc. enums */ +typedef enum { + TDEFL_NO_FLUSH = 0, + TDEFL_SYNC_FLUSH = 2, + TDEFL_FULL_FLUSH = 3, + TDEFL_FINISH = 4 +} tdefl_flush; + +/* tdefl's compression state structure. */ +typedef struct +{ + tdefl_put_buf_func_ptr m_pPut_buf_func; + void *m_pPut_buf_user; + mz_uint m_flags, m_max_probes[2]; + int m_greedy_parsing; + mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size; + mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end; + mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, m_bit_buffer; + mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, m_output_flush_ofs, m_output_flush_remaining, m_finished, m_block_index, m_wants_to_finish; + tdefl_status m_prev_return_status; + const void *m_pIn_buf; + void *m_pOut_buf; + size_t *m_pIn_buf_size, *m_pOut_buf_size; + tdefl_flush m_flush; + const mz_uint8 *m_pSrc; + size_t m_src_buf_left, m_out_buf_ofs; + mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1]; + mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE]; + mz_uint16 m_next[TDEFL_LZ_DICT_SIZE]; + mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE]; + mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE]; +} tdefl_compressor; + +/* Initializes the compressor. */ +/* There is no corresponding deinit() function because the tdefl API's do not dynamically allocate memory. */ +/* pBut_buf_func: If NULL, output data will be supplied to the specified callback. In this case, the user should call the tdefl_compress_buffer() API for compression. */ +/* If pBut_buf_func is NULL the user should always call the tdefl_compress() API. */ +/* flags: See the above enums (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) */ +MINIZ_EXPORT tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +/* Compresses a block of data, consuming as much of the specified input buffer as possible, and writing as much compressed data to the specified output buffer as possible. */ +MINIZ_EXPORT tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush); + +/* tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr. */ +/* tdefl_compress_buffer() always consumes the entire input buffer. */ +MINIZ_EXPORT tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush); + +MINIZ_EXPORT tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); +MINIZ_EXPORT mz_uint32 tdefl_get_adler32(tdefl_compressor *d); + +/* Create tdefl_compress() flags given zlib-style compression parameters. */ +/* level may range from [0,10] (where 10 is absolute max compression, but may be much slower on some files) */ +/* window_bits may be -15 (raw deflate) or 15 (zlib) */ +/* strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, MZ_RLE, or MZ_FIXED */ +MINIZ_EXPORT mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy); + +#ifndef MINIZ_NO_MALLOC +/* Allocate the tdefl_compressor structure in C so that */ +/* non-C language bindings to tdefl_ API don't need to worry about */ +/* structure size and allocation mechanism. */ +MINIZ_EXPORT tdefl_compressor *tdefl_compressor_alloc(void); +MINIZ_EXPORT void tdefl_compressor_free(tdefl_compressor *pComp); +#endif + +#ifdef __cplusplus +} +#endif + #pragma once + +/* ------------------- Low-level Decompression API Definitions */ + +#ifdef __cplusplus +extern "C" { +#endif +/* Decompression flags used by tinfl_decompress(). */ +/* TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream. */ +/* TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input. */ +/* TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB). */ +/* TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes. */ +enum +{ + TINFL_FLAG_PARSE_ZLIB_HEADER = 1, + TINFL_FLAG_HAS_MORE_INPUT = 2, + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4, + TINFL_FLAG_COMPUTE_ADLER32 = 8 +}; + +/* High level decompression functions: */ +/* tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block allocated via malloc(). */ +/* On entry: */ +/* pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data to decompress. */ +/* On return: */ +/* Function returns a pointer to the decompressed data, or NULL on failure. */ +/* *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. */ +/* The caller must call mz_free() on the returned block when it's no longer needed. */ +MINIZ_EXPORT void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); + +/* tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. */ +/* Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. */ +#define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) +MINIZ_EXPORT size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); + +/* tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. */ +/* Returns 1 on success or 0 on failure. */ +typedef int (*tinfl_put_buf_func_ptr)(const void *pBuf, int len, void *pUser); +MINIZ_EXPORT int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +struct tinfl_decompressor_tag; +typedef struct tinfl_decompressor_tag tinfl_decompressor; + +#ifndef MINIZ_NO_MALLOC +/* Allocate the tinfl_decompressor structure in C so that */ +/* non-C language bindings to tinfl_ API don't need to worry about */ +/* structure size and allocation mechanism. */ +MINIZ_EXPORT tinfl_decompressor *tinfl_decompressor_alloc(void); +MINIZ_EXPORT void tinfl_decompressor_free(tinfl_decompressor *pDecomp); +#endif + +/* Max size of LZ dictionary. */ +#define TINFL_LZ_DICT_SIZE 32768 + +/* Return status. */ +typedef enum { + /* This flags indicates the inflator needs 1 or more input bytes to make forward progress, but the caller is indicating that no more are available. The compressed data */ + /* is probably corrupted. If you call the inflator again with more bytes it'll try to continue processing the input but this is a BAD sign (either the data is corrupted or you called it incorrectly). */ + /* If you call it again with no input you'll just get TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS again. */ + TINFL_STATUS_FAILED_CANNOT_MAKE_PROGRESS = -4, + + /* This flag indicates that one or more of the input parameters was obviously bogus. (You can try calling it again, but if you get this error the calling code is wrong.) */ + TINFL_STATUS_BAD_PARAM = -3, + + /* This flags indicate the inflator is finished but the adler32 check of the uncompressed data didn't match. If you call it again it'll return TINFL_STATUS_DONE. */ + TINFL_STATUS_ADLER32_MISMATCH = -2, + + /* This flags indicate the inflator has somehow failed (bad code, corrupted input, etc.). If you call it again without resetting via tinfl_init() it it'll just keep on returning the same status failure code. */ + TINFL_STATUS_FAILED = -1, + + /* Any status code less than TINFL_STATUS_DONE must indicate a failure. */ + + /* This flag indicates the inflator has returned every byte of uncompressed data that it can, has consumed every byte that it needed, has successfully reached the end of the deflate stream, and */ + /* if zlib headers and adler32 checking enabled that it has successfully checked the uncompressed data's adler32. If you call it again you'll just get TINFL_STATUS_DONE over and over again. */ + TINFL_STATUS_DONE = 0, + + /* This flag indicates the inflator MUST have more input data (even 1 byte) before it can make any more forward progress, or you need to clear the TINFL_FLAG_HAS_MORE_INPUT */ + /* flag on the next call if you don't have any more source data. If the source data was somehow corrupted it's also possible (but unlikely) for the inflator to keep on demanding input to */ + /* proceed, so be sure to properly set the TINFL_FLAG_HAS_MORE_INPUT flag. */ + TINFL_STATUS_NEEDS_MORE_INPUT = 1, + + /* This flag indicates the inflator definitely has 1 or more bytes of uncompressed data available, but it cannot write this data into the output buffer. */ + /* Note if the source compressed data was corrupted it's possible for the inflator to return a lot of uncompressed data to the caller. I've been assuming you know how much uncompressed data to expect */ + /* (either exact or worst case) and will stop calling the inflator and fail after receiving too much. In pure streaming scenarios where you have no idea how many bytes to expect this may not be possible */ + /* so I may need to add some code to address this. */ + TINFL_STATUS_HAS_MORE_OUTPUT = 2 +} tinfl_status; + +/* Initializes the decompressor to its initial state. */ +#define tinfl_init(r) \ + do \ + { \ + (r)->m_state = 0; \ + } \ + MZ_MACRO_END +#define tinfl_get_adler32(r) (r)->m_check_adler32 + +/* Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. */ +/* This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. */ +MINIZ_EXPORT tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags); + +/* Internal/private bits follow. */ +enum +{ + TINFL_MAX_HUFF_TABLES = 3, + TINFL_MAX_HUFF_SYMBOLS_0 = 288, + TINFL_MAX_HUFF_SYMBOLS_1 = 32, + TINFL_MAX_HUFF_SYMBOLS_2 = 19, + TINFL_FAST_LOOKUP_BITS = 10, + TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS +}; + +typedef struct +{ + mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0]; + mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; +} tinfl_huff_table; + +#if MINIZ_HAS_64BIT_REGISTERS +#define TINFL_USE_64BIT_BITBUF 1 +#else +#define TINFL_USE_64BIT_BITBUF 0 +#endif + +#if TINFL_USE_64BIT_BITBUF +typedef mz_uint64 tinfl_bit_buf_t; +#define TINFL_BITBUF_SIZE (64) +#else +typedef mz_uint32 tinfl_bit_buf_t; +#define TINFL_BITBUF_SIZE (32) +#endif + +struct tinfl_decompressor_tag +{ + mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES]; + tinfl_bit_buf_t m_bit_buf; + size_t m_dist_from_out_buf_start; + tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES]; + mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; +}; + +#ifdef __cplusplus +} +#endif + +#pragma once + + +/* ------------------- ZIP archive reading/writing */ + +#ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef __cplusplus +extern "C" { +#endif + +enum +{ + /* Note: These enums can be reduced as needed to save memory or stack space - they are pretty conservative. */ + MZ_ZIP_MAX_IO_BUF_SIZE = 64 * 1024, + MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 512, + MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 512 +}; + +typedef struct +{ + /* Central directory file index. */ + mz_uint32 m_file_index; + + /* Byte offset of this entry in the archive's central directory. Note we currently only support up to UINT_MAX or less bytes in the central dir. */ + mz_uint64 m_central_dir_ofs; + + /* These fields are copied directly from the zip's central dir. */ + mz_uint16 m_version_made_by; + mz_uint16 m_version_needed; + mz_uint16 m_bit_flag; + mz_uint16 m_method; + +#ifndef MINIZ_NO_TIME + MZ_TIME_T m_time; +#endif + + /* CRC-32 of uncompressed data. */ + mz_uint32 m_crc32; + + /* File's compressed size. */ + mz_uint64 m_comp_size; + + /* File's uncompressed size. Note, I've seen some old archives where directory entries had 512 bytes for their uncompressed sizes, but when you try to unpack them you actually get 0 bytes. */ + mz_uint64 m_uncomp_size; + + /* Zip internal and external file attributes. */ + mz_uint16 m_internal_attr; + mz_uint32 m_external_attr; + + /* Entry's local header file offset in bytes. */ + mz_uint64 m_local_header_ofs; + + /* Size of comment in bytes. */ + mz_uint32 m_comment_size; + + /* MZ_TRUE if the entry appears to be a directory. */ + mz_bool m_is_directory; + + /* MZ_TRUE if the entry uses encryption/strong encryption (which miniz_zip doesn't support) */ + mz_bool m_is_encrypted; + + /* MZ_TRUE if the file is not encrypted, a patch file, and if it uses a compression method we support. */ + mz_bool m_is_supported; + + /* Filename. If string ends in '/' it's a subdirectory entry. */ + /* Guaranteed to be zero terminated, may be truncated to fit. */ + char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; + + /* Comment field. */ + /* Guaranteed to be zero terminated, may be truncated to fit. */ + char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE]; + +} mz_zip_archive_file_stat; + +typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n); +typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n); +typedef mz_bool (*mz_file_needs_keepalive)(void *pOpaque); + +struct mz_zip_internal_state_tag; +typedef struct mz_zip_internal_state_tag mz_zip_internal_state; + +typedef enum { + MZ_ZIP_MODE_INVALID = 0, + MZ_ZIP_MODE_READING = 1, + MZ_ZIP_MODE_WRITING = 2, + MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 +} mz_zip_mode; + +typedef enum { + MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100, + MZ_ZIP_FLAG_IGNORE_PATH = 0x0200, + MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400, + MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800, + MZ_ZIP_FLAG_VALIDATE_LOCATE_FILE_FLAG = 0x1000, /* if enabled, mz_zip_reader_locate_file() will be called on each file as its validated to ensure the func finds the file in the central dir (intended for testing) */ + MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY = 0x2000, /* validate the local headers, but don't decompress the entire file and check the crc32 */ + MZ_ZIP_FLAG_WRITE_ZIP64 = 0x4000, /* always use the zip64 file format, instead of the original zip file format with automatic switch to zip64. Use as flags parameter with mz_zip_writer_init*_v2 */ + MZ_ZIP_FLAG_WRITE_ALLOW_READING = 0x8000, + MZ_ZIP_FLAG_ASCII_FILENAME = 0x10000, + /*After adding a compressed file, seek back + to local file header and set the correct sizes*/ + MZ_ZIP_FLAG_WRITE_HEADER_SET_SIZE = 0x20000 +} mz_zip_flags; + +typedef enum { + MZ_ZIP_TYPE_INVALID = 0, + MZ_ZIP_TYPE_USER, + MZ_ZIP_TYPE_MEMORY, + MZ_ZIP_TYPE_HEAP, + MZ_ZIP_TYPE_FILE, + MZ_ZIP_TYPE_CFILE, + MZ_ZIP_TOTAL_TYPES +} mz_zip_type; + +/* miniz error codes. Be sure to update mz_zip_get_error_string() if you add or modify this enum. */ +typedef enum { + MZ_ZIP_NO_ERROR = 0, + MZ_ZIP_UNDEFINED_ERROR, + MZ_ZIP_TOO_MANY_FILES, + MZ_ZIP_FILE_TOO_LARGE, + MZ_ZIP_UNSUPPORTED_METHOD, + MZ_ZIP_UNSUPPORTED_ENCRYPTION, + MZ_ZIP_UNSUPPORTED_FEATURE, + MZ_ZIP_FAILED_FINDING_CENTRAL_DIR, + MZ_ZIP_NOT_AN_ARCHIVE, + MZ_ZIP_INVALID_HEADER_OR_CORRUPTED, + MZ_ZIP_UNSUPPORTED_MULTIDISK, + MZ_ZIP_DECOMPRESSION_FAILED, + MZ_ZIP_COMPRESSION_FAILED, + MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE, + MZ_ZIP_CRC_CHECK_FAILED, + MZ_ZIP_UNSUPPORTED_CDIR_SIZE, + MZ_ZIP_ALLOC_FAILED, + MZ_ZIP_FILE_OPEN_FAILED, + MZ_ZIP_FILE_CREATE_FAILED, + MZ_ZIP_FILE_WRITE_FAILED, + MZ_ZIP_FILE_READ_FAILED, + MZ_ZIP_FILE_CLOSE_FAILED, + MZ_ZIP_FILE_SEEK_FAILED, + MZ_ZIP_FILE_STAT_FAILED, + MZ_ZIP_INVALID_PARAMETER, + MZ_ZIP_INVALID_FILENAME, + MZ_ZIP_BUF_TOO_SMALL, + MZ_ZIP_INTERNAL_ERROR, + MZ_ZIP_FILE_NOT_FOUND, + MZ_ZIP_ARCHIVE_TOO_LARGE, + MZ_ZIP_VALIDATION_FAILED, + MZ_ZIP_WRITE_CALLBACK_FAILED, + MZ_ZIP_TOTAL_ERRORS +} mz_zip_error; + +typedef struct +{ + mz_uint64 m_archive_size; + mz_uint64 m_central_directory_file_ofs; + + /* We only support up to UINT32_MAX files in zip64 mode. */ + mz_uint32 m_total_files; + mz_zip_mode m_zip_mode; + mz_zip_type m_zip_type; + mz_zip_error m_last_error; + + mz_uint64 m_file_offset_alignment; + + mz_alloc_func m_pAlloc; + mz_free_func m_pFree; + mz_realloc_func m_pRealloc; + void *m_pAlloc_opaque; + + mz_file_read_func m_pRead; + mz_file_write_func m_pWrite; + mz_file_needs_keepalive m_pNeeds_keepalive; + void *m_pIO_opaque; + + mz_zip_internal_state *m_pState; + +} mz_zip_archive; + +typedef struct +{ + mz_zip_archive *pZip; + mz_uint flags; + + int status; +#ifndef MINIZ_DISABLE_ZIP_READER_CRC32_CHECKS + mz_uint file_crc32; +#endif + mz_uint64 read_buf_size, read_buf_ofs, read_buf_avail, comp_remaining, out_buf_ofs, cur_file_ofs; + mz_zip_archive_file_stat file_stat; + void *pRead_buf; + void *pWrite_buf; + + size_t out_blk_remain; + + tinfl_decompressor inflator; + +} mz_zip_reader_extract_iter_state; + +/* -------- ZIP reading */ + +/* Inits a ZIP archive reader. */ +/* These functions read and validate the archive's central directory. */ +MINIZ_EXPORT mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint flags); + +MINIZ_EXPORT mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint flags); + +#ifndef MINIZ_NO_STDIO +/* Read a archive from a disk file. */ +/* file_start_ofs is the file offset where the archive actually begins, or 0. */ +/* actual_archive_size is the true total size of the archive, which may be smaller than the file's actual size on disk. If zero the entire file is treated as the archive. */ +MINIZ_EXPORT mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags); +MINIZ_EXPORT mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags, mz_uint64 file_start_ofs, mz_uint64 archive_size); + +/* Read an archive from an already opened FILE, beginning at the current file position. */ +/* The archive is assumed to be archive_size bytes long. If archive_size is 0, then the entire rest of the file is assumed to contain the archive. */ +/* The FILE will NOT be closed when mz_zip_reader_end() is called. */ +MINIZ_EXPORT mz_bool mz_zip_reader_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint64 archive_size, mz_uint flags); +#endif + +/* Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used. */ +MINIZ_EXPORT mz_bool mz_zip_reader_end(mz_zip_archive *pZip); + +/* -------- ZIP reading or writing */ + +/* Clears a mz_zip_archive struct to all zeros. */ +/* Important: This must be done before passing the struct to any mz_zip functions. */ +MINIZ_EXPORT void mz_zip_zero_struct(mz_zip_archive *pZip); + +MINIZ_EXPORT mz_zip_mode mz_zip_get_mode(mz_zip_archive *pZip); +MINIZ_EXPORT mz_zip_type mz_zip_get_type(mz_zip_archive *pZip); + +/* Returns the total number of files in the archive. */ +MINIZ_EXPORT mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip); + +MINIZ_EXPORT mz_uint64 mz_zip_get_archive_size(mz_zip_archive *pZip); +MINIZ_EXPORT mz_uint64 mz_zip_get_archive_file_start_offset(mz_zip_archive *pZip); +MINIZ_EXPORT MZ_FILE *mz_zip_get_cfile(mz_zip_archive *pZip); + +/* Reads n bytes of raw archive data, starting at file offset file_ofs, to pBuf. */ +MINIZ_EXPORT size_t mz_zip_read_archive_data(mz_zip_archive *pZip, mz_uint64 file_ofs, void *pBuf, size_t n); + +/* All mz_zip funcs set the m_last_error field in the mz_zip_archive struct. These functions retrieve/manipulate this field. */ +/* Note that the m_last_error functionality is not thread safe. */ +MINIZ_EXPORT mz_zip_error mz_zip_set_last_error(mz_zip_archive *pZip, mz_zip_error err_num); +MINIZ_EXPORT mz_zip_error mz_zip_peek_last_error(mz_zip_archive *pZip); +MINIZ_EXPORT mz_zip_error mz_zip_clear_last_error(mz_zip_archive *pZip); +MINIZ_EXPORT mz_zip_error mz_zip_get_last_error(mz_zip_archive *pZip); +MINIZ_EXPORT const char *mz_zip_get_error_string(mz_zip_error mz_err); + +/* MZ_TRUE if the archive file entry is a directory entry. */ +MINIZ_EXPORT mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index); + +/* MZ_TRUE if the file is encrypted/strong encrypted. */ +MINIZ_EXPORT mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index); + +/* MZ_TRUE if the compression method is supported, and the file is not encrypted, and the file is not a compressed patch file. */ +MINIZ_EXPORT mz_bool mz_zip_reader_is_file_supported(mz_zip_archive *pZip, mz_uint file_index); + +/* Retrieves the filename of an archive file entry. */ +/* Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename. */ +MINIZ_EXPORT mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size); + +/* Attempts to locates a file in the archive's central directory. */ +/* Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH */ +/* Returns -1 if the file cannot be found. */ +MINIZ_EXPORT int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *file_index); + +/* Returns detailed information about an archive file entry. */ +MINIZ_EXPORT mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat); + +/* MZ_TRUE if the file is in zip64 format. */ +/* A file is considered zip64 if it contained a zip64 end of central directory marker, or if it contained any zip64 extended file information fields in the central directory. */ +MINIZ_EXPORT mz_bool mz_zip_is_zip64(mz_zip_archive *pZip); + +/* Returns the total central directory size in bytes. */ +/* The current max supported size is <= MZ_UINT32_MAX. */ +MINIZ_EXPORT size_t mz_zip_get_central_dir_size(mz_zip_archive *pZip); + +/* Extracts a archive file to a memory buffer using no memory allocation. */ +/* There must be at least enough room on the stack to store the inflator's state (~34KB or so). */ +MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); +MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); + +/* Extracts a archive file to a memory buffer. */ +MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags); + +/* Extracts a archive file to a dynamically allocated heap buffer. */ +/* The memory will be allocated via the mz_zip_archive's alloc/realloc functions. */ +/* Returns NULL and sets the last error on failure. */ +MINIZ_EXPORT void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags); +MINIZ_EXPORT void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags); + +/* Extracts a archive file using a callback function to output the file's data. */ +MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); + +/* Extract a file iteratively */ +MINIZ_EXPORT mz_zip_reader_extract_iter_state* mz_zip_reader_extract_iter_new(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); +MINIZ_EXPORT mz_zip_reader_extract_iter_state* mz_zip_reader_extract_file_iter_new(mz_zip_archive *pZip, const char *pFilename, mz_uint flags); +MINIZ_EXPORT size_t mz_zip_reader_extract_iter_read(mz_zip_reader_extract_iter_state* pState, void* pvBuf, size_t buf_size); +MINIZ_EXPORT mz_bool mz_zip_reader_extract_iter_free(mz_zip_reader_extract_iter_state* pState); + +#ifndef MINIZ_NO_STDIO +/* Extracts a archive file to a disk file and sets its last accessed and modified times. */ +/* This function only extracts files, not archive directory records. */ +MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags); + +/* Extracts a archive file starting at the current position in the destination FILE stream. */ +MINIZ_EXPORT mz_bool mz_zip_reader_extract_to_cfile(mz_zip_archive *pZip, mz_uint file_index, MZ_FILE *File, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_reader_extract_file_to_cfile(mz_zip_archive *pZip, const char *pArchive_filename, MZ_FILE *pFile, mz_uint flags); +#endif + +#if 0 +/* TODO */ + typedef void *mz_zip_streaming_extract_state_ptr; + mz_zip_streaming_extract_state_ptr mz_zip_streaming_extract_begin(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); + uint64_t mz_zip_streaming_extract_get_size(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); + uint64_t mz_zip_streaming_extract_get_cur_ofs(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); + mz_bool mz_zip_streaming_extract_seek(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, uint64_t new_ofs); + size_t mz_zip_streaming_extract_read(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState, void *pBuf, size_t buf_size); + mz_bool mz_zip_streaming_extract_end(mz_zip_archive *pZip, mz_zip_streaming_extract_state_ptr pState); +#endif + +/* This function compares the archive's local headers, the optional local zip64 extended information block, and the optional descriptor following the compressed data vs. the data in the central directory. */ +/* It also validates that each file can be successfully uncompressed unless the MZ_ZIP_FLAG_VALIDATE_HEADERS_ONLY is specified. */ +MINIZ_EXPORT mz_bool mz_zip_validate_file(mz_zip_archive *pZip, mz_uint file_index, mz_uint flags); + +/* Validates an entire archive by calling mz_zip_validate_file() on each file. */ +MINIZ_EXPORT mz_bool mz_zip_validate_archive(mz_zip_archive *pZip, mz_uint flags); + +/* Misc utils/helpers, valid for ZIP reading or writing */ +MINIZ_EXPORT mz_bool mz_zip_validate_mem_archive(const void *pMem, size_t size, mz_uint flags, mz_zip_error *pErr); +MINIZ_EXPORT mz_bool mz_zip_validate_file_archive(const char *pFilename, mz_uint flags, mz_zip_error *pErr); + +/* Universal end function - calls either mz_zip_reader_end() or mz_zip_writer_end(). */ +MINIZ_EXPORT mz_bool mz_zip_end(mz_zip_archive *pZip); + +/* -------- ZIP writing */ + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +/* Inits a ZIP archive writer. */ +/*Set pZip->m_pWrite (and pZip->m_pIO_opaque) before calling mz_zip_writer_init or mz_zip_writer_init_v2*/ +/*The output is streamable, i.e. file_ofs in mz_file_write_func always increases only by n*/ +MINIZ_EXPORT mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size); +MINIZ_EXPORT mz_bool mz_zip_writer_init_v2(mz_zip_archive *pZip, mz_uint64 existing_size, mz_uint flags); + +MINIZ_EXPORT mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size); +MINIZ_EXPORT mz_bool mz_zip_writer_init_heap_v2(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size, mz_uint flags); + +#ifndef MINIZ_NO_STDIO +MINIZ_EXPORT mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning); +MINIZ_EXPORT mz_bool mz_zip_writer_init_file_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning, mz_uint flags); +MINIZ_EXPORT mz_bool mz_zip_writer_init_cfile(mz_zip_archive *pZip, MZ_FILE *pFile, mz_uint flags); +#endif + +/* Converts a ZIP archive reader object into a writer object, to allow efficient in-place file appends to occur on an existing archive. */ +/* For archives opened using mz_zip_reader_init_file, pFilename must be the archive's filename so it can be reopened for writing. If the file can't be reopened, mz_zip_reader_end() will be called. */ +/* For archives opened using mz_zip_reader_init_mem, the memory block must be growable using the realloc callback (which defaults to realloc unless you've overridden it). */ +/* Finally, for archives opened using mz_zip_reader_init, the mz_zip_archive's user provided m_pWrite function cannot be NULL. */ +/* Note: In-place archive modification is not recommended unless you know what you're doing, because if execution stops or something goes wrong before */ +/* the archive is finalized the file's central directory will be hosed. */ +MINIZ_EXPORT mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename); +MINIZ_EXPORT mz_bool mz_zip_writer_init_from_reader_v2(mz_zip_archive *pZip, const char *pFilename, mz_uint flags); + +/* Adds the contents of a memory buffer to an archive. These functions record the current local time into the archive. */ +/* To add a directory entry, call this method with an archive name ending in a forwardslash with an empty buffer. */ +/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ +MINIZ_EXPORT mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags); + +/* Like mz_zip_writer_add_mem(), except you can specify a file comment field, and optionally supply the function with already compressed data. */ +/* uncomp_size/uncomp_crc32 are only used if the MZ_ZIP_FLAG_COMPRESSED_DATA flag is specified. */ +MINIZ_EXPORT mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, + mz_uint64 uncomp_size, mz_uint32 uncomp_crc32); + +MINIZ_EXPORT mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, + mz_uint64 uncomp_size, mz_uint32 uncomp_crc32, MZ_TIME_T *last_modified, const char *user_extra_data_local, mz_uint user_extra_data_local_len, + const char *user_extra_data_central, mz_uint user_extra_data_central_len); + +/* Adds the contents of a file to an archive. This function also records the disk file's modified time into the archive. */ +/* File data is supplied via a read callback function. User mz_zip_writer_add_(c)file to add a file directly.*/ +MINIZ_EXPORT mz_bool mz_zip_writer_add_read_buf_callback(mz_zip_archive *pZip, const char *pArchive_name, mz_file_read_func read_callback, void* callback_opaque, mz_uint64 max_size, + const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data_local, mz_uint user_extra_data_local_len, + const char *user_extra_data_central, mz_uint user_extra_data_central_len); + + +#ifndef MINIZ_NO_STDIO +/* Adds the contents of a disk file to an archive. This function also records the disk file's modified time into the archive. */ +/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ +MINIZ_EXPORT mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); + +/* Like mz_zip_writer_add_file(), except the file data is read from the specified FILE stream. */ +MINIZ_EXPORT mz_bool mz_zip_writer_add_cfile(mz_zip_archive *pZip, const char *pArchive_name, MZ_FILE *pSrc_file, mz_uint64 max_size, + const MZ_TIME_T *pFile_time, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, const char *user_extra_data_local, mz_uint user_extra_data_local_len, + const char *user_extra_data_central, mz_uint user_extra_data_central_len); +#endif + +/* Adds a file to an archive by fully cloning the data from another archive. */ +/* This function fully clones the source file's compressed data (no recompression), along with its full filename, extra data (it may add or modify the zip64 local header extra data field), and the optional descriptor following the compressed data. */ +MINIZ_EXPORT mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint src_file_index); + +/* Finalizes the archive by writing the central directory records followed by the end of central directory record. */ +/* After an archive is finalized, the only valid call on the mz_zip_archive struct is mz_zip_writer_end(). */ +/* An archive must be manually finalized by calling this function for it to be valid. */ +MINIZ_EXPORT mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip); + +/* Finalizes a heap archive, returning a poiner to the heap block and its size. */ +/* The heap block will be allocated using the mz_zip_archive's alloc/realloc callbacks. */ +MINIZ_EXPORT mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **ppBuf, size_t *pSize); + +/* Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used. */ +/* Note for the archive to be valid, it *must* have been finalized before ending (this function will not do it for you). */ +MINIZ_EXPORT mz_bool mz_zip_writer_end(mz_zip_archive *pZip); + +/* -------- Misc. high-level helper functions: */ + +/* mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) appends a memory blob to a ZIP archive. */ +/* Note this is NOT a fully safe operation. If it crashes or dies in some way your archive can be left in a screwed up state (without a central directory). */ +/* level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. */ +/* TODO: Perhaps add an option to leave the existing central dir in place in case the add dies? We could then truncate the file (so the old central dir would be at the end) if something goes wrong. */ +MINIZ_EXPORT mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); +MINIZ_EXPORT mz_bool mz_zip_add_mem_to_archive_file_in_place_v2(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_zip_error *pErr); + +/* Reads a single file from an archive into a heap block. */ +/* If pComment is not NULL, only the file with the specified comment will be extracted. */ +/* Returns NULL on failure. */ +MINIZ_EXPORT void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags); +MINIZ_EXPORT void *mz_zip_extract_archive_file_to_heap_v2(const char *pZip_filename, const char *pArchive_name, const char *pComment, size_t *pSize, mz_uint flags, mz_zip_error *pErr); + +#endif /* #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS */ + +#ifdef __cplusplus +} +#endif + +#endif /* MINIZ_NO_ARCHIVE_APIS */ diff --git a/lualib/Cache/init.lua b/lualib/Cache/init.lua index a1194f51..fb3b5a73 100644 --- a/lualib/Cache/init.lua +++ b/lualib/Cache/init.lua @@ -1,180 +1 @@ -local log = require "log" -local Co = require "internal.Co" -local timer = require "internal.Timer" -local redis = require "protocol.redis" - -local co_self = Co.self -local co_wait = Co.wait -local co_wakeup = Co.wakeup - -local type = type -local ipairs = ipairs -local setmetatable = setmetatable - -local table = table -local unpack = table.unpack -local remove = table.remove -local upper = string.upper -local lower = string.lower -local splite = string.gmatch - - --- 默认情况下, 保持50个redis连接 -local MAX, COUNT = 50, 0 - --- 连接池 -local POOL = {} - --- 是否已经初始化 -local INITIALIZATION = false - --- session创建函数 -local CREATE_CACHE - --- 注册命令 -local commands = { - 'sismember', 'exists' -} - -local function in_command(cmd) - for _, command in ipairs(commands) do - if lower(cmd) == command then - return true - end - end - return false -end - -local wlist = {} - -local function add_wait(co) - wlist[#wlist+1] = co -end - -local function pop_wait() - return remove(wlist) -end - -local function add_cache(session) - POOL[#POOL+1] = session -end - -local function pop_cache() - if #POOL > 0 then - return remove(POOL) - end - if COUNT < MAX then - COUNT = COUNT + 1 - return CREATE_CACHE() - end - add_wait(co_self()) - return co_wait() -end - - -local Cache = setmetatable({}, {__index = function (_, key) - if not INITIALIZATION then - return nil, 'Cache尚未初始化' - end - if lower(key) == "publish" or lower(key) == "subscribe" or lower(key) == "psubscribe" then - return nil, 'Cache error: Cache不支持在缓存中直接使用此命令.' - end - local cache = pop_cache() - if in_command(key) then - return function (_, ...) - local ok, ret - while 1 do - ok, ret = cache[key](cache, ...) - if ret ~= 'server close!!' then - break - end - cache:close() - cache = CREATE_CACHE() - end - if #wlist > 0 then - co_wakeup(pop_wait(), cache) - else - add_cache(cache) - end - return ok, ret - end - end - return function (_, ...) - local ok, ret - local keys = {} - for k in splite(key, "([^_]+)") do - keys[#keys+1] = k - end - while 1 do - if #keys > 1 then - ok, ret = cache:cmd(upper(keys[1]), upper(keys[2]), ...) - else - ok, ret = cache:cmd(upper(keys[1]), ...) - end - if ret ~= 'server close!!' then - break - end - cache:close() - cache = CREATE_CACHE() - end - if #wlist > 0 then - co_wakeup(pop_wait(), cache) - else - add_cache(cache) - end - return ok, ret - end -end}) - --- 初始化 -function Cache.init(opt) - if INITIALIZATION then - return nil, "Cache已经初始化." - end - - assert(type(opt) == 'table', "Cache error: 错误的Cache配置文件.") - - assert(type(opt.host) == 'string' and opt.host ~= '', "Cache error: 异常的主机名.") - - assert(type(opt.port) == 'number' and opt.port > 0 and opt.port <= 65535, "Cache error: 异常的端口.") - - assert(not opt.auth or type(opt.auth) == 'string' , "Cache error: 异常的auth.") - - assert(not opt.db or type(opt.db) == 'number' and opt.db >= 0 and opt.db <= 15, "Cache error: 异常的db.") - - if type(opt.max) == 'number' and opt.max > 0 then - MAX = opt.max - end - - CREATE_CACHE = function () - local times = 1 - local rds - while 1 do - rds = redis:new(opt) - local ok, err = rds:connect() - if ok then - break - end - log.error('第'..tostring(times)..'次连接失败:'..err.." 3 秒后尝试再次连接") - times = times + 1 - rds:close() - timer.sleep(3) - end - local ok, ret = rds:cmd("CONFIG", "GET", "TIMEOUT") - if not INITIALIZATION and ret[2] ~= '0' then - rds:cmd("CONFIG SET", "TIMEOUT", "0") - end - return rds - end - add_cache(CREATE_CACHE()) - INITIALIZATION = true - COUNT = COUNT + 1 - return true -end - --- 连接池数量 -function Cache.count() - return #POOL -end - -return Cache \ No newline at end of file +return require "Cache.redis" \ No newline at end of file diff --git a/lualib/Cache/redis.lua b/lualib/Cache/redis.lua new file mode 100644 index 00000000..2ec67630 --- /dev/null +++ b/lualib/Cache/redis.lua @@ -0,0 +1,190 @@ +local class = require "class" +local log = require "logging" +local Co = require "internal.Co" +local timer = require "internal.Timer" +local redis = require "protocol.redis" + +local co_self = Co.self +local co_wait = Co.wait +local co_wakeup = Co.wakeup + +local ipairs = ipairs +local assert = assert +local tostring = tostring +local setmetatable = setmetatable + +local table = table +local insert = table.insert +local remove = table.remove +local upper = string.upper +local lower = string.lower +local splite = string.gmatch + +local Log = log:new({ dump = true, path = 'Cache'}) + +-- 注册命令 +local commands = { + 'sismember', 'exists', 'pipeline' +} + +local function in_command(cmd) + for _, command in ipairs(commands) do + if lower(cmd) == command then + return true + end + end + return false +end + +local keys = {} + +-- 注入函数 +local function in_keys(key) + return keys[key] +end + +-- 创建Cache函数 +local function CREATE_CACHE(opt) + local rds + while 1 do + rds = redis:new(opt):set_timeout(3) + local ok, err = rds:connect() + if ok then + if not opt.INITIALIZATION then + local opok, ret = assert(rds:cmd("CONFIG", "GET", "TIMEOUT")) + if opok and ret[2] ~= '0' then + assert(rds:cmd("CONFIG SET", "TIMEOUT", "0"), "SET TIMEOUT faild.") + end + end + break + end + Log:WARN("[Cache ERROR]: The connection failed. The reasons are: [" .. err .. "], Try to reconnect after 3 seconds") + rds:close() + timer.sleep(3) + end + return rds:set_timeout(0) +end + +-- 加入到协程池内 +local function add_wait(self, co) + insert(self.co_pool, co) +end + +-- 弹出一个等待协程 +local function pop_wait(self) + return remove(self.co_pool) +end + +-- 加入到连接池内 +local function add_cache(self, cache) + insert(self.cache_pool, cache) +end + +-- 从连接池内取出一个cache对象 +local function pop_cache(self) + if #self.cache_pool > 0 then + return remove(self.cache_pool) + end + if self.current < self.max then + self.current = self.current + 1 + return CREATE_CACHE(self) + end + add_wait(self, co_self()) + return co_wait() +end + +-- 构建Cache对象 +local function setmeta(self) + keys['count'] = self.count + return setmetatable(self, { + __index = function(t, key) + local f = in_keys(key) + if f then + return f + end + if lower(key) == "publish" or lower(key) == "subscribe" or lower(key) == "psubscribe" then + return nil, '[Cache ERROR]: Cache不支持在缓存中直接使用此命令.' + end + if in_command(key) then + return function (_, ...) + local ok, ret + local session + while 1 do + session = pop_cache(t) + ok, ret = session[key](session, ...) + if session:isconnected() then + break + end + session:close() + session = nil + end + local co = pop_wait(t) + if co then + co_wakeup(co, session) + return ok, ret + end + add_cache(t, session) + return ok, ret + end + end + return function (_, ...) + local ok, ret + local keys = {} + for k in splite(key, "([^_]+)") do + keys[#keys+1] = k + end + local session + while 1 do + session = pop_cache(t) + if #keys > 1 then + ok, ret = session:cmd(upper(keys[1]), upper(keys[2]), ...) + else + ok, ret = session:cmd(upper(keys[1]), ...) + end + if session:isconnected() then + break + end + session:close() + session = nil + end + local co = pop_wait(t) + if co then + co_wakeup(co, session) + return ok, ret + end + add_cache(t, session) + return ok, ret + end + end}) == self +end + +local Cache = class("Cache") + +function Cache:ctor (opt) + self.host = opt.host + self.port = opt.port + self.unixdomain = opt.unixdomain + self.db = opt.db + self.auth = opt.auth + self.max = opt.max or 50 + self.current = 0 + -- 连接池 + self.cache_pool = {} + -- 协程池 + self.co_pool = {} +end + +function Cache:connect () + if not self.INITIALIZATION then + add_cache(self, pop_cache(self)) + self.INITIALIZATION = true + return setmeta(self) + end + return true +end + +function Cache:count() + return self.current, self.max +end + +return Cache \ No newline at end of file diff --git a/lualib/Cache/ssdb.lua b/lualib/Cache/ssdb.lua new file mode 100644 index 00000000..24ffcbbf --- /dev/null +++ b/lualib/Cache/ssdb.lua @@ -0,0 +1,190 @@ +local class = require "class" +local log = require "logging" +local Co = require "internal.Co" +local timer = require "internal.Timer" +local ssdb = require "protocol.redis" + +local co_self = Co.self +local co_wait = Co.wait +local co_wakeup = Co.wakeup + +local ipairs = ipairs +local setmetatable = setmetatable + +local table = table +local insert = table.insert +local remove = table.remove +local upper = string.upper +local lower = string.lower +local splite = string.gmatch + +local Log = log:new({ dump = true, path = 'Cache'}) + +-- 注册命令 +local commands = { 'exists', 'pipeline' } + +local function in_command(cmd) + for _, command in ipairs(commands) do + if lower(cmd) == command then + return true + end + end + return false +end + +local keys = {} + +-- 注入函数 +local function in_keys(key) + return keys[key] +end + +-- 创建Cache函数 +local function CREATE_CACHE(opt) + local rds + opt.db = nil + while 1 do + rds = ssdb:new(opt):set_timeout(3) + local ok, err = rds:connect() + if ok then + -- SSDB 不支持配置命令 + break + end + Log:WARN("[Cache ERROR]: The connection failed. The reasons are: [" .. err .. "], Try to reconnect after 3 seconds") + rds:close() + timer.sleep(3) + end + return rds:set_timeout(0) +end + +-- 加入到协程池内 +local function add_wait(self, co) + insert(self.co_pool, co) +end + +-- 弹出一个等待协程 +local function pop_wait(self) + return remove(self.co_pool) +end + +-- 加入到连接池内 +local function add_cache(self, cache) + insert(self.cache_pool, cache) +end + +-- 从连接池内取出一个cache对象 +local function pop_cache(self) + if #self.cache_pool > 0 then + return remove(self.cache_pool) + end + if self.current < self.max then + self.current = self.current + 1 + return CREATE_CACHE(self) + end + add_wait(self, co_self()) + return co_wait() +end + +-- 构建Cache对象 +local function setmeta(self) + keys['count'] = self.count + return setmetatable(self, { + __index = function(t, key) + local f = in_keys(key) + if f then + return f + end + if lower(key) == "publish" or lower(key) == "subscribe" or lower(key) == "psubscribe" then + return nil, '[Cache ERROR]: Cache不支持在缓存中直接使用此命令.' + end + if in_command(key) then + return function (_, ...) + local ok, ret + local session + while 1 do + session = pop_cache(t) + ok, ret = session[key](session, ...) + if session:isconnected() then + break + end + session:close() + session = nil + end + local co = pop_wait(t) + if co then + co_wakeup(co, session) + return ok, ret + end + add_cache(t, session) + return ok, ret + end + end + return function (_, ...) + local ok, ret + local keys = {} + for k in splite(key, "([^_]+)") do + keys[#keys+1] = k + end + if + key == 'multi_set' or key == 'multi_get' or key == 'multi_del' or + key == 'multi_hget' or key == 'multi_hset' or key == 'multi_hdel' or + key == 'multi_zget' or key == 'multi_zset' or key == 'multi_zdel' or + key == 'zpop_front' or key == 'zpop_back' or key == 'zpush_front' or key == 'zpush_back' + then + keys = {key} + end + local session + while 1 do + session = pop_cache(t) + if #keys > 1 then + ok, ret = session:cmd(upper(keys[1]), upper(keys[2]), ...) + else + ok, ret = session:cmd(upper(keys[1]), ...) + end + if session:isconnected() then + break + end + session:close() + session = nil + end + local co = pop_wait(t) + if co then + co_wakeup(co, session) + return ok, ret + end + add_cache(t, session) + return ok, ret + end + end}) == self +end + +local Cache = class("Cache") + +function Cache:ctor (opt) + self.host = opt.host + self.port = opt.port + self.unixdomain = opt.unixdomain + self.db = opt.db + self.auth = opt.auth + self.max = opt.max or 50 + self.current = 0 + -- 连接池 + self.cache_pool = {} + -- 协程池 + self.co_pool = {} +end + +function Cache:connect () + if not self.INITIALIZATION then + add_cache(self, pop_cache(self)) + self.INITIALIZATION = true + return setmeta(self) + end + return true +end + +function Cache:count() + return self.current, self.max +end + +return Cache \ No newline at end of file diff --git a/lualib/DB/init.lua b/lualib/DB/init.lua index e5a5184f..36b57870 100644 --- a/lualib/DB/init.lua +++ b/lualib/DB/init.lua @@ -1,511 +1 @@ -local mysql = require "protocol.mysql" -local timer = require "internal.Timer" -local co = require "internal.Co" -local log = require "log" - -local co_self = co.self -local co_wait = co.wait -local co_wakeup = co.wakeup - -local type = type -local tostring = tostring -local tonumber = tonumber -local assert = assert -local ipairs = ipairs - -local rep = string.rep -local find = string.find -local fmt = string.format -local lower = string.lower -local upper = string.upper -local match = string.match - -local insert = table.insert -local remove = table.remove -local concat = table.concat -local unpack = table.unpack - -local os_time = os.time - -local SELECT = "SELECT" - -local INSERT = "INSERT INTO" - -local UPDATE = "UPDATE" - -local DELETE = "DELETE" - -local SET = "SET" - -local FROM = "FROM" - -local WHERE = "WHERE" - -local IN = "IN" - -local IS = "IS" - -local NOT = "NOT" - -local BETWEEN = "BETWEEN" - -local AND = " AND " - -local LIMIT = "LIMIT" - -local DESC = "DESC" - -local ASC = "ASC" - -local ORDERBY = "ORDER BY" - -local GROUPBY = "GROUP BY" - -local COMMA = ", " - -local INITIALIZATION - --- 最大DB连接数量 -local MAX, COUNT = 50, 0 - --- 空闲连接时间 -local WAIT_TIMEOUT = 2592000 - --- 数据库连接创建函数 -local DB_CREATE - --- 等待db对象的协程列表 -local wlist = {} - -local function add_wait(co) - wlist[#wlist+1] = co -end - -local function pop_wait() - return remove(wlist) -end - --- 数据库连接池 -local POOL = {} - -local function add_db(db) - POOL[#POOL + 1] = { session = db, ttl = os_time() } -end - --- 负责创建连接/加入等待队列 -local function get_db() - if #POOL > 0 then - while 1 do - local db = remove(POOL) - if not db then break end -- 连接池内已经没有连接了 - if db.ttl > os_time() - WAIT_TIMEOUT then - return db.session - end - db.session:close() - COUNT = COUNT - 1 - end - end - if COUNT < MAX then - COUNT = COUNT + 1 - local db = DB_CREATE() - if db then - return db - end - COUNT = COUNT - 1 - -- 连接失败或者其他情况, 将等待其他协程唤醒; 保证公平竞争数据库连接 - end - add_wait(co_self()) - return co_wait() -end - - --- 格式化处理函数 --- 将['field', '=', 'a'] 格式化为 field = a --- 将['field', 'NOT', 'IN', '(1, 2, 3)'] 格式化为 field NOT IN (1, 2, 3) -local function format(t) - return fmt(concat({rep("%s ", #t-1), "%s"}), unpack(t)) -end - -local function format_value1(t) - return fmt("%s %s '%s'", unpack(t)) -end - -local function format_value2(t, split) - local tmp = {} - for i=1, #t do - tmp[i] = "'%s'" - end - return fmt(concat(tmp, split), unpack(t)) -end - -local function format_value3(t) - return fmt("%s %s '%s'", unpack(t)) -end - -local function format_value4(t) - return fmt("%s %s %s '%s'", unpack(t)) -end - --- 执行 -local function execute(query) - if query.SELECT then - assert(query.FROM and query.WHERE, "查询语句必须使用from方法与where条件.") - end - if query.DELETE then - assert(query.WHERE, "删除语句请加上where条件") - end - if query.INSERT then - assert(query.FILEDS and query.VALUES, "插入语句请加上fields与values") - end - if query.UPDATE then - assert(query.WHERE, "更新语句请加上where条件") - end - local QUERY = concat(query, " ") - -- print(QUERY) - local db, ret, err - while 1 do - db = get_db() - if db then - ret, err = db:query(QUERY) - if db.state then - break - end - log.error(err) - db:close() - db, ret, err = nil - end - end - if #wlist > 0 then - co_wakeup(pop_wait(), db) - else - add_db(db) - end - return ret, err -end - -local function limit(query, limit1, limit2) - local t1 = type(limit1) - local t2 = type(limit2) - assert(query and type(query) == 'table' and (t1 == "number" or t1 == "string" ), "错误的限制条件(limit):"..tostring(limit1)) - - insert(query, LIMIT) - - local limits = {tostring(limit1)} - if t2 == "number" or tpy == "string" then - insert(limits, tostring(limit2)) - end - - insert(query, concat(limits, COMMA)) - - return query -end - --- 降序 -local function desc(query) - insert(query, DESC) - return query -end --- 升序 -local function asc(query) - insert(query, ASC) - return query -end - --- 聚合字段 -local function groupby(query, fields) - local tpy = type(fields) - assert(query and (tpy == "string" or tpy == "table"), "错误的聚合字段类型(fields):"..tostring(fields)) - insert(query, GROUPBY) - if tpy == "string" then - insert(query, fields) - end - if tpy == "table" then - insert(query, concat(fields, COMMA)) - end - return query -end - --- 排序字段 -local function orderby(query, orders) - local tpy = type(orders) - assert(query and (tpy == "string" or tpy == "table"), "错误的排序条件(orders):"..tostring(orders)) - insert(query, ORDERBY) - if tpy == "string" then - insert(query, orders) - end - if tpy == "table" then - insert(query, concat(orders, COMMA)) - end - return query -end - --- 条件 -local function where(query, conditions) - local tpy = type(conditions) - assert(query and (tpy == "string" or tpy == "table"), "错误的条件类型(where):"..tostring(conditions)) - insert(query, WHERE) - if tpy == "string" then - insert(query, conditions) - end - if tpy == "table" then - local CONDITIONS = {} - for index, condition in ipairs(conditions) do - if type(condition) == "table" and #condition == 3 then - local con2 = upper(condition[2]) - if con2 == IN or con2 == BETWEEN then -- 假设比较符是IN 或者 BETWEEN - local LEFT, RIGHT = '', '' - local c = AND - if con2 == IN then - c = COMMA - LEFT, RIGHT = '(', ')' - end - insert(CONDITIONS, format({condition[1], con2, LEFT..format_value2(condition[3], c)..RIGHT})) - elseif con2 == IS then - insert(CONDITIONS, concat(condition, " ")) - elseif find(condition[3], condition[1]) then - insert(CONDITIONS, concat(condition, " ")) - else - insert(CONDITIONS, format_value3(condition)) - end - elseif type(condition) == "table" and #condition == 4 then - local con2 = upper(condition[2]) - local con3 = upper(condition[3]) - if con3 == IN or con3 == BETWEEN then -- 假设比较符是IN 或者 BETWEEN - local LEFT, RIGHT = '', '' - local c = AND - if con3 == IN then - c = COMMA - LEFT, RIGHT = '(', ')' - end - insert(CONDITIONS, format({condition[1], con2, con3, LEFT..format_value2(condition[4], c)..RIGHT})) - elseif con2 == IS then - insert(CONDITIONS, concat(condition, " ")) - else - insert(CONDITIONS, format_value4(condition)) - end - else -- 假设condition为 "AND" 、 "OR" - insert(CONDITIONS, upper(condition)) - end - end - insert(query, concat(CONDITIONS, " ")) - end - query.WHERE = true - return query -end - --- 表(s) -local function from(query, tables) - local tpy = type(tables) - assert(query and (tpy == "string" or tpy == "table"), "错误的表名:"..tostring(tables)) - insert(query, FROM) - if tpy == "string" then - insert(query, tables) - end - if tpy == "table" then - insert(query, concat(tables, COMMA)) - end - query.FROM = true - return query -end - --- 插入语句专用函数 -- -local function values(query, values) - local tpy = type(values) - assert(tpy == "table" and #values > 0 and type(values[1]) == "table" , "错误的值类型(values):"..tostring(values)) - local VALUES = {} - for _, value in ipairs(values) do - insert(VALUES, "("..format_value2(value, COMMA)..")") - end - insert(query, "VALUES") - insert(query, concat(VALUES, COMMA)) - query.VALUES = true - return query -end - -local function fields(query, fields) - local tpy = type(fields) - assert(tpy == "table" and #fields > 0, "错误的字段类型(values):"..tostring(fields)) - insert(query, "("..concat(fields, COMMA)..")") - query.FILEDS = true - return query -end --- 插入语句专用函数 -- - - - - - --- 更新语句专用 -- -local function set(query, values) - local tpy = type(values) - assert(tpy == "table" and #values > 0 and type(values[1]) == "table" , "错误的值类型(values):"..tostring(values)) - local VALUES = {} - for _, value in ipairs(values) do - insert(VALUES, format_value1(value)) - end - insert(query, SET) - insert(query, concat(VALUES, COMMA)) - return query -end --- 更新语句专用 -- - - - -local DB = {} - --- 初始化数据库 -function DB.init(opt) - if INITIALIZATION then - return nil, "DB已经初始化." - end - if type(opt) ~= 'table' then - return nil, '错误的DB配置文件.' - end - if type(opt.max) == 'number' then - MAX = opt.max - end - local config = { - host = opt.host or 'localhost', - port = opt.port or 3306, - database = opt.database, - user = opt.user, - password = opt.password, - } - DB_CREATE = function(...) - local times = 1 - local db - while 1 do - db = mysql:new() - local connect, err = db:connect(config) - if connect then - break - end - log.error('第'..tostring(times)..'次连接失败:'..err.." 3 秒后尝试再次连接") - db:close() - times = times + 1 - timer.sleep(3) - end - db:query(fmt('SET wait_timeout=%s', tostring(WAIT_TIMEOUT))) - db:query(fmt('SET interactive_timeout=%s', tostring(WAIT_TIMEOUT))) - return db - end - add_db(get_db()) - INITIALIZATION = true - return true -end - - --- 查询语句 -function DB.select(fields) - if not INITIALIZATION then - return nil, "DB尚未初始化" - end - local tpy = type(fields) - assert(tpy == "string" or tpy == "table", "错误的字段类型(fields):"..tostring(fields)) - local query = { - [1] = SELECT, - SELECT = true, - from = from, - where = where, - orderby = orderby, - groupby = groupby, - desc = desc, - asc = asc, - limit = limit, - execute = execute, - } - if tpy == "string" then - insert(query, fields) - end - if tpy == "table" then - insert(query, concat(fields, COMMA)) - end - return query -end - --- 插入语句 -function DB.insert(table_name) - if not INITIALIZATION then - return nil, "DB尚未初始化" - end - local tpy = type(table_name) - assert(tpy == "string", "错误的表名(table_name):"..tostring(table_name)) - return { - [1] = INSERT, - [2] = table_name, - INSERT = true, - fields = fields, - values = values, - execute = execute, - } -end - --- 更新语句 -function DB.update(table_name) - if not INITIALIZATION then - return nil, "DB尚未初始化" - end - local tpy = type(table_name) - assert(tpy == "string" or tpy == "tables", "错误的表名(table_name):"..tostring(table_name)) - return { - [1] = UPDATE, - [2] = table_name, - UPDATE = true, - set = set, - where = where, - limit = limit, - execute = execute, - } -end - --- 删除语句 -function DB.delete(table_name) - if not INITIALIZATION then - return nil, "DB尚未初始化" - end - local tpy = type(table_name) - assert(tpy == "string" or tpy == "tables", "错误的表名(table_name):"..tostring(table_name)) - return { - [1] = DELETE, - [2] = FROM, - [3] = table_name, - DELETE = true, - where = where, - limit = limit, - orderby = orderby, - execute = execute, - } -end - --- 原始SQL -function DB.query(query) - if not INITIALIZATION then - return nil, "DB尚未初始化" - end - assert(type(query) == 'string' and query ~= '' , "原始SQL类型错误(query):"..tostring(query)) - local db, ret, err - while 1 do - db = get_db() - if db then - ret, err = db:query(query) - if db.state then - break - end - log.error(err) - db:close() - db, ret, err = nil - end - end - if #wlist > 0 then - co_wakeup(pop_wait(), db) - else - add_db(db) - end - return ret, err -end - -function DB.count( ... ) - return #POOL -end - -return DB \ No newline at end of file +return require "DB.mysql" diff --git a/lualib/DB/mssql.lua b/lualib/DB/mssql.lua new file mode 100644 index 00000000..3f9795f6 --- /dev/null +++ b/lualib/DB/mssql.lua @@ -0,0 +1,230 @@ +local class = require "class" + +local timer = require "internal.Timer" +local mssql = require "protocol.mssql" + +local log = require "logging" +local Log = log:new({ dump = true, path = 'DB'}) + +local crypt = require "crypt" +local hashkey = crypt.hashkey + +local co = require "internal.Co" +local co_self = co.self +local co_wait = co.wait +local co_spawn = co.spawn +local co_wakeup = co.wakeup + +local type = type +local error = error +local xpcall = xpcall +local assert = assert + +local fmt = string.format + +local insert = table.insert +local remove = table.remove + +-- 数据库连接创建函数 +local function DB_CREATE (opt) + local db + while 1 do + db = mssql:new(opt) + db:set_timeout(3) + local connect, err = db:connect() + if connect then + db:set_timeout(0) + break + end + Log:WARN("The connection failed. The reasons are: [" .. err .. "], Try to reconnect after 3 seconds") + timer.sleep(3) + db:close() + end + return db +end + +local function add_wait(self, co) + insert(self.co_pool, co) +end + +local function pop_wait(self) + return remove(self.co_pool) +end + +local function add_db(self, db) + insert(self.db_pool, db) +end + +-- 负责创建连接/加入等待队列 +local function pop_db(self) + local session = remove(self.db_pool) + if session then + return session + end + if self.current < self.max then + self.current = self.current + 1 + return DB_CREATE(self) + end + add_wait(self, co_self()) + return co_wait() +end + +local function run_query(self, query) + local db, ret, err + while 1 do + db = pop_db(self) + if db then + ret, err = db:query(query) + if db.state then + break + end + db:close() + self.current = self.current - 1 + db, ret, err = nil, nil, nil + end + end + local co = pop_wait(self) + if co then + co_wakeup(co, db) + else + add_db(self, db) + end + return ret, err +end + +local DB = class("DB") + +function DB:ctor(opt) + self.host = opt.host + self.port = opt.port + self.unixdomain = opt.unixdomain + self.username = opt.username + self.password = opt.password + self.database = opt.database + self.TSQL = opt.TSQL + self.max = opt.max or 50 + self.current = 0 + -- 协程池 + self.co_pool = {} + -- 连接池 + self.db_pool = {} +end + +function DB:connect () + if not self.INITIALIZATION then + add_db(self, pop_db(self)) + self.INITIALIZATION = true + return self.INITIALIZATION + end + return self.INITIALIZATION +end + +local function traceback(msg) + return fmt("[%s] %s", os.date("%Y/%m/%d %H:%M:%S"), debug.traceback(co_self(), msg, 3)) +end + +function DB:transaction(f) + assert(self.INITIALIZATION, "DB needs to be initialized first.") + assert(type(f) == 'function', "A function must be passed to describe the execution of the transaction.") + local db, ret, err + while 1 do + db = pop_db(self) + if db then + ret, err = db:query("BEGIN TRAN;") + if db.state then + break + end + db:close() + self.current = self.current - 1 + end + -- db, ret, err = nil, nil, nil + end + -- 每个事务都有独立的session + local session = { nil, nil, nil } + session.query = function (self, sql) + assert(self and self == session, "Must use the syntax of `session:query()`") + if self.over then + return nil, "Please use `return session:rollback()` or `return session:commit()` after the transaction process is over or Process error." + end + assert(db.state, "MSSQL transaction session closed. 1") + local ret, err = db:query(sql) + assert(db.state, "MSSQL transaction session closed. 2") + return ret, err + end + session.rollback = function ( self ) + assert(self and self == session, "Must use the syntax of `session:rollback()`") + assert(db.state, "MSSQL transaction session closed. 3") + db:query("ROLLBACK TRAN;") + assert(db.state, "MSSQL transaction session closed. 4") + self.over = true + return { state = "rollback" } + end + session.commit = function ( self ) + assert(self and self == session, "Must use the syntax of `session:commit()`") + assert(db.state, "MSSQL transaction session closed. 5") + db:query("COMMIT TRAN;") + assert(db.state, "MSSQL transaction session closed. 6") + self.over = true + return { state = "successed" } + end + + local ok, info = xpcall(f, traceback, session) + if not ok then + -- 如果在自定义事务流程的内部发生了错误 + if not db.state then + self.current = self.current - 1 + db:close() + local co = pop_wait(self) + if co then + co_spawn(function ( ... ) + co_wakeup(co, pop_db(self)) + end) + end + return nil, info + end + db:query("ROLLBACK TRAN;") + local co = pop_wait(self) + if co then + co_wakeup(co, db) + else + add_db(self, db) + end + return nil, info + end + -- 如果定义的事务没有以`commit`或者`rollback`结尾. + if type(info) ~= 'table' or (info.state ~= "successed" and info.state ~= 'rollback') then + db:query("ROLLBACK TRAN;") + local co = pop_wait(self) + if co then + co_wakeup(co, db) + else + add_db(self, db) + end + error("Must return after transaction ends`session:commit()`or`session:rollback()`.") + end + local co = pop_wait(self) + if co then + co_wakeup(co, db) + else + add_db(self, db) + end + return info.state == "successed" and true or false +end + +-- 原始查询语句 +function DB:query(query) + assert(self.INITIALIZATION, "DB needs to be initialized first.") + return run_query(self, assert(type(query) == 'string' and query ~= '' and query , "Invalid MSSQL syntax.")) +end + +-- 字符串安全转义 +function DB.quote_to_str( str ) + return mssql.quote_to_str(str) +end + +function DB:count() + assert(self.INITIALIZATION, "DB needs to be initialized first.") + return self.current, self.max, #self.co_pool, #self.db_pool +end + +return DB diff --git a/lualib/DB/mysql.lua b/lualib/DB/mysql.lua new file mode 100644 index 00000000..e6caff73 --- /dev/null +++ b/lualib/DB/mysql.lua @@ -0,0 +1,283 @@ +local class = require "class" + +local timer = require "internal.Timer" +local mysql = require "protocol.mysql" + +local log = require "logging" +local Log = log:new({ dump = true, path = 'DB'}) + +local co = require "internal.Co" +local co_self = co.self +local co_wait = co.wait +local co_wakeup = co.wakeup + +local type = type +local error = error +local xpcall = xpcall +local assert = assert + +local fmt = string.format + +local insert = table.insert +local remove = table.remove + +-- 空闲连接时间 +local WAIT_TIMEOUT = 31536000 +local INTERACTIVE_TIMEOUT = 31536000 + +-- 数据库连接创建函数 +local function DB_CREATE (opt) + local db + while 1 do + db = mysql:new(opt) + db:set_timeout(3) + local connect, err = db:connect() + if connect then + assert(db:query(fmt('SET wait_timeout=%u', WAIT_TIMEOUT))) + assert(db:query(fmt('SET interactive_timeout=%u', INTERACTIVE_TIMEOUT))) + db:set_timeout(0) + break + end + Log:WARN("The connection failed. The reasons are: [" .. err .. "], Try to reconnect after 3 seconds") + timer.sleep(3) + db:close() + end + return db +end + +local function add_wait(self, co) + insert(self.co_pool, co) +end + +local function pop_wait(self) + return remove(self.co_pool) +end + +local function add_db(self, db) + insert(self.db_pool, db) +end + +-- 负责创建连接/加入等待队列 +local function pop_db(self) + local session = remove(self.db_pool) + if session then + return session + end + if self.current < self.max then + self.current = self.current + 1 + return DB_CREATE(self) + end + add_wait(self, co_self()) + return co_wait() +end + +local function run_query(self, query) + local db, ret, err + while 1 do + db = pop_db(self) + if db then + ret, err = db:query(query) + if db.state then + break + end + db:close() + self.current = self.current - 1 + db, ret, err = nil, nil, nil + end + end + local co = pop_wait(self) + if co then + co_wakeup(co, db) + else + add_db(self, db) + end + return ret, err +end + +local function run_execute(self, query, ...) + local db, ret, err + while 1 do + db = pop_db(self) + if db then + ret, err = db:execute(query, ...) + if db.state then + break + end + db:close() + self.current = self.current - 1 + db, ret, err = nil, nil, nil + end + end + local co = pop_wait(self) + if co then + co_wakeup(co, db) + else + add_db(self, db) + end + return ret, err +end + +-- 事务session +local session = class("session") + +function session:ctor(opt) + self.db = opt.db + self.over = false +end + +function session:execute(sql, ...) + if self.over then + return nil, "Please use `return session:rollback()` or `return session:commit()` after the transaction process is over or Process error." + end + -- assert(self.db.state, "MySQL transaction session closed. 1") + local ret, err = self.db:execute(sql, ...) + assert(self.db.state, "MySQL transaction session closed. 1") + return ret, err +end + +function session:query(sql) + if self.over then + return nil, "Please use `return session:rollback()` or `return session:commit()` after the transaction process is over or Process error." + end + -- assert(self.db.state, "MySQL transaction session closed. 1") + local ret, err = self.db:query(sql) + assert(self.db.state, "MySQL transaction session closed. 2") + return ret, err +end + +function session:rollback() + if self.over then + return + end + assert(self.db.state, "MySQL transaction session closed. 3") + self.db:query("rollback; set autocommit=1;") + assert(self.db.state, "MySQL transaction session closed. 4") + self.over = true + self.db = nil + return { state = "rollback" } +end + +function session:commit() + if self.over then + return + end + assert(self.db.state, "MySQL transaction session closed. 5") + self.db:query("set autocommit=1;") + assert(self.db.state, "MySQL transaction session closed. 6") + self.over = true + self.db = nil + return { state = "successed" } +end + +local DB = class("DB") + +function DB:ctor(opt) + self.host = opt.host + self.port = opt.port + self.unixdomain = opt.unixdomain + self.username = opt.username + self.password = opt.password + self.database = opt.database + self.charset = opt.charset or 'utf8' + self.max = opt.max or 50 + self.current = 0 + -- 协程池 + self.co_pool = {} + -- 连接池 + self.db_pool = {} +end + +function DB:connect () + if not self.INITIALIZATION then + add_db(self, pop_db(self)) + self.INITIALIZATION = true + return self.INITIALIZATION + end + return self.INITIALIZATION +end + +local function traceback(msg) + return fmt("[%s] %s", os.date("%Y/%m/%d %H:%M:%S"), debug.traceback(co_self(), msg, 3)) +end + +function DB:transaction(f) + assert(self.INITIALIZATION, "DB needs to be initialized first.") + assert(type(f) == 'function', "A function must be passed to describe the execution of the transaction.") + local db, ret, err + while 1 do + db = pop_db(self) + if db then + ret, err = db:query("set autocommit=0;") + if db.state then + break + end + db:close() + self.current = self.current - 1 + end + -- db, ret, err = nil, nil, nil + end + + local ok, info = xpcall(f, traceback, session:new{ db = db }) + if not ok then + -- 如果在自定义事务流程的内部发生了错误 + if not db.state then + self.current = self.current - 1 + db:close() + local co = pop_wait(self) + if co then + co_wakeup(co, pop_db(self)) + end + return nil, info + end + db:query("rollback; set autocommit=1;") + local co = pop_wait(self) + if co then + co_wakeup(co, db) + else + add_db(self, db) + end + return nil, info + end + -- 如果定义的事务没有以`commit`或者`rollback`结尾. + if type(info) ~= 'table' or (info.state ~= "successed" and info.state ~= 'rollback') then + db:query("rollback; set autocommit=1;") + local co = pop_wait(self) + if co then + co_wakeup(co, db) + else + add_db(self, db) + end + error("After the transaction ends, either 'return session:commit()' or 'return session:rollback()' must be required.") + end + local co = pop_wait(self) + if co then + co_wakeup(co, db) + else + add_db(self, db) + end + return info.state == "successed" and true or false +end + +-- 查询接口 +function DB:query(query) + assert(self.INITIALIZATION, "DB needs to be initialized first.") + return run_query(self, assert(type(query) == 'string' and query ~= '' and query , "Invalid MySQL query syntax.")) +end + +-- 预处理接口 +function DB:execute(query, ...) + assert(self.INITIALIZATION, "DB needs to be initialized first.") + return run_execute(self, assert(type(query) == 'string' and query ~= '' and query , "Invalid MySQL prepare syntax."), ...) +end + +-- 字符串安全转义 +function DB.quote_to_str( str ) + return mysql.quote_to_str(str) +end + +function DB:count() + assert(self.INITIALIZATION, "DB needs to be initialized first.") + return self.current, self.max, #self.co_pool, #self.db_pool +end + +return DB diff --git a/lualib/DB/pgsql.lua b/lualib/DB/pgsql.lua new file mode 100644 index 00000000..2f4e1fc6 --- /dev/null +++ b/lualib/DB/pgsql.lua @@ -0,0 +1,229 @@ +local class = require "class" + +local timer = require "internal.Timer" +local pgsql = require "protocol.pgsql" + +local log = require "logging" +local Log = log:new({ dump = true, path = 'DB'}) + +local crypt = require "crypt" +local hashkey = crypt.hashkey + +local co = require "internal.Co" +local co_self = co.self +local co_wait = co.wait +local co_spawn = co.spawn +local co_wakeup = co.wakeup + +local type = type +local error = error +local xpcall = xpcall +local assert = assert + +local fmt = string.format + +local insert = table.insert +local remove = table.remove + +-- 数据库连接创建函数 +local function DB_CREATE (opt) + local db + while 1 do + db = pgsql:new(opt) + db:set_timeout(3) + local connect, err = db:connect() + if connect then + db:set_timeout(0) + break + end + Log:WARN("The connection failed. The reasons are: [" .. err .. "], Try to reconnect after 3 seconds") + timer.sleep(3) + db:close() + end + return db +end + +local function add_wait(self, co) + insert(self.co_pool, co) +end + +local function pop_wait(self) + return remove(self.co_pool) +end + +local function add_db(self, db) + insert(self.db_pool, db) +end + +-- 负责创建连接/加入等待队列 +local function pop_db(self) + local session = remove(self.db_pool) + if session then + return session + end + if self.current < self.max then + self.current = self.current + 1 + return DB_CREATE(self) + end + add_wait(self, co_self()) + return co_wait() +end + +local function run_query(self, query) + local db, ret, err + while 1 do + db = pop_db(self) + if db then + ret, err = db:query(query) + if db.state then + break + end + db:close() + self.current = self.current - 1 + db, ret, err = nil, nil, nil + end + end + local co = pop_wait(self) + if co then + co_wakeup(co, db) + else + add_db(self, db) + end + return ret, err +end + +local DB = class("DB") + +function DB:ctor(opt) + self.host = opt.host + self.port = opt.port + self.unixdomain = opt.unixdomain + self.username = opt.username + self.password = opt.password + self.database = opt.database + self.charset = opt.charset + self.max = opt.max or 50 + self.current = 0 + -- 协程池 + self.co_pool = {} + -- 连接池 + self.db_pool = {} +end + +function DB:connect () + if not self.INITIALIZATION then + add_db(self, pop_db(self)) + self.INITIALIZATION = true + return self.INITIALIZATION + end + return self.INITIALIZATION +end + +local function traceback(msg) + return fmt("[%s] %s", os.date("%Y/%m/%d %H:%M:%S"), debug.traceback(co_self(), msg, 3)) +end + +function DB:transaction(f) + assert(self.INITIALIZATION, "DB needs to be initialized first.") + assert(type(f) == 'function', "A function must be passed to describe the execution of the transaction.") + local db, ret, err + while 1 do + db = pop_db(self) + if db then + ret, err = db:query("BEGIN;") + if db.state then + break + end + db:close() + self.current = self.current - 1 + end + -- db, ret, err = nil, nil, nil + end + -- 每个事务都有独立的session + local session = { nil, nil, nil } + session.query = function (self, sql) + assert(self and self == session, "Must use the syntax of `session:query()`") + if self.over then + return nil, "Please use `return session:rollback()` or `return session:commit()` after the transaction process is over or Process error." + end + assert(db.state, "PGSQL transaction session closed. 1") + local ret, err = db:query(sql) + assert(db.state, "PGSQL transaction session closed. 2") + return ret, err + end + session.rollback = function ( self ) + assert(self and self == session, "Must use the syntax of `session:rollback()`") + assert(db.state, "PGSQL transaction session closed. 3") + db:query("ROLLBACK;") + assert(db.state, "PGSQL transaction session closed. 4") + self.over = true + return { state = "rollback" } + end + session.commit = function ( self ) + assert(self and self == session, "Must use the syntax of `session:commit()`") + assert(db.state, "PGSQL transaction session closed. 5") + db:query("COMMIT;") + assert(db.state, "PGSQL transaction session closed. 6") + self.over = true + return { state = "successed" } + end + local ok, info = xpcall(f, traceback, session) + if not ok then + -- 如果在自定义事务流程的内部发生了错误 + if not db.state then + self.current = self.current - 1 + db:close() + local co = pop_wait(self) + if co then + co_spawn(function ( ... ) + co_wakeup(co, pop_db(self)) + end) + end + return nil, info + end + db:query("ROLLBACK;") + local co = pop_wait(self) + if co then + co_wakeup(co, db) + else + add_db(self, db) + end + return nil, info + end + -- 如果定义的事务没有以`commit`或者`rollback`结尾. + if type(info) ~= 'table' or (info.state ~= "successed" and info.state ~= 'rollback') then + db:query("ROLLBACK;") + local co = pop_wait(self) + if co then + co_wakeup(co, db) + else + add_db(self, db) + end + error("Must return after transaction ends`session:commit()`or`session:rollback()`.") + end + local co = pop_wait(self) + if co then + co_wakeup(co, db) + else + add_db(self, db) + end + return info.state == "successed" and true or false +end + +-- 原始查询语句 +function DB:query(query) + assert(self.INITIALIZATION, "DB needs to be initialized first.") + return run_query(self, assert(type(query) == 'string' and query ~= '' and query , "Invalid PGSQL syntax.")) +end + +-- 字符串安全转义 +function DB.quote_to_str( str ) + return pgsql.quote_to_str(str) +end + +function DB:count() + assert(self.INITIALIZATION, "DB needs to be initialized first.") + return self.current, self.max, #self.co_pool, #self.db_pool +end + +return DB diff --git a/lualib/MQ/init.lua b/lualib/MQ/init.lua deleted file mode 100644 index 29b02942..00000000 --- a/lualib/MQ/init.lua +++ /dev/null @@ -1,191 +0,0 @@ -local log = require "log" -local class = require "class" -local Timer = require "internal.Timer" -local mqtt = require "protocol.mqtt" -local redis = require "protocol.redis" - -local type = type -local math = math -local random = math.random -local stirng = string -local fmt = string.format -local assert = assert - -local mq = class("mq") - -function mq:ctor(opt) - self.id = opt.id -- mqtt需要id可以指定 - self.host = opt.host -- 地址 - self.port = opt.port -- 端口 - self.type = opt.type -- 消息队列种类 - self.auth = opt.auth -- redis需要auth可以指定 - self.clean = opt.clean or true -- mqtt 默认清除会话 - self.username = opt.username -- mqtt只支持用户名+密码认证 - self.password = opt.password -- mqtt只支持用户名+密码认证 -end - -local function mq_login(self) - local times = 1 - while 1 do - if self.type == 'redis' then - local rds = redis:new {auth = self.auth, host = self.host, port = self.port} - local ok, err = rds:connect() - if ok then - return rds - end - rds:close() - log.error('连接mq(redis)失败:'..(err or "unknow")..'.正在尝试重连') - Timer.sleep(3) - times = times + 1 - elseif self.type == 'mqtt' then - local mqtt = mqtt:new { - host = self.host, - port = self.port, - auth = { - username = self.username, - password = self.password - }, - id = self.id or fmt('luamqtt-cf-v1-%X', random(1, 0xFFFFFFFF)), - } - local ok, err = mqtt:connect() - if ok then - return mqtt - end - mqtt:close() - log.error('连接mq(mqtt)失败:'..(err or "unknow")..'.正在尝试重连') - Timer.sleep(3) - times = times + 1 - else - error("未知的mq类型.") - end - end -end - -local function redis_subscribe(self) - local sub_mq, err = mq_login(self) - if not sub_mq then - return nil, err - end - self.sub_mq = sub_mq - return sub_mq:subscribe(self.pattern, self.func) -end - -local function mqtt_subscribe(self) - local sub_mq, err = mq_login(self) - if not sub_mq then - return nil, err - end - self.sub_mq = sub_mq - return sub_mq:subscribe({qos = 2, topic = self.pattern, clean = self.clean}, self.func) -end - -local function redis_publish(self) - local index = 1 - while 1 do - if not self.pub_mq then - local pub_mq, err = mq_login(self) - if not pub_mq then - return nil, err - end - self.pub_mq = pub_mq - end - while 1 do - local msg = self.queue[index] - if not msg then - self.queue = {} - return true - end - local ok, err = self.pub_mq:publish(msg.pattern, msg.payload) - if not ok then - break - end - index = index + 1 - end - if self.pub_mq then - self.pub_mq:close() - self.pub_mq = nil - end - end -end - -local function mqtt_publish(self) - local index = 1 - while 1 do - if not self.pub_mq then - local pub_mq, err = mq_login(self) - if not pub_mq then - return nil, err - end - self.pub_mq = pub_mq - end - while 1 do - local msg = self.queue[index] - if not msg then - self.queue = {} - return true - end - local ok = self.pub_mq:publish{topic = msg.pattern, payload = msg.payload, qos = 2} - if not ok then - break - end - index = index + 1 - end - if self.pub_mq then - self.pub_mq:close() - self.pub_mq = nil - end - end -end - --- 发布消息 -function mq:emit(pattern, data) - if type(pattern) ~= 'string' or pattern == '' then - return nil, "publish pattern error." - end - if type(data) ~= 'string' or data == '' then - return nil, "publish string error." - end - if not self.queue then - self.queue = {{pattern = pattern, payload = data}} - else - self.queue[#self.queue + 1] = {pattern = pattern, payload = data} - end - if self.type == 'redis' then - return redis_publish(self) - elseif self.type == 'mqtt' then - return mqtt_publish(self) - end - return error("mq publish error: 目前仅支持redis/mqtt协议.") -end - --- 订阅消息 -function mq:on(pattern, func) - if type(pattern) ~= 'string' or pattern == '' then - return nil, "subscribe pattern error." - end - if type(func) ~= 'function' then - return nil, "subscribe func error." - end - self.func = func -- 回调处理函数 - self.pattern = pattern -- 监听规则 - if self.type == 'redis' then - return redis_subscribe(self) - elseif self.type == 'mqtt' then - return mqtt_subscribe(self) - end - return error("mq subscribe error: 目前仅支持redis/mqtt协议.") -end - --- 关闭消息队列监听 -function mq:close() - if self.sub_mq then - self.sub_mq:close() - self.sub_mq = nil - end - if self.pub_mq then - self.pub_mq:close() - self.pub_mq = nil - end -end - -return mq diff --git a/lualib/MQ/mqtt.lua b/lualib/MQ/mqtt.lua new file mode 100644 index 00000000..153af41f --- /dev/null +++ b/lualib/MQ/mqtt.lua @@ -0,0 +1,134 @@ +local class = require "class" +local mqtt = require "protocol.mqtt" + +local logging = require "logging" +local Log = logging:new{dump = true, path = 'MQ-mqtt'} + +local cf = require "cf" +local cf_self = cf.self +local cf_fork = cf.fork +local cf_sleep = cf.sleep +local cf_wait = cf.wait +local cf_wakeup = cf.wakeup + +local ipairs = ipairs +local type = type +local insert = table.insert +local random = math.random +local fmt = string.format + +local mq = class('mqtt-mq') + +function mq:ctor (opt) + self.host = opt.host + self.port = opt.port + self.username = opt.username + self.password = opt.password + self.keepalive = opt.keepalive + self.patterns = {} + self.subsribes = {} + self.queue = {} +end + +local function _login (opt) + local times = 1 + while 1 do + local mq = mqtt:new { + id = opt.id or fmt('luamqtt-cf-v1-%X', random(1, 0xFFFFFFFF)), + host = opt.host, + port = opt.port, + clean = true, + keep_alive = opt.keepalive, + auth = { + username = opt.username, + password = opt.password, + }, + } + local ok, err = mq:connect() + if ok then + return mq + end + Log:WARN("第"..times.."次连接MQ(mqtt)失败:"..(err or "未知错误.")) + times = times + 1 + mq:close() + cf_sleep(3) + end +end + +-- 订阅事件循环 +local function subscribe (self, pattern, func) + local mq = _login(self) + self.subsribes[#self.subsribes+1] = mq + return mq:subscribe({qos = 2, topic = pattern}, func) +end + +-- 发布事件循环 +local function publish (self, pattern, data) + if #self.queue == 0 then + self.queue[#self.queue + 1] = {pattern = pattern, data = data, co = cf_self()} + cf_fork(function (...) + for _, msg in ipairs(self.queue) do + if not self.closed and self.emiter then + cf_wakeup(msg.co, self.emiter:publish{topic = msg.pattern, payload = msg.data, qos = 2}) + end + end + self.queue = {} + end) + return cf_wait() + end + insert(self.queue, {pattern = pattern, data = data, co = cf_self()}) + return cf_wait() +end + +-- 订阅 +function mq:on (pattern, func) + if type(pattern) ~= 'string' or pattern == '' then + return nil, "订阅消息失败: 错误的pattern类型" + end + if type(func) ~= 'function' then + return nil, "订阅消息失败: 错误的func类型" + end + for _, patt in ipairs(self.patterns) do + if patt == pattern then + return nil, '禁止重复订阅相同的频道' + end + end + self.patterns[#self.patterns + 1] = pattern + return subscribe(self, pattern, func) +end + +-- 发布 +function mq:emit (pattern, data) + if type(pattern) ~= 'string' or pattern == '' then + return nil, "推送消息失败: 错误的pattern类型" + end + if type(data) ~= 'string' or data == '' then + return nil, "推送消息失败: 错误的data类型" + end + if not self.emiter then + self.emiter = _login(self) + end + return publish(self, pattern, data) +end + +-- 启动MQ服务 +function mq:start() + return cf.wait() +end + +-- 关闭MQ服务 +function mq:close () + self.closed = true + if self.emiter then + self.emiter:close() + self.emiter = nil + end + if self.subsribes then + for _, sub in ipairs(self.subsribes) do + sub:close() + end + self.subsribes = nil + end +end + +return mq diff --git a/lualib/MQ/redis.lua b/lualib/MQ/redis.lua new file mode 100644 index 00000000..b25f2949 --- /dev/null +++ b/lualib/MQ/redis.lua @@ -0,0 +1,126 @@ +local class = require "class" +local redis = require "protocol.redis" + +local logging = require "logging" +local Log = logging:new{dump = true, path = 'MQ-redis'} + +local cf = require "cf" +local cf_self = cf.self +local cf_fork = cf.fork +local cf_sleep = cf.sleep +local cf_wait = cf.wait +local cf_wakeup = cf.wakeup + +local ipairs = ipairs +local type = type +local insert = table.insert + +local mq = class('redis-mq') + +function mq:ctor (opt) + self.host = opt.host + self.port = opt.port + self.auth = opt.auth + self.db = opt.db + self.patterns = {} + self.subsribes = {} + self.queue = {} +end + +local function _login (opt) + local times = 1 + while 1 do + local mq = redis:new { + host = opt.host, + port = opt.port, + auth = opt.auth, + db = opt.db, + } + local ok, err = mq:connect() + if ok then + return mq + end + Log:WARN("第"..times.."次连接MQ(redis)失败:"..(err or "未知错误.")) + times = times + 1 + mq:close() + cf_sleep(3) + end +end + +-- 订阅事件循环 +local function subscribe (self, pattern, func) + local mq = _login(self) + self.subsribes[#self.subsribes+1] = mq + return mq:subscribe(pattern, func) +end + +-- 发布事件循环 +local function publish (self, pattern, data) + if #self.queue == 0 then + self.queue[#self.queue + 1] = {pattern = pattern, data = data, co = cf_self()} + cf_fork(function (...) + for _, msg in ipairs(self.queue) do + if not self.closed and self.emiter then + cf_wakeup(msg.co, self.emiter:publish(msg.pattern, msg.data)) + end + end + self.queue = {} + end) + return cf_wait() + end + insert(self.queue, {pattern = pattern, data = data, co = cf_self()}) + return cf_wait() +end + +-- 订阅 +function mq:on (pattern, func) + if type(pattern) ~= 'string' or pattern == '' then + return nil, "订阅消息失败: 错误的pattern类型" + end + if type(func) ~= 'function' then + return nil, "订阅消息失败: 错误的func类型" + end + for _, patt in ipairs(self.patterns) do + if patt == pattern then + return nil, '禁止重复订阅相同的频道' + end + end + self.patterns[#self.patterns + 1] = pattern + return subscribe(self, pattern, func) +end + +-- 发布 +function mq:emit (pattern, data) + if type(pattern) ~= 'string' or pattern == '' then + return nil, "推送消息失败: 错误的pattern类型" + end + if type(data) ~= 'string' or data == '' then + return nil, "推送消息失败: 错误的data类型" + end + if not self.emiter then + self.emiter = _login(self) + end + return publish(self, pattern, data) +end + +-- 启动MQ服务 +function mq:start() + return cf.wait() +end + +-- 关闭MQ服务 +function mq:close () + self.closed = true + if self.emiter then + self.emiter:close() + self.emiter = nil + end + if self.subsribes then + for _, sub in ipairs(self.subsribes) do + sub:close() + end + self.subsribes = nil + end +end + +return mq diff --git a/lualib/MQ/stomp.lua b/lualib/MQ/stomp.lua new file mode 100644 index 00000000..7d76612c --- /dev/null +++ b/lualib/MQ/stomp.lua @@ -0,0 +1,112 @@ +local class = require "class" +local stomp = require "protocol.stomp" + +local logging = require "logging" +local Log = logging:new{dump = true, path = 'MQ-stomp'} + +local cf = require "cf" +local cf_fork = cf.fork +local cf_sleep = cf.sleep + +local ipairs = ipairs +local type = type +local insert = table.insert + +local mq = class('stomp-mq') + +function mq:ctor (opt) + self.host = opt.host + self.port = opt.port + self.vhost = opt.vhost + self.header = opt.header + self.username = opt.username + self.password = opt.password + self.patterns = {} + self.subsribes = {} +end + +local function _login (opt) + local times = 1 + while 1 do + local mq = stomp:new { + host = opt.host, port = opt.port, vhost = opt.vhost, auth = opt.auth, + header = opt.header, username = opt.username, password = opt.password, + } + local ok, err = mq:connect() + if ok then + return mq + end + Log:WARN("第"..times.."次连接MQ(stomp)失败:"..(err or "未知错误.")) + times = times + 1 + mq:close() + cf_sleep(3) + end +end + +-- 订阅事件循环 +local function subscribe (self, pattern, func) + local m = _login(self) + insert(self.subsribes, m) + return m:subscribe(pattern, func) +end + +-- 发布事件循环 +local function publish (self, pattern, data) + if not self.queue then + self.queue = {} + cf_fork(function () + for _, msg in ipairs(self.queue) do + if self.closed or not self.emiter:publish(msg.pattern, msg.data) then + break + end + end + self.queue = nil + end) + end + insert(self.queue, {pattern = pattern, data = data}) + return true +end + +-- 订阅 +function mq:on (pattern, func) + assert((type(pattern) == 'string' and pattern ~= '') and type(func) == 'function', "[STOMP ERROR] : Invalid `pattern`/`func` type.") + for _, patt in ipairs(self.patterns) do + if patt == pattern then + return nil, '[STOMP ERROR] : already subscribe.' + end + end + insert(self.patterns, pattern) + -- self.patterns[#self.patterns + 1] = pattern + return subscribe(self, pattern, func) +end + +-- 发布 +function mq:emit (pattern, data) + assert((type(pattern) == 'string' and pattern ~= '') and (type(data) == 'string' and data ~= ''), "[STOMP ERROR] : Invalid `pattern`/`data` type.") + if not self.emiter then + self.emiter = _login(self) + end + return publish(self, pattern, data) +end + +-- 启动MQ服务 +function mq:start() + return cf.wait() +end + +-- 关闭MQ服务 +function mq:close () + self.closed = true + if self.emiter then + self.emiter:close() + self.emiter = nil + end + if self.subsribes then + for _, sub in ipairs(self.subsribes) do + sub:close() + end + self.subsribes = nil + end +end + +return mq diff --git a/lualib/README.md b/lualib/README.md new file mode 100644 index 00000000..f178368b --- /dev/null +++ b/lualib/README.md @@ -0,0 +1,39 @@ +## 内置库列表 + + * mail库 + + * csv库 + + * SDK库 + + * DB库(MySQL) + + * Cache库(Redis) + + * logging库 + + * httpc库 + + * MQ库 + + * crypt库 + + * webhook库 + + * json库 + + * msgpack库 + + * protobuf库 + + * template库 + + * cf库 + + * admin库 + + * xml库 + + * httpd库 + + * system库 \ No newline at end of file diff --git a/lualib/admin/README.md b/lualib/admin/README.md new file mode 100644 index 00000000..7c74f401 --- /dev/null +++ b/lualib/admin/README.md @@ -0,0 +1,7 @@ +## admin库 + + admin是cf内置的web后台管理模板库, 目的是为使用者快速构建后台基于web的管理系统. + +## 基准 + + 详情清参考[这里](https://candymi.github.io/LuaWeb/wiki/Benchmark.html) diff --git a/lualib/admin/config.lua b/lualib/admin/config.lua new file mode 100644 index 00000000..34b97317 --- /dev/null +++ b/lualib/admin/config.lua @@ -0,0 +1,12 @@ +local config = { + cdn = '/', -- 静态文件前缀地址 + github = 'https://github.com/candymi/core_framework', -- 跳转地址 + cache = false, -- 是否缓存模板 + locale = "ZH-CN", -- 当前语言 + display_lang = true, -- 默认显示语言标签 + locales = require "admin.locales", -- 语言表 + secure = 'cfadmin', -- 生成token的secure + cookie_timeout = 86400 -- Cookie超时时间 +} + +return config diff --git a/lualib/admin/cookie.lua b/lualib/admin/cookie.lua new file mode 100644 index 00000000..2cd7f34d --- /dev/null +++ b/lualib/admin/cookie.lua @@ -0,0 +1,34 @@ +local config = require "admin.config" + +local Cookie = require "httpd.Cookie" +local getCookie = Cookie.getCookie +local setCookie = Cookie.setCookie +local delCookie = Cookie.delCookie + +local os_time = os.time + +local Cookie = {} + +-- 登录页面需要初始化Cookie. +function Cookie.init () + local session = getCookie('CFTOKEN') + if session then + return delCookie("CFTOKEN") + end + local session = getCookie('CFLANG') + if not session then + setCookie("CFLANG", config.locale) + end +end + +-- 设置Cookie +function Cookie.setCookie (name, value) + return setCookie(name, value, config.cookie_timeout + os_time()) +end + +-- 获取Cookie +function Cookie.getCookie (name) + return getCookie(name) +end + +return Cookie diff --git a/lualib/admin/db/README.md b/lualib/admin/db/README.md new file mode 100644 index 00000000..9295f2b8 --- /dev/null +++ b/lualib/admin/db/README.md @@ -0,0 +1,9 @@ +# db + +## view + + dashboard视图的sql + +## user + + user的sql diff --git a/lualib/admin/db/database.sql b/lualib/admin/db/database.sql new file mode 100644 index 00000000..90a70f98 --- /dev/null +++ b/lualib/admin/db/database.sql @@ -0,0 +1,110 @@ +# ************************************************************ +# Sequel Pro SQL dump +# Version 4541 +# +# http://www.sequelpro.com/ +# https://github.com/sequelpro/sequelpro +# +# Host: 127.0.0.1 (MySQL 5.7.25) +# Database: cfadmin +# Generation Time: 2019-05-21 02:32:16 +0000 +# ************************************************************ + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +# ---------------------------- +# Table structure for cfadmin_headers +# ---------------------------- +CREATE TABLE IF NOT EXISTS `cfadmin_headers` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID', + `name` varchar(255) NOT NULL COMMENT '头部名称', + `url` varchar(255) NOT NULL COMMENT '头部URL', + `create_at` int(11) unsigned NOT NULL COMMENT '创建时间', + `update_at` int(11) unsigned NOT NULL COMMENT '修改时间', + `active` tinyint(4) unsigned NOT NULL COMMENT '删除标志', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='顶部菜单表'; + +# ---------------------------- +# Table structure for cfadmin_menus +# ---------------------------- +CREATE TABLE IF NOT EXISTS `cfadmin_menus` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID', + `parent` int(11) unsigned NOT NULL COMMENT '父菜单ID', + `name` varchar(255) NOT NULL COMMENT '菜单名称', + `url` varchar(255) DEFAULT NULL COMMENT '菜单链接', + `icon` char(255) DEFAULT NULL COMMENT '菜单图标', + `create_at` int(11) unsigned NOT NULL COMMENT '创建时间', + `update_at` int(11) unsigned NOT NULL COMMENT '更新时间', + `active` tinyint(4) unsigned NOT NULL COMMENT '删除标志', + PRIMARY KEY (`id`), + KEY `parant_index` (`parent`) USING BTREE COMMENT '父ID索引' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='侧边菜单表'; + +# ---------------------------- +# Table structure for cfadmin_permissions +# ---------------------------- +CREATE TABLE IF NOT EXISTS `cfadmin_permissions` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID', + `role_id` int(11) unsigned NOT NULL COMMENT '所属角色', + `menu_id` int(11) unsigned NOT NULL COMMENT '所属菜单', + `create_at` int(11) unsigned NOT NULL COMMENT '创建时间', + `update_at` int(11) unsigned NOT NULL COMMENT '修改时间', + `active` tinyint(4) unsigned NOT NULL COMMENT '是否启用', + PRIMARY KEY (`id`), + KEY `com_index` (`active`,`role_id`,`menu_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色权限表'; + +# ---------------------------- +# Table structure for cfadmin_roles +# ---------------------------- +CREATE TABLE IF NOT EXISTS `cfadmin_roles` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID', + `name` varchar(255) NOT NULL COMMENT '角色名称', + `is_admin` tinyint(4) unsigned NOT NULL COMMENT '管理员标志', + `create_at` int(11) unsigned NOT NULL COMMENT '创建时间', + `update_at` int(1) unsigned NOT NULL COMMENT '修改时间', + `active` tinyint(4) unsigned NOT NULL COMMENT '删除标志', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色表'; + +# ---------------------------- +# Table structure for cfadmin_tokens +# ---------------------------- +CREATE TABLE IF NOT EXISTS `cfadmin_tokens` ( + `uid` int(11) unsigned NOT NULL COMMENT '用户ID', + `name` varchar(255) NOT NULL COMMENT '用户名称', + `token` varchar(255) NOT NULL COMMENT '用户TOKEN', + `create_at` int(11) unsigned NOT NULL COMMENT '登录时间', + PRIMARY KEY (`uid`) +) ENGINE=MEMORY DEFAULT CHARSET=utf8mb4 COMMENT='用户Token表'; + +# ---------------------------- +# Table structure for cfadmin_users +# ---------------------------- +CREATE TABLE IF NOT EXISTS `cfadmin_users` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID', + `name` varchar(255) NOT NULL COMMENT '用户名', + `username` varchar(255) NOT NULL COMMENT '用户账户', + `password` varchar(255) NOT NULL COMMENT '用户密码', + `email` varchar(255) NOT NULL COMMENT '用户邮箱', + `phone` bigint(11) unsigned NOT NULL COMMENT '用户手机', + `role` int(11) unsigned NOT NULL COMMENT '用户角色', + `create_at` int(11) unsigned NOT NULL COMMENT '创建时间', + `update_at` int(11) unsigned NOT NULL COMMENT '修改时间', + `active` tinyint(4) unsigned NOT NULL COMMENT '删除标志', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; + +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/lualib/admin/db/header.lua b/lualib/admin/db/header.lua new file mode 100644 index 00000000..daced276 --- /dev/null +++ b/lualib/admin/db/header.lua @@ -0,0 +1,49 @@ +local toint = math.tointeger +local os_time = os.time +local fmt = string.format + +local header = {} + +-- header 列表 +function header.header_list (db, opt) + local limit = toint(opt.limit) or 10 + local page = toint(opt.page) or 1 + return db:query(fmt([[SELECT id, name, url, create_at, update_at FROM cfadmin_headers WHERE active = 1 ORDER BY id LIMIT %s, %s]], limit * (page - 1) , limit)) +end + +-- 获取指定header +function header.get_header (db, id) + local ret = db:query(fmt([[ SELECT id, name, url FROM cfadmin_headers WHERE id = '%s' LIMIT 1]], id)) + if not ret or #ret == 0 then + return + end + return ret[1] +end + +-- header 总数 +function header.header_count (db) + return db:query([[SELECT count(id) AS count FROM cfadmin_headers WHERE active = 1]])[1]['count'] +end + +-- 是否存在此header +function header.header_exists (db, id) + local ret = db:query(fmt([[SELECT id FROM cfadmin_headers WHERE id = '%s' AND active = 1 LIMIT 1]], id)) + return ret and #ret > 0 +end + +-- 删除header +function header.header_delete (db, id) + return db:query(fmt([[UPDATE cfadmin_headers SET active = '0', update_at = '%s' WHERE id = '%s' AND active = 1]], os_time(), id)) +end + +-- 增加header +function header.header_add(db, opt) + return db:query(fmt([[INSERT INTO cfadmin_headers(`name`, `url`, `create_at`, `update_at`, `active`) VALUES('%s', '%s', '%s', '%s', 1)]], opt.name, opt.url, os_time(), os_time())) +end + +-- 修改header +function header.header_update (db, opt) + return db:query(fmt([[UPDATE cfadmin_headers SET name = '%s', url = '%s', update_at = '%s' WHERE id = '%s']], opt.name, opt.url, os_time(), opt.id)) +end + +return header diff --git a/lualib/admin/db/init.lua b/lualib/admin/db/init.lua new file mode 100644 index 00000000..9ecba344 --- /dev/null +++ b/lualib/admin/db/init.lua @@ -0,0 +1,43 @@ +local crypt = require "crypt" +local config = require "admin.config" + +local log = require "logging" +local Log = log:new({path = 'admin-db'}) + +local fmt = string.format +local os_time = os.time +-- 作为初始化DB工作, 这个函数(must)只能运行一次. +-- 一般情况下, 大家在设计完成后都会手动简历数据表并导入内容. +-- 此文件仅作为作者调试与使用者开发使用, 不对此文件做任何其它保证. +local create_admin = fmt([[ +INSERT INTO + `cfadmin_users` + (`id`, `name`, `username`, `password`, `email`, `phone`, `role`, `create_at`, `update_at`, `active`) + VALUES + ('1', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '1')]], +'管理员', 'admin', crypt.sha1('admin', true), '869646063@qq.com', '13000000000', '1', os_time(), os_time()) + +local create_admin_role = fmt([[ +INSERT INTO + cfadmin_roles + (`id`, `name`, `is_admin`, `create_at`, `update_at`, `active`) + VALUES + ('1', '管理员', '1', '%s', '%s', '1') +]], os_time(), os_time()) + +return function () + local ret, err + local db = config.db + local now = os_time() + -- 初始化用户 + ret, err = db:query(create_admin) + if not ret then + Log:ERROR(err) + end + -- 初始化角色 + ret, err = db:query(create_admin_role) + if not ret then + Log:ERROR(err) + end + return true, '初始化完成' +end diff --git a/lualib/admin/db/menu.lua b/lualib/admin/db/menu.lua new file mode 100644 index 00000000..f4199a51 --- /dev/null +++ b/lualib/admin/db/menu.lua @@ -0,0 +1,77 @@ +local toint = math.tointeger +local fmt = string.format +local concat = table.concat +local os_time = os.time + +local menu = {} + +-- 菜单列表 +function menu.menu_list (db, opt) + local limit = toint(opt.limit) or 100 + local page = toint(opt.page) or 1 + return db:query(fmt([[SELECT id, parent, name, url, icon, create_at, update_at FROM cfadmin_menus WHERE active = 1 LIMIT %u, %u]], limit * (page - 1), limit)) +end + +-- 菜单名已存在 +function menu.menu_name_exists (db, name) + local ret = db:query(fmt([[SELECT name FROM cfadmin_menus WHERE name = '%s' AND active = 1]], name)) + if ret and #ret > 0 then + return true + end + return false +end + +-- 菜单信息 +function menu.menu_info (db, id) + return db:query(fmt([[SELECT id, name, url, icon FROM cfadmin_menus WHERE id = '%s' AND active = 1]], id))[1] +end + +-- 添加菜单菜单 +function menu.menu_add (db, opt) + local now = os_time() + -- if opt.id > 0 then -- 二级菜单增加后需要保留一级菜单 + -- db:query(fmt([[UPDATE cfadmin_menus SET URL = 'NULL' WHERE id = '%s' AND active = 1]], opt.id)) + -- end + return db:query(fmt([[INSERT INTO cfadmin_menus(`parent`, `name`, `url`, `icon`, `create_at`, `update_at`, `active`) VALUES('%s', '%s', '%s', '%s', '%s', '%s', 1)]], opt.id, opt.name, opt.url, opt.icon, now, now)) +end + +-- 更新菜单 +function menu.menu_update (db, opt) + local ret = db:query(fmt([[SELECT id FROM cfadmin_menus WHERE parent == '%s' AND active = 1]], opt.id)) + return db:query(fmt([[UPDATE cfadmin_menus SET name = '%s', url = '%s', icon = '%s' WHERE id = '%s' AND active = 1]], opt.name, ret and #ret > 0 and "NULL" or opt.url, opt.icon, opt.id)) +end + +-- dtree专用结构 +function menu.menu_tree (db) + local menus = db:query([[SELECT id, parent AS parentId, name AS title FROM cfadmin_menus WHERE active = 1]]) + for _, menu in ipairs(menus) do + menu.checkArr = "0" + end + return menus +end + +-- 删除菜单与下属子菜单 +function menu.menu_delete (db, id) + local id_list = {} + local menus = db:query(fmt([[SELECT id FROM cfadmin_menus WHERE parent = '%s' AND active = 1]], id)) + if menus and #menus > 0 then + for _, menu in ipairs(menus) do + id_list[#id_list+1] = menu.id + end + local subs = db:query(fmt([[SELECT id FROM cfadmin_menus WHERE parent IN (%s) AND active = 1]], concat(id_list, ', '))) + if subs and #subs > 0 then + for _, sub in ipairs(subs) do + id_list[#id_list+1] = sub.id + end + end + end + id_list[#id_list+1] = id + local now = os_time() + local list = concat(id_list, ', ') + -- 删除menu + db:query(fmt([[UPDATE cfadmin_menus SET active = '0', update_at = '%s' WHERE id IN (%s) AND active = 1]], now, list)) + -- 删除role关联permissions + db:query(fmt([[UPDATE cfadmin_permissions SET active = '0', update_at = '%s' WHERE menu_id IN (%s) AND active = 1]], now, list)) +end + +return menu diff --git a/lualib/admin/db/permission.lua b/lualib/admin/db/permission.lua new file mode 100644 index 00000000..318f779c --- /dev/null +++ b/lualib/admin/db/permission.lua @@ -0,0 +1,25 @@ +local fmt = string.format + +local permission = {} + +-- 用户是否有此菜单的权限 +function permission.user_have_menu_permission (db, uid, url) + -- 查询用户Role ID + local uinfo = db:query(fmt([[SELECT id, role AS role_id FROM cfadmin_users WHERE `cfadmin_users`.id = %u AND `cfadmin_users`.active = 1 LIMIT 1]], uid))[1] + if type(uinfo) ~= 'table' then + return false + end + -- 查询菜单Menu ID + local minfo = db:query(fmt([[SELECT * FROM cfadmin_menus WHERE `cfadmin_menus`.url = '%s' AND `cfadmin_menus`.active = 1 LIMIT 1]], url))[1] + if type(minfo) ~= 'table' then + return true + end + -- 检查权限 + local role, err = db:query(fmt([[SELECT * FROM cfadmin_permissions p WHERE p.`active` = 1 AND p.`role_id` = %u AND p.`menu_id` = %u LIMIT 1]], uinfo.role_id, minfo.id)) + if type(role) == 'table' then + return role[1] + end + return role, err +end + +return permission diff --git a/lualib/admin/db/role.lua b/lualib/admin/db/role.lua new file mode 100644 index 00000000..fd2ed56f --- /dev/null +++ b/lualib/admin/db/role.lua @@ -0,0 +1,96 @@ +local toint = math.tointeger +local concat = table.concat +local os_time = os.time +local os_date = os.date +local fmt = string.format + +local role = {} + +-- 角色列表 +function role.role_list (db, opt) + local limit = toint(opt.limit) or 10 + local page = toint(opt.page) or 1 + local roles = db:query(fmt([[SELECT id, name, create_at, update_at FROM cfadmin_roles WHERE active = '1' ORDER BY id LIMIT %s, %s]], limit * (page - 1) , limit)) + if not roles then + return + end + for _, r in ipairs(roles) do + r.create_at = os_date("%F %X", r.create_at) + r.update_at = os_date("%F %X", r.update_at) + end + return roles +end + +-- 计算角色数量 +function role.role_count (db) + return db:query([[SELECT count(id) AS count FROM cfadmin_roles WHERE active = '1']])[1]['count'] +end + +-- 角色对应权限 +function role.role_permissions (db, id) + return db:query(fmt([[SELECT role_id, menu_id FROM cfadmin_permissions WHERE role_id = '%s' AND active = '1']], id)) +end + +-- 角色名已存在 +function role.role_name_exists (db, name) + local ret = db:query(fmt([[SELECT id, name FROM cfadmin_roles WHERE name = '%s' AND active = '1' LIMIT 1]], name)) + if ret and #ret > 0 then + return ret[1] + end + return false +end + +-- 角色id已存在 +function role.role_id_exists (db, id) + local ret = db:query(fmt([[SELECT id, name FROM cfadmin_roles WHERE id = '%s' AND active = '1' LIMIT 1]], id)) + if ret and #ret > 0 then + return ret[1] + end + return false +end + +-- 添加角色 +function role.role_add (db, opt) + local now = os_time() + db:query(fmt([[INSERT INTO cfadmin_roles(`name`, `is_admin`, `create_at`, `update_at`, `active`) VALUES('%s', '0', '%s', '%s', '1')]], opt.name, now, now)) + if opt.permissions then + local id = db:query(fmt([[SELECT id FROM cfadmin_roles WHERE name = '%s' AND active = '1' LIMIT 1]], opt.name))[1]['id'] + local tab = {} + local SQL = [[INSERT INTO cfadmin_permissions(`role_id`, `menu_id`, `create_at`, `update_at`, `active`) VALUES]] + for _, permission in ipairs(opt.permissions) do + tab[#tab+1] = '('..concat({id, permission.menu_id, now, now, 1}, ', ')..')' + end + db:query(SQL..concat(tab, ', ')) + end +end + +-- 删除角色关联数据 +function role.role_delete (db, id) + local now = os_time() + -- 删除角色 + local ok = db:query(fmt([[UPDATE cfadmin_roles SET active = '0', update_at = '%s' WHERE id = '%s' AND active = '1']], now, id)) + if not ok then + return + end + -- 删除角色对应的权限 + local ok = db:query(fmt([[UPDATE cfadmin_permissions SET active = '0', update_at = '%s' WHERE role_id = '%s' AND active = '1']], now, id)) + if not ok then + return + end + return true +end + +-- 更新role相关数据 +function role.role_update(db, opt) + local now = os_time() + db:query(fmt([[UPDATE cfadmin_roles SET name = '%s', update_at = '%s' where id = '%s' AND active = '1']], opt.name, now, opt.id)) + db:query(fmt([[UPDATE cfadmin_permissions SET active = '0', update_at = '%s' WHERE role_id = '%s']], now, opt.id)) + local tab = {} + local SQL = [[INSERT INTO cfadmin_permissions(`role_id`, `menu_id`, `create_at`, `update_at`, `active`) VALUES]] + for _, permission in ipairs(opt.permissions) do + tab[#tab+1] = '('..concat({opt.id, permission.menu_id, now, now, 1}, ', ')..')' + end + db:query(SQL..concat(tab, ', ')) +end + +return role diff --git a/lualib/admin/db/token.lua b/lualib/admin/db/token.lua new file mode 100644 index 00000000..6f9e204d --- /dev/null +++ b/lualib/admin/db/token.lua @@ -0,0 +1,68 @@ + +local fmt = string.format +local os_time = os.time + +local token = {} + +-- 写入Token +function token.token_add (db, uid, name, token) + return db:query(fmt( + [[ + INSERT INTO + `cfadmin_tokens`(`uid`, `name`, `token`, `create_at`) + VALUES + ('%s', '%s', '%s', '%s') + ON DUPLICATE KEY UPDATE `token` = '%s', `name` = '%s', `create_at` = '%s' + ]], + uid, name, token, os_time(), token, name, os_time())) +end + +-- 删除Token +function token.token_delete (db, id, tk) + return db:query(fmt([[DELETE FROM cfadmin_tokens WHERE uid = '%s' or token = '%s' LIMIT 1]], id, tk)) +end + +-- Token 是否存在 +function token.token_exists (db, token) + local ret = db:query(fmt([[SELECT uid, name, token FROM cfadmin_tokens WHERE token = '%s']], token:gsub("['\\]", ""))) + if not ret or #ret == 0 then + return + end + return ret[1] +end + +-- 根据token查用户信息 +function token.token_to_userinfo (db, token) + local tokens, err = db:query(fmt([[ + SELECT + `cfadmin_users`.id, + `cfadmin_users`.name, + `cfadmin_users`.username, + `cfadmin_users`.password, + `cfadmin_tokens`.token, + `cfadmin_users`.role, + `cfadmin_users`.name AS role_name, + `cfadmin_roles`.is_admin, + `cfadmin_users`.email, + `cfadmin_users`.phone, + `cfadmin_users`.create_at, + `cfadmin_users`.update_at + FROM + cfadmin_users, cfadmin_tokens, cfadmin_roles + WHERE + `cfadmin_tokens`.uid = `cfadmin_users`.id AND + `cfadmin_roles`.id = `cfadmin_users`.role AND + `cfadmin_tokens`.token = '%s' + LIMIT 1]], token:gsub("['\\]", ""))) + if type(tokens) ~= 'table'then + return nil, err + end + return tokens[1] +end + +-- 删除其他用户token信息 +function token.flush_all(db, uid) + return db:query(fmt([[DELETE FROM cfadmin_tokens WHERE uid != %u]], uid)) +end + +return token diff --git a/lualib/admin/db/user.lua b/lualib/admin/db/user.lua new file mode 100644 index 00000000..1fde7b03 --- /dev/null +++ b/lualib/admin/db/user.lua @@ -0,0 +1,156 @@ +local toint = math.tointeger +local tostring = tostring +local os_time = os.time +local fmt = string.format + +local user = {} + +-- 用户列表 +function user.user_list (db, opt) + local limit = toint(opt.limit) or 10 + local page = toint(opt.page) or 1 + return db:query(fmt([[ + SELECT + `cfadmin_users`.id, + `cfadmin_users`.name, + `cfadmin_users`.username, + `cfadmin_roles`.name AS role_name, + `cfadmin_users`.email, + `cfadmin_users`.phone, + `cfadmin_users`.create_at, + `cfadmin_users`.update_at, + `cfadmin_users`.active + FROM cfadmin_users, cfadmin_roles + WHERE + `cfadmin_roles`.id = `cfadmin_users`.role AND `cfadmin_users`.active = 1 + LIMIT %s, %s + ]], limit * (page - 1), limit)) +end + +-- 模糊查找用户列表 +function user.find_user (db, opt) + local limit = toint(opt.limit) or 10 + local page = toint(opt.page) or 1 + local condition + if opt.condition == 'id' or opt.condition == 'email' or opt.condition == 'phone' then + condition = fmt("`cfadmin_users`.`%s` = '%s'", opt.condition, opt.value) + else + condition = fmt("`cfadmin_users`.`%s` LIKE '%%%s%%'", opt.condition, opt.value) + end + local counts = db:query(fmt([[SELECT COUNT(id) as count FROM cfadmin_users WHERE active = 1 AND %s ]], condition)) + if not counts or not counts[1] then + return + end + local data_sql = fmt([[ + SELECT + `cfadmin_users`.id, + `cfadmin_users`.name, + `cfadmin_users`.username, + `cfadmin_roles`.name AS role_name, + `cfadmin_users`.email, + `cfadmin_users`.phone, + `cfadmin_users`.create_at, + `cfadmin_users`.update_at, + `cfadmin_users`.active + FROM cfadmin_users, cfadmin_roles + WHERE + `cfadmin_roles`.id = `cfadmin_users`.role AND `cfadmin_users`.active = 1 AND %s + ORDER BY `cfadmin_users`.id LIMIT %s, %s + ]], condition, limit * (page - 1), limit) + return db:query(data_sql), counts[1]['count'] +end + +-- 用户总数 +function user.user_count (db) + return db:query([[SELECT count(id) AS count FROM cfadmin_users WHERE active = '1']])[1]['count'] +end + +-- 用户是否存在 +function user.user_exists (db, username, uid) + local condition + if username then + condition = fmt([[`cfadmin_users`.username = '%s']], username:gsub("['\\]", "")) + elseif uid then + condition = fmt([[`cfadmin_users`.id = '%s']], tostring(uid):gsub("['\\]", "")) + else + return + end + local user, err = db:query(fmt([[ + SELECT + `cfadmin_users`.id, + `cfadmin_users`.name, + `cfadmin_users`.username, + `cfadmin_users`.password + FROM cfadmin_users + WHERE + `cfadmin_users`.active = '1' AND %s + LIMIT 1]], condition)) + if not user then + return nil, err + end + return user[1] +end + +-- 用户名或者登录名是否存在 +function user.user_name_or_username_exists (db, name, username) + local ret = db:query(fmt([[SELECT name, username FROM cfadmin_users WHERE active = '1' AND (name = '%s' OR username = '%s')]], name, username)) + if ret and #ret > 0 then + return true + end + return false +end + +-- 用户信息 +function user.user_info (db, uid) + local ret = db:query(fmt([[ + SELECT + `cfadmin_users`.id, + `cfadmin_users`.name, + `cfadmin_users`.username, + `cfadmin_users`.password, + `cfadmin_roles`.name AS role_name, + `cfadmin_roles`.is_admin, + `cfadmin_users`.role, + `cfadmin_users`.phone, + `cfadmin_users`.email + FROM + cfadmin_users, cfadmin_roles + WHERE + `cfadmin_users`.role = `cfadmin_roles`.id AND `cfadmin_users`.active = '1' AND `cfadmin_users`.id = '%s' + LIMIT 1]], uid)) + return ret[1] +end + +-- 添加用户 +function user.user_add (db, opt) + local now = os_time() + return db:query(fmt([[ + INSERT INTO cfadmin_users(`name`, `username`, `password`, `role`, `email`, `phone`, `create_at`, `update_at`, `active`) + VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '1') + ]], opt.name, opt.username, opt.password, opt.role, opt.email, opt.phone, now, now)) +end + +-- 删除用户 +function user.user_delete (db, uid) + return db:query(fmt([[UPDATE cfadmin_users SET active = '0', update_at = '%s' WHERE id = '%s']], os_time(), uid)) +end + +-- 更新用户信息 1 +function user.user_update (db, opt) + return db:query(fmt([[ + UPDATE cfadmin_users + SET name = '%s', username = '%s', password = '%s', role = '%s', email = '%s', phone = '%s', update_at = '%s' WHERE id = '%s' + ]], opt.name, opt.username, opt.password, opt.role, opt.email, opt.phone, os_time(), opt.id)) +end + +-- 更新用户密码 +function user.user_update_password (db, opt) + return db:query(fmt([[UPDATE cfadmin_users SET password = '%s', update_at = '%s' WHERE id = '%s' AND active = '1']], opt.password, os_time(), opt.id)) +end + +-- 更新用户信息 2 +function user.user_update_info (db, opt) + return db:query(fmt([[UPDATE cfadmin_users SET name = '%s', phone = '%s', email = '%s', update_at = '%s' WHERE id = '%s' AND active = '1']], opt.name, opt.phone, opt.email, os_time(), opt.id)) +end + +return user diff --git a/lualib/admin/db/view.lua b/lualib/admin/db/view.lua new file mode 100644 index 00000000..34a6e867 --- /dev/null +++ b/lualib/admin/db/view.lua @@ -0,0 +1,13 @@ +local view = {} + +-- 获取顶部栏 +function view.get_headers (db) + return db:query([[SELECT id, name, url FROM cfadmin_headers WHERE active = 1 ORDER BY `id`]]) +end + +-- 获取菜单栏 +function view.get_menus (db) + return db:query([[SELECT id, parent, name, url, icon, create_at, update_at FROM cfadmin_menus WHERE active = 1 ORDER BY `id`]]) +end + +return view diff --git a/lualib/admin/html/404.html b/lualib/admin/html/404.html new file mode 100644 index 00000000..60bca51f --- /dev/null +++ b/lualib/admin/html/404.html @@ -0,0 +1,29 @@ + + + + + {* locale['error.404.title'] *} + + + + + + + + +
+
+
+

+

{* locale['error.404.message'] *}

+

3秒后将自动跳转

+
+
+
+ + + diff --git a/lualib/admin/html/dashboard/action.html b/lualib/admin/html/dashboard/action.html new file mode 100644 index 00000000..c64496ee --- /dev/null +++ b/lualib/admin/html/dashboard/action.html @@ -0,0 +1 @@ + diff --git a/lualib/admin/html/dashboard/aside.html b/lualib/admin/html/dashboard/aside.html new file mode 100644 index 00000000..6bd0df37 --- /dev/null +++ b/lualib/admin/html/dashboard/aside.html @@ -0,0 +1,88 @@ + +
+
+ +
+
diff --git a/lualib/admin/html/dashboard/base.html b/lualib/admin/html/dashboard/base.html new file mode 100644 index 00000000..3add00c9 --- /dev/null +++ b/lualib/admin/html/dashboard/base.html @@ -0,0 +1,12 @@ + + + + {(lualib/admin/html/dashboard/head.html)} + + + {(lualib/admin/html/dashboard/header.html)} + {(lualib/admin/html/dashboard/aside.html)} + {(lualib/admin/html/dashboard/content.html)} + {(lualib/admin/html/dashboard/action.html)} + + diff --git a/lualib/admin/html/dashboard/content.html b/lualib/admin/html/dashboard/content.html new file mode 100644 index 00000000..8bac9bba --- /dev/null +++ b/lualib/admin/html/dashboard/content.html @@ -0,0 +1,20 @@ +
+
+
    +
  • {{ locale['dashboard.crumbs.home'] }}
  • +
+
+
+
{{ locale['dashboard.crumbs.close_this'] }}
+
{{ locale['dashboard.crumbs.close_other'] }}
+
{{ locale['dashboard.crumbs.close_all'] }}
+
+
+
+
+
+
+
+
+
+ diff --git a/lualib/admin/html/dashboard/head.html b/lualib/admin/html/dashboard/head.html new file mode 100644 index 00000000..22ece642 --- /dev/null +++ b/lualib/admin/html/dashboard/head.html @@ -0,0 +1,24 @@ + +{* locale['dashboard.header.title' ] *} + + + + + + + + + + + + + + + + + diff --git a/lualib/admin/html/dashboard/header.html b/lualib/admin/html/dashboard/header.html new file mode 100644 index 00000000..6c89b2a0 --- /dev/null +++ b/lualib/admin/html/dashboard/header.html @@ -0,0 +1,46 @@ + +
+ + + + +
+ diff --git a/lualib/admin/html/deny.html b/lualib/admin/html/deny.html new file mode 100644 index 00000000..0b095c3c --- /dev/null +++ b/lualib/admin/html/deny.html @@ -0,0 +1,19 @@ + + + + Access Deny + + + + + + + diff --git a/lualib/admin/html/login/action.html b/lualib/admin/html/login/action.html new file mode 100644 index 00000000..34e1a1c0 --- /dev/null +++ b/lualib/admin/html/login/action.html @@ -0,0 +1,58 @@ + + + \ No newline at end of file diff --git a/lualib/admin/html/login/base.html b/lualib/admin/html/login/base.html new file mode 100644 index 00000000..805ad78b --- /dev/null +++ b/lualib/admin/html/login/base.html @@ -0,0 +1,10 @@ + + + {(lualib/admin/html/login/head.html)} + +
+ {(lualib/admin/html/login/content.html)} + {(lualib/admin/html/login//action.html)} +
+ + diff --git a/lualib/admin/html/login/content.html b/lualib/admin/html/login/content.html new file mode 100644 index 00000000..2dbae1ae --- /dev/null +++ b/lualib/admin/html/login/content.html @@ -0,0 +1,18 @@ + diff --git a/lualib/admin/html/login/head.html b/lualib/admin/html/login/head.html new file mode 100644 index 00000000..5d93bd2c --- /dev/null +++ b/lualib/admin/html/login/head.html @@ -0,0 +1,16 @@ + + + {{ locale['login.form.title'] }} + + + + + + + + + + diff --git a/lualib/admin/html/profile/base.html b/lualib/admin/html/profile/base.html new file mode 100644 index 00000000..ca04b8e0 --- /dev/null +++ b/lualib/admin/html/profile/base.html @@ -0,0 +1,173 @@ + + + + + profile + + + + + + + + + + + +
+
+
{*locale['dashboard.header.profile.update_userinfo']*}
+
+
+
+ +
+ +
+
+ *{*locale['dashboard.header.profile.form.name.notice']*} +
+
+
+ +
+ +
+
+ *{*locale['dashboard.header.profile.form.username.notice']*} +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+ + +
+
+
+
+
+
{*locale['dashboard.header.profile.update_password']*}
+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+ + +
+
+
+
+ + + diff --git a/lualib/admin/html/redirect.html b/lualib/admin/html/redirect.html new file mode 100644 index 00000000..16696a4d --- /dev/null +++ b/lualib/admin/html/redirect.html @@ -0,0 +1,5 @@ + + + + + diff --git a/lualib/admin/html/system/header/header-add.html b/lualib/admin/html/system/header/header-add.html new file mode 100644 index 00000000..0a226a57 --- /dev/null +++ b/lualib/admin/html/system/header/header-add.html @@ -0,0 +1,97 @@ + + + + + + header-add + + + + + + + + + + + +
+
+
+
+ +
+ +
+
+ *{*locale['dashboard.menu.header_manage.form.header_add.name.notice']*} +
+
+
+ +
+ +
+
+ *{*locale['dashboard.menu.header_manage.form.header_add.url.notice']*} +
+
+
+
+ +
+
+
+
+ +
+
+
+ + +
+
+
+
+ + + + diff --git a/lualib/admin/html/system/header/header-edit.html b/lualib/admin/html/system/header/header-edit.html new file mode 100644 index 00000000..2a58aaaf --- /dev/null +++ b/lualib/admin/html/system/header/header-edit.html @@ -0,0 +1,102 @@ + + + + + + header-edit + + + + + + + + + + + +
+
+
+
+ +
+ +
+
+ *{*locale['dashboard.menu.header_manage.form.header_edit.name.notice']*} +
+
+
+ +
+ +
+
+ *{*locale['dashboard.menu.header_manage.form.header_edit.url.notice']*} +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+ + +
+
+
+
+ + + + diff --git a/lualib/admin/html/system/header/header.html b/lualib/admin/html/system/header/header.html new file mode 100644 index 00000000..5928c417 --- /dev/null +++ b/lualib/admin/html/system/header/header.html @@ -0,0 +1,88 @@ + + + + + {* locale['dashboard.header_manage.title'] *} + + + + + + + + + + + +
+ + + + + + + + + + + +
#{* locale['dashboard.menu.header_manage.table.name'] *}{* locale['dashboard.menu.header_manage.table.url'] *}{* locale['dashboard.menu.header_manage.table.create_time'] *}{* locale['dashboard.menu.header_manage.table.update_time'] *}{* locale['dashboard.menu.header_manage.table.options'] *}
+ + + {-raw-} + + + {-raw-} +
+ + + diff --git a/lualib/admin/html/system/menu/menu-add.html b/lualib/admin/html/system/menu/menu-add.html new file mode 100644 index 00000000..7f760f7e --- /dev/null +++ b/lualib/admin/html/system/menu/menu-add.html @@ -0,0 +1,116 @@ + + + + + + user-add + + + + + + + + + + + +
+
+
+
+ +
+ +
+
+ *{*locale['dashboard.menu.menu_manage.menu_add.form.name.notice']*} +
+
+
+ +
+ +
+
+ *{*locale['dashboard.menu.menu_manage.menu_add.form.url.notice']*} +
+
+
+ +
+ +
+
+ *{*locale['dashboard.menu.menu_manage.menu_add.form.icon.notice']*} + +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+ + +
+
+
+
+ + + + diff --git a/lualib/admin/html/system/menu/menu-edit.html b/lualib/admin/html/system/menu/menu-edit.html new file mode 100644 index 00000000..7d11e2da --- /dev/null +++ b/lualib/admin/html/system/menu/menu-edit.html @@ -0,0 +1,120 @@ + + + + + + user-add + + + + + + + + + + + +
+
+
+
+ +
+ +
+
+ *{*locale['dashboard.menu.menu_manage.menu_edit.form.name.notice']*} +
+
+
+ +
+ +
+
+ *{*locale['dashboard.menu.menu_manage.menu_edit.form.url.notice']*} +
+
+
+ +
+ +
+
+ *{*locale['dashboard.menu.menu_manage.menu_edit.form.icon.notice']*} + +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+ + +
+
+
+
+ + + + diff --git a/lualib/admin/html/system/menu/menu.html b/lualib/admin/html/system/menu/menu.html new file mode 100644 index 00000000..f191e4fa --- /dev/null +++ b/lualib/admin/html/system/menu/menu.html @@ -0,0 +1,166 @@ + + + + + + menu + + + + + + + + + + + + + +
+
+
+ + +
+
+ + + + + + + + + + + + {% if menus and type(menus) == 'table' then%} + {% for index, menu in ipairs(menus) do %} + + + + + + + + {% if type(menu.list) == 'table' then%} + {% for index, sub in ipairs(menu.list) do%} + + + + + + + + {% if type(sub.list) == 'table' then %} + {% for index, it in ipairs(sub.list) do %} + + + + + + + + {% end %} + {% end%} + {% end %} + {% end %} + {% end %} + {% end %} + +
{*locale['dashboard.menu.menu_manage.table.id']*}{*locale['dashboard.menu.menu_manage.table.icon']*}{*locale['dashboard.menu.menu_manage.table.name']*}{*locale['dashboard.menu.menu_manage.table.url']*}{*locale['dashboard.menu.menu_manage.table.options']*}
{*menu.id*}{*menu.icon*} + {% if type(menu.list) == 'table' then %} + + {% end %}{{locale[menu.name] or menu.name}} + {{ menu.url or '-' }} + + + +
{*sub.id*}{*sub.icon*} + |-------> + {% if type(sub.list) == 'table' then %} + + {% end %} + {{locale[sub.name] or sub.name}} + {{ sub.url or '-' }} + + + +
{*it.id*}{*it.icon*} +         +         + |-------> {{locale[it.name] or it.name}} + {*it.url*} + + +
+
+
+
+ + + diff --git a/lualib/admin/html/system/role/role-add.html b/lualib/admin/html/system/role/role-add.html new file mode 100644 index 00000000..6f68b934 --- /dev/null +++ b/lualib/admin/html/system/role/role-add.html @@ -0,0 +1,126 @@ + + + + + + role-add + + + + + + + + + + + + + +
+
+
+
+ +
+ +
+
+ *用户的角色名 +
+
+
+ +
+
    +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    + + + + diff --git a/lualib/admin/html/system/role/role-edit.html b/lualib/admin/html/system/role/role-edit.html new file mode 100644 index 00000000..1ec40de3 --- /dev/null +++ b/lualib/admin/html/system/role/role-edit.html @@ -0,0 +1,133 @@ + + + + + + role-edit + + + + + + + + + + + + + +
    +
    +
    +
    + +
    + +
    +
    + *用户的角色名 +
    +
    +
    + +
    +
      +
      +
      +
      +
      + +
      +
      +
      +
      + +
      +
      +
      + + +
      +
      +
      +
      + + + + diff --git a/lualib/admin/html/system/role/role.html b/lualib/admin/html/system/role/role.html new file mode 100644 index 00000000..2ce2899a --- /dev/null +++ b/lualib/admin/html/system/role/role.html @@ -0,0 +1,83 @@ + + + + + role + + + + + + + + + + + +
      + + + + + + + + + + +
      #{* locale['dashboard.menu.role_manage.table.name'] *}{* locale['dashboard.menu.role_manage.table.create_time'] *}{* locale['dashboard.menu.role_manage.table.update_time'] *}{* locale['dashboard.menu.role_manage.table.options'] *}
      + + +
      + + + diff --git a/lualib/admin/html/system/user/user-add.html b/lualib/admin/html/system/user/user-add.html new file mode 100644 index 00000000..121f133b --- /dev/null +++ b/lualib/admin/html/system/user/user-add.html @@ -0,0 +1,139 @@ + + + + + + user-add + + + + + + + + + + + +
      +
      +
      +
      + +
      + +
      +
      + *{*locale['dashboard.menu.user_manage.form.user_add.name.notice']*} +
      +
      +
      + +
      + +
      +
      + *{*locale['dashboard.menu.user_manage.form.user_add.username.notice']*} +
      +
      +
      + +
      + +
      +
      + *{*locale['dashboard.menu.user_manage.form.user_add.password.notice']*} +
      +
      +
      + +
      + +
      +
      +
      + +
      + +
      +
      +
      + +
      + +
      +
      +
      +
      + +
      +
      +
      +
      + +
      +
      +
      + + +
      +
      +
      +
      + + + + diff --git a/lualib/admin/html/system/user/user-edit.html b/lualib/admin/html/system/user/user-edit.html new file mode 100644 index 00000000..08ae1f57 --- /dev/null +++ b/lualib/admin/html/system/user/user-edit.html @@ -0,0 +1,148 @@ + + + + + + user-add + + + + + + + + + + + +
      +
      +
      +
      + +
      + +
      +
      + *{*locale['dashboard.menu.user_manage.form.user_edit.name.notice']*} +
      +
      +
      + +
      + +
      +
      + *{*locale['dashboard.menu.user_manage.form.user_edit.username.notice']*} +
      +
      +
      + +
      + +
      +
      + *{*locale['dashboard.menu.user_manage.form.user_edit.password.notice']*} +
      +
      +
      + +
      + +
      +
      +
      + +
      + +
      +
      +
      + +
      + +
      +
      +
      +
      + +
      +
      +
      +
      + +
      +
      +
      +
      + +
      +
      +
      + + +
      +
      +
      +
      + + + + diff --git a/lualib/admin/html/system/user/user.html b/lualib/admin/html/system/user/user.html new file mode 100644 index 00000000..df1b141c --- /dev/null +++ b/lualib/admin/html/system/user/user.html @@ -0,0 +1,144 @@ + + + + + {* locale['dashboard.menu.user_manage.title'] *} + + + + + + + + + + + +
      + + + + + + + + + + + + + + +
      #{* locale['dashboard.menu.user_manage.table.name'] *}{* locale['dashboard.menu.user_manage.table.username'] *}{* locale['dashboard.menu.user_manage.table.permission_name'] *}{* locale['dashboard.menu.user_manage.table.email'] *}{* locale['dashboard.menu.user_manage.table.phone'] *}{* locale['dashboard.menu.user_manage.table.create_time'] *}{* locale['dashboard.menu.user_manage.table.update_time'] *}{* locale['dashboard.menu.user_manage.table.options'] *}
      + + +
      + {-raw-} + + + + {-raw-} + + + diff --git a/lualib/admin/http/dashboard.lua b/lualib/admin/http/dashboard.lua new file mode 100644 index 00000000..a17f6280 --- /dev/null +++ b/lualib/admin/http/dashboard.lua @@ -0,0 +1,143 @@ +local template = require "template" +local utils = require "admin.utils" +local Cookie = require "admin.cookie" +local config = require "admin.config" +local locales = require "admin.locales" +local role = require "admin.db.role" +local view = require "admin.db.view" +local user = require "admin.db.user" +local user_token = require "admin.db.token" + +local type = type +local ipairs = ipairs + +local get_locale = utils.get_locale +local user_have_permission = utils.user_have_permission + +local template_path = 'lualib/admin/html/dashboard/base.html' + +local function verify_permission (content, db) + local args = content.args + if type(args) == 'table' then + local lang, token, logout = args.lang, args.token, args.logout + if lang then -- 切换语言 + Cookie.setCookie('CFLANG', config.locales[lang] and lang or config.locale) + return false, config.dashboard + end + if logout then -- 注销登录 + local tk = Cookie.getCookie('CFTOKEN') + if tk then -- 注销的时候有token必须清除 + user_token.token_delete(db, 0, tk) + end + return false, config.login_render + end + if token then -- 登录授权 + local exists = user_token.token_exists(db, token) + if not exists then -- Token不存在需要重新登录 + return false, config.login_render + end + Cookie.setCookie("CFTOKEN", token) + return false, config.dashboard + end + end + local token = Cookie.getCookie('CFTOKEN') + if not token then -- 未授权的访问 + return false, config.login_render + end + local info = user_token.token_to_userinfo(db, token) + if not info then + return false, config.login_render + end + info.is_admin = info.is_admin == 1 + info.roles = role.role_permissions(db, info.role) + return true, info +end + +-- 构建根节点 +local function root_tree (list, role) + local root = {} + for _, item in ipairs(list) do + -- 判断是否超级管理员或用户所在权限组有当前页面权限 + if (role.is_admin or user_have_permission(role.roles, item.id)) and item.parent == 0 then + root[#root+1] = {id = item.id, item.name, item.url ~= null and item.url or nil, item.icon ~= null and item.icon or nil} + end + end + return root -- 返回树的根结构 +end + +-- 构建叶(子)节点 +local function sub_tree (root, list, role) + local tab = {} + for index, item in ipairs(list) do + -- 判断是否超级管理员或用户所在权限组有当前页面权限 + if (role.is_admin or user_have_permission(role.roles, item.id)) and root.id == item.parent then + tab[#tab+1] = {id = item.id, item.name, item.url ~= null and item.url or nil, item.icon ~= null and item.icon or nil} + end + end + return #tab > 0 and tab or nil -- 返回树的叶(子)结构 +end + +-- menu 生成 +local function get_menus (db, role) + local list = view.get_menus(db) + if not list then + return {} + end + local roots = root_tree(list, role) + if #roots == 0 then + return roots + end + for _, root in ipairs(roots) do + local subs = sub_tree(root, list, role) + if subs then + for _, sub in ipairs(subs) do + sub[2] = sub_tree(sub, list, role) or sub[2] + end + end + root[2] = subs or root[2] + end + return roots +end + +-- header 生成 +local function get_headers (db) + local tab = {} + for _, header in ipairs(view.get_headers(db)) do + tab[#tab+1] = {header.name, header.url} + end + return tab +end + +local dashboard = {} + +-- 渲染登录页模板 +function dashboard.render(content) + local db = config.db + local ok, user = verify_permission(content, db) + if not ok then + return utils.redirect(user) + end + if not config.cache then + template.cache = {} + end + return template.compile(template_path){ + cdn = config.cdn, + home = config.home, + logo = config.dashboard, + menus = get_menus(db, {is_admin = user.is_admin, roles = user.roles}), + headers = get_headers(db), + token = user.token, + username = user.name, + is_admin = user.is_admin, + logout = config.dashboard .."?logout=true", + user = config.system_user_render, + menu = config.system_menu_render, + header = config.system_header_render, + role = config.system_role_render, + display_lang = config.display_lang, + profile = config.profile_render, + locale = get_locale(Cookie.getCookie("CFLANG")) + } +end + +return dashboard diff --git a/lualib/admin/http/login.lua b/lualib/admin/http/login.lua new file mode 100644 index 00000000..4b88e760 --- /dev/null +++ b/lualib/admin/http/login.lua @@ -0,0 +1,78 @@ +local template = require "template" +local utils = require "admin.utils" +local config = require "admin.config" +local Cookie = require "admin.cookie" +local user = require "admin.db.user" +local user_token = require "admin.db.token" +local token = require "admin.token" +local crypt = require "crypt" + +local json = require "json" +local json_decode = json.decode +local json_encode = json.encode + +local type = type +local byte = string.byte +local get_locale = utils.get_locale + +local template_path = 'lualib/admin/html/login/base.html' + +local function random_sign(randomkey, value) + local sign = {} + randomkey = randomkey:reverse() + for i = 1, #value do + sign[i] = (byte(value, i) ~ byte(randomkey, i <= #randomkey and i or (i % randomkey) + 1)) & 0xff + sign[i] = string.format("%02x", sign[i]) + end + return table.concat(sign) +end + +local login = {} + +-- 登录页面逻辑 +function login.render(content) + if not config.cache then + template.cache = {} + end + Cookie.init() -- 初始化所有Cookie + return template.compile(template_path){ + cdn = config.cdn, + login_api = config.login_api, + randomkey = crypt.randomkey_ex(16, true):reverse(), + locale = get_locale(Cookie.getCookie("CFLANG")) + } +end + +-- 登录接口逻辑 +function login.response (content) + local db = config.db + local args = content.args or (content.json and json_decode(content.body)) + -- 验证参数是否存在 + if type(args) ~= 'table' or type(args.username) ~= 'string' or type(args.password) ~= 'string' or type(args.randomkey) ~= 'string' then + return json_encode({code = 401, msg = "1. 无效的请求参数"}) + end + local username, password, randomkey, verify_code = args.username, args.password, args.randomkey, args.verify_code + if username == '' or password == '' or randomkey == '' or type(verify_code) ~= 'string' then + return json_encode({code = 401, msg = "2. 无效的请求参数"}) + end + -- 验证随机数行为 + if random_sign(randomkey, username .. password) ~= verify_code then + return json_encode({code = 401, msg = "3. 用户密码验证失败"}) + end + -- 获取登录信息 + local user_info = user.user_exists(db, username) + if not user_info or username ~= user_info.username or crypt.sha1(password, true) ~= user_info.password then + return json_encode({code = 403, msg = "4. 用户不存在或者密码错误"}) + end + local uid, name = user_info.id, user_info.name + -- 生成token + local TOKEN = token.generate(uid) + -- 将Token写入到数据库内 + local ok, err = user_token.token_add(db, uid, name, TOKEN) + if not ok then + return json_encode({code = 500, msg = err}) + end + return json_encode({code = 0, msg = "登录成功", token = TOKEN, url = config.dashboard }) +end + +return login diff --git a/lualib/admin/http/profile.lua b/lualib/admin/http/profile.lua new file mode 100644 index 00000000..9947033e --- /dev/null +++ b/lualib/admin/http/profile.lua @@ -0,0 +1,155 @@ +local template = require "template" +local utils = require "admin.utils" +local Cookie = require "admin.cookie" +local config = require "admin.config" +local locales = require "admin.locales" +local role = require "admin.db.role" +local user = require "admin.db.user" +local user_token = require "admin.db.token" +local view = require "admin.db.view" +local crypt = require "crypt" + +local type = type +local ipairs = ipairs +local toint = math.tointeger + +local json = require "json" +local json_decode = json.decode +local json_encode = json.encode + +local url = require "url" +local url_encode = url.encode +local url_decode = url.decode + +local get_locale = utils.get_locale + +local template_path = 'lualib/admin/html/profile/base.html' + +-- 管理页面验权 +local function verify_permission (content, db) + local args = content.args + -- 切换语言 + if type(args) == 'table' and args.lang then + local lang = args.lang + local locale = config.locales[lang] + if locale then + Cookie.setCookie('CFLANG', lang) + else + Cookie.setCookie('CFLANG', config.locale) + end + return false, config.dashboard + end + -- 登录注入Cookie + if type(args) == 'table' and args.token then + local token = args.token + if not token then -- 对一些错误传参直接重定向到登录页 + return false, config.login_render + end + -- 开启验证Token + local exists = user_token.token_exists(db, token) + if not exists then -- Token不存在需要重新登录 + return false, config.login_render + end + -- 如果是第一次带上Token访问后, admin会给予一个重写URL后的url. + -- 同时admin会在这里将Token写入到Cookie中去, 用户无需感知. + Cookie.setCookie("CFTOKEN", token) + return false, utils.get_path(content) + end + local token = Cookie.getCookie('CFTOKEN') + if not token then -- 未授权的访问 + return false, config.login_render + end + local exists = user_token.token_exists(db, token) + if not exists then -- Token不存在需要重新登录 + return false, config.login_render + end + local info = user.user_info(db, exists.uid) + if not info then + return false, config.login_render + end + info.token = exists.token + info.roles = role.role_permissions(db, info.role) + return true, info +end + + +local profile = {} + +-- 渲染登录页模板 +function profile.render(content) + local db = config.db + local ok, user = verify_permission(content, db) + if not ok then + return utils.redirect(user) + end + if not config.cache then + template.cache = {} + end + return template.compile(template_path){ + cdn = config.cdn, + user = user, + api_url = config.profile_api, + locale = get_locale(Cookie.getCookie("CFLANG")) + } +end + +function profile.response (content) + local db = config.db + if type(content.args) ~= 'table' then + return json_encode({code = 500, msg = "1. 错误的参数" }) + end + local args = content.args + local token = args.token + if not token then + return json_encode({code = 400, msg = '2. 错误的参数'}) + end + -- 验证Token + local exists = user_token.token_exists(db, token) + if not exists then + return json_encode({code = 400, msg = '3. token不存在'}) + end + args.id = toint(exists.uid) + local user_info = user.user_info(db, args.id) + if not user_info then + return json_encode({code = 400, msg = '4. 用户不存在'}) + end + if args.action == 'update_password' then + if not args.password or not args.cupassword then + return json_encode({code = 401, msg = "1. 错误的password参数" }) + end + if #args.password < 6 or #args.password > 20 then + return json_encode({code = 403, msg = "2. password长度必须在6~20之间" }) + end + if args.password == args.cupassword then + return json_encode({code = 403, msg = "3. 新老密码不能一样" }) + end + args.password = crypt.sha1(url_decode(args.password), true) + args.cupassword = crypt.sha1(url_decode(args.cupassword), true) + if user_info.password == args.password then + return json_encode({code = 403, msg = "4. 当前密码不正确或新老密码一致" }) + end + local ok = user.user_update_password(db, args) + if not ok then + return json_encode({code = 401, msg = '5. 密码更新失败'}) + end + user_token.token_delete(db, args.id) + return json_encode({code = 0, msg = 'Success: 密码修改成功'}) + end + if args.action == 'update_userinfo' then + if not args.name or not args.email or not args.phone then + return json_encode({code = 500, msg = "1. 错误的info参数" }) + end + args.name = utils.escape_script(url_decode(args.name)) + args.email = utils.escape_script(url_decode(args.email)) + args.phone = toint(args.phone) + local ok = user.user_update_info(db, args) + if not ok then + return json_encode({code = 401, msg = '2. 用户信息更新失败'}) + end + user_token.token_delete(db, args.id) + return json_encode({code = 0, msg = 'Success: 用户信息更新成功'}) + end + return json_encode({code = 500, msg = "恭喜您完美的错过了所有正确参数" }) +end + +return profile diff --git a/lualib/admin/http/system/header.lua b/lualib/admin/http/system/header.lua new file mode 100644 index 00000000..f1f6a3c7 --- /dev/null +++ b/lualib/admin/http/system/header.lua @@ -0,0 +1,190 @@ +local config = require 'admin.config' +local template = require "template" +local utils = require "admin.utils" +local user = require "admin.db.user" +local user_token = require "admin.db.token" +local role = require "admin.db.role" +local header = require "admin.db.header" +local Cookie = require "admin.cookie" + +local json = require "json" +local json_encode = json.encode +local json_decode = json.decode + +local url = require "url" +local url_encode = url.encode +local url_decode = url.decode + +local type = type +local ipairs = ipairs +local tostring = tostring +local os_date = os.date +local toint = math.tointeger + +local get_path = utils.get_path +local get_locale = utils.get_locale +local access_deny = utils.access_deny + +local template_path = 'lualib/admin/html/system/header/' + +local function verify_permission (content, db) + local token = Cookie.getCookie("CFTOKEN") + if not token then + return false, config.login_render + end + local exists = user_token.token_exists(db, token) + if not exists then -- Token不存在需要重新登录 + return false, config.login_render + end + local info = user.user_info(db, exists.uid) + if not info or info.is_admin ~= 1 then + return false, access_deny(get_path(content)) + end + info.token = exists.token + info.roles = role.role_permissions(db, info.role) + return true, info +end + +local system = {} + +-- 用户管理render +function system.header_render (content) + local db = config.db + local ok, user = verify_permission(content, db) + if not ok then + return utils.redirect(user) + end + if not config.cache then + template.cache = {} + end + return template.compile(template_path..'header.html'){ + cdn = config.cdn, + token = user.token, + api_url = config.system_header_api, + header_add_url = config.system_header_add_render, + header_edit_url = config.system_header_edit_render, + locale = get_locale(Cookie.getCookie('CFLANG')) + } +end + +-- 添加导航 +function system.header_add_render (content) + local db = config.db + local ok, user = verify_permission(content, db) + if not ok then + return utils.redirect(user) + end + if not config.cache then + template.cache = {} + end + return template.compile(template_path..'header-add.html'){ + cdn = config.cdn, + api_url = config.system_header_api, + token = Cookie.getCookie("CFTOKEN"), + locale = get_locale(Cookie.getCookie('CFLANG')) + } +end + +-- 编辑导航 +function system.header_edit_render (content) + local db = config.db + local ok, user = verify_permission(content, db) + if not ok then + return utils.redirect(user) + end + local args = content.args + if not args or not args.id then + return utils.error_404() + end + local h = header.get_header(db, args.id) + if not h then + return utils.error_404() + end + if not config.cache then + template.cache = {} + end + return template.compile(template_path..'header-edit.html'){ + cdn = config.cdn, + id = h.id, + url = h.url, + name = h.name, + api_url = config.system_header_api, + token = Cookie.getCookie("CFTOKEN"), + locale = get_locale(Cookie.getCookie('CFLANG')) + } +end + +-- 用户管理API接口 +function system.header_response (content) + local db = config.db + local args = content.args + if type(args) ~= 'table' then + return json_encode({code = 400, msg = '1. 错误的参数'}) + end + local token = args.token + if not token then + return json_encode({code = 400, msg = '2. 错误的参数'}) + end + -- 验证Token + local exists = user_token.token_exists(db, token) + if not exists then + return json_encode({code = 400, msg = '3. token不存在或权限不足'}) + end + local user_info = user.user_info(db, exists.uid) + if not user_info or user_info.is_admin == 0 then + return json_encode({code = 400, msg = '4. 用户权限不足'}) + end + if args.action == 'delete' then + local headerid = toint(args.headerid) + if not headerid then + return json_encode({ code = 400, msg = '1. 未知的header' }) + end + local exists = header.header_exists(db, headerid) + if not exists then + return json_encode({ code = 400, msg = '2. 试图删除一个不存在的的header' }) + end + header.header_delete(db, headerid) + return json_encode({ code = 0, msg = '删除成功'}) + end + if args.action == 'list' then + local headers = header.header_list(db, args) + for _, header in ipairs(headers) do + header.create_at = os_date("%Y-%m-%d %H:%M:%S", header.create_at) + header.update_at = os_date("%Y-%m-%d %H:%M:%S", header.update_at) + end + return json_encode({code = 0, data = headers, count = header.header_count(db)}) + end + if args.action == 'add' then + if not args.url or not args.name then + return json_encode({ code = 400, msg = "1. 添加导航栏参数错误"}) + end + args.url = url_decode(args.url) + args.name = url_decode(args.name) + local ok = header.header_add(db, args) + if not ok then + return json_encode({code = 401, msg = "2. 添加导航失败"}) + end + return json_encode({code = 0, msg = "添加成功"}) + end + if args.action == 'edit' then + if not args.id then + json_encode({ code = 400, msg = "1. 找不到此header"}) + end + local url = tostring(args.url) + local name = tostring(args.name) + if not url or not name then + json_encode({ code = 400, msg = "2. 非法的参数"}) + end + args.url = url_decode(args.url) + args.name = url_decode(args.name) + local ok = header.header_update(db, args) + if not ok then + return json_encode({code = 401, msg = "3. 修改失败"}) + end + return json_encode({ code = 0, msg = "修改成功"}) + end + return json_encode({code = 500, msg = '恭喜您完美的错过了所有正确参数'}) +end + + +return system diff --git a/lualib/admin/http/system/init.lua b/lualib/admin/http/system/init.lua new file mode 100644 index 00000000..a3b2d1f5 --- /dev/null +++ b/lualib/admin/http/system/init.lua @@ -0,0 +1,32 @@ +local user = require "admin.http.system.user" +local menu = require "admin.http.system.menu" +local header = require "admin.http.system.header" +local role = require "admin.http.system.role" + +return { + + -- 用户管理 Controller + user_response = user.user_response, + user_render = user.user_render, + user_add_render = user.user_add_render, + user_edit_render = user.user_edit_render, + + -- 角色管理 Controller + role_response = role.role_response, + role_render = role.role_render, + role_add_render = role.role_add_render, + role_edit_render = role.role_edit_render, + + -- 侧边栏 Controller + menu_response = menu.menu_response, + menu_render = menu.menu_render, + menu_add_render = menu.menu_add_render, + menu_edit_render = menu.menu_edit_render, + + -- 导航栏 Controller + header_response = header.header_response, + header_render = header.header_render, + header_add_render = header.header_add_render, + header_edit_render = header.header_edit_render, + +} diff --git a/lualib/admin/http/system/menu.lua b/lualib/admin/http/system/menu.lua new file mode 100644 index 00000000..5f228301 --- /dev/null +++ b/lualib/admin/http/system/menu.lua @@ -0,0 +1,244 @@ +local config = require 'admin.config' +local template = require "template" +local utils = require "admin.utils" +local user = require "admin.db.user" +local user_token = require "admin.db.token" +local menu = require "admin.db.menu" +local role = require "admin.db.role" +local view = require "admin.db.view" +local Cookie = require "admin.cookie" + +local json = require "json" +local json_encode = json.encode +local json_decode = json.decode + +local url = require "url" +local url_encode = url.encode +local url_decode = url.decode + +local type = type +local ipairs = ipairs +local os_date = os.date +local toint = math.tointeger + +local get_path = utils.get_path +local get_locale = utils.get_locale +local access_deny = utils.access_deny +local user_have_permission = utils.user_have_permission + +local template_path = 'lualib/admin/html/system/menu/' + +local function verify_permission (content, db) + local token = Cookie.getCookie("CFTOKEN") + if not token then + return false, config.login_render + end + local exists = user_token.token_exists(db, token) + if not exists then -- Token不存在需要重新登录 + return false, config.login_render + end + local info = user.user_info(db, exists.uid) + if not info or info.is_admin ~= 1 then + return false, access_deny(get_path(content)) + end + info.token = exists.token + info.roles = role.role_permissions(db, info.role) + return true, info +end + +-- 构建根节点 +local function root_tree (list, role) + local root = {} + for _, item in ipairs(list) do + -- 判断是否超级管理员或用户所在权限组有当前页面权限 + if (role.is_admin or user_have_permission(role.roles, item.id)) and item.parent == 0 then + root[#root+1] = {id = item.id, name = item.name, url = item.url ~= null and item.url or nil, icon = item.icon ~= null and item.icon or nil} + end + end + return root -- 返回树的根结构 +end + +-- 构建叶(子)节点 +local function sub_tree (root, list, role) + local tab = {} + for index, item in ipairs(list) do + -- 判断是否超级管理员或用户所在权限组有当前页面权限 + if (role.is_admin or user_have_permission(role.roles, item.id)) and root.id == item.parent then + tab[#tab+1] = {id = item.id, name = item.name, url = item.url ~= null and item.url or nil, icon = item.icon ~= null and item.icon or nil} + end + end + return #tab > 0 and tab or nil -- 返回树的叶(子)结构 +end + +-- menu 生成 +local function get_menus (db, role) + local list = view.get_menus(db) + if not list then + return {} + end + local roots = root_tree(list, role) + if #roots == 0 then + return roots + end + for _, root in ipairs(roots) do + local subs = sub_tree(root, list, role) + if subs then + for _, sub in ipairs(subs) do + sub['list'] = sub_tree(sub, list, role) + end + end + root['list'] = subs + end + return roots +end + + +local system = {} + +-- 菜单管理render +function system.menu_render (content) + local db = config.db + local ok, user = verify_permission(content, db) + if not ok then + return utils.redirect(user) + end + if not config.cache then + template.cache = {} + end + return template.compile(template_path..'menu.html'){ + cdn = config.cdn, + token = user.token, + api_url = config.system_menu_api, + menu_add_url = config.system_menu_add_render, + menu_edit_url = config.system_menu_edit_render, + menus = get_menus(db, {is_admin = user.is_admin, roles = user.roles}), + locale = get_locale(Cookie.getCookie('CFLANG')) + } +end + +function system.menu_add_render (content) + local db = config.db + local ok, user = verify_permission(content, db) + if not ok then + return utils.redirect(user) + end + if not config.cache then + template.cache = {} + end + local args = content.args + if type(args) ~= 'table' then + args = { id = nil } + else + args.id = toint(args.id) or nil + end + return template.compile(template_path..'menu-add.html'){ + cdn = config.cdn, + id = args.id, + token = user.token, + api_url = config.system_menu_api, + locale = get_locale(Cookie.getCookie('CFLANG')) + } +end + +function system.menu_edit_render (content) + local db = config.db + local ok, user = verify_permission(content, db) + if not ok then + return utils.redirect(user) + end + if not config.cache then + template.cache = {} + end + local args = content.args + if type(args) ~= 'table' then + return utils.error_404(config.login_render) + end + local id = toint(args.id) + if not id then + return utils.error_404(config.login_render) + end + local menu = menu.menu_info(db, id) + if not menu then + return utils.error_404(config.login_render) + end + return template.compile(template_path..'menu-edit.html'){ + cdn = config.cdn, + token = user.token, + id = id, + menu = menu, + api_url = config.system_menu_api, + locale = get_locale(Cookie.getCookie('CFLANG')) + } +end + +-- 菜单管理API接口 +function system.menu_response (content) + local db = config.db + local args = content.args + if type(args) ~= 'table' then + return json_encode({code = 400, msg = '1. 错误的参数'}) + end + local token = args.token + if not token then + return json_encode({code = 400, msg = '2. 错误的参数'}) + end + -- 验证Token + local exists = user_token.token_exists(db, token) + if not exists then + return json_encode({code = 400, msg = '3. token不存在或权限不足'}) + end + local user_info = user.user_info(db, exists.uid) + if not user_info or user_info.is_admin == 0 then + return json_encode({code = 400, msg = '4. 用户权限不足'}) + end + -- 添加菜单 + local action = args.action + if action == 'add' then + if not args.name then + return json_encode({ code = 401, msg = '1. add参数不足'}) + end + args.id = toint(args.id) or 0 -- id > 0 则为添加子菜单 + args.name = url_decode(args.name) + args.url = url_decode(args.url) or "NULL" + args.icon = url_decode(args.icon) + if menu.menu_name_exists(db, args.name) then + return json_encode({code = 401, msg = '菜单名已存在'}) + end + menu.menu_add(db, args) + return json_encode({code = 0, msg = 'Success'}) + end + -- 删除菜单 + if action == 'delete' then + local id = toint(args.id) + if not id then + return json_encode({ code = 500, msg = "1.1 错误的删除菜单参数"}) + end + menu.menu_delete(db, id) + return json_encode({code = 0, msg = "删除成功"}) + end + if action == 'edit' then + args.id = toint(args.id) + if not args.id then + return json_encode({ code = 500, msg = "1.1 错误的删除菜单参数"}) + end + args.name = url_decode(args.name) + args.url = url_decode(args.url) or "NULL" + args.icon = url_decode(args.icon) + menu.menu_update(db, args) + return json_encode({code = 0, msg = "修改成功"}) + end + -- 获取菜单列表 + if action == 'list' then + local menus = menu.menu_list(db, args) + for _, menu in ipairs(menus) do + menu.create_at = os_date("%Y-%m-%d %H:%M:%S", menu.create_at) + menu.update_at = os_date("%Y-%m-%d %H:%M:%S", menu.delete_at) + end + return json_encode({ code = 0, data = menus, count = menu.menu_count(db) }) + end + -- 如果没有action字段则返回500 + return json_encode({code = 500, msg = '恭喜您完美的给予了错误的参数'}) +end + + +return system diff --git a/lualib/admin/http/system/role.lua b/lualib/admin/http/system/role.lua new file mode 100644 index 00000000..aae500b8 --- /dev/null +++ b/lualib/admin/http/system/role.lua @@ -0,0 +1,208 @@ +local config = require 'admin.config' +local template = require "template" +local utils = require "admin.utils" +local user = require "admin.db.user" +local user_token = require "admin.db.token" +local menu = require "admin.db.menu" +local role = require "admin.db.role" +local Cookie = require "admin.cookie" + +local json = require "json" +local json_encode = json.encode +local json_decode = json.decode + +local url = require "url" +local url_encode = url.encode +local url_decode = url.decode + +local type = type +local ipairs = ipairs +local os_date = os.date +local toint = math.tointeger + +local get_path = utils.get_path +local get_locale = utils.get_locale +local access_deny = utils.access_deny +local role_already_selected = utils.role_already_selected + +local template_path = 'lualib/admin/html/system/role/' + +local function verify_permission (content, db) + local token = Cookie.getCookie("CFTOKEN") + if not token then + return false, config.login_render + end + local exists = user_token.token_exists(db, token) + if not exists then -- Token不存在需要重新登录 + return false, config.login_render + end + local info = user.user_info(db, exists.uid) + if not info or info.is_admin ~= 1 then + return false, access_deny(get_path(content)) + end + info.token = exists.token + info.roles = role.role_permissions(db, info.role) + return true, info +end + +local system = {} + +-- 角色管理 +function system.role_render (content) + local db = config.db + local ok, user = verify_permission(content, db) + if not ok then + return utils.redirect(user) + end + if not config.cache then + template.cache = {} + end + return template.compile(template_path..'role.html'){ + cdn = config.cdn, + token = user.token, + api_url = config.system_role_api, + role_add_url = config.system_role_add_render, + role_edit_url = config.system_role_edit_render, + locale = get_locale(Cookie.getCookie('CFLANG')) + } +end + +-- 添加角色 +function system.role_add_render (content) + local db = config.db + local ok, user = verify_permission(content, db) + if not ok then + return utils.redirect(user) + end + if not config.cache then + template.cache = {} + end + return template.compile(template_path..'role-add.html'){ + cdn = config.cdn, + token = user.token, + api_url = config.system_role_api, + locale = get_locale(Cookie.getCookie('CFLANG')) + } +end + +-- 编辑角色 +function system.role_edit_render (content) + local db = config.db + local ok, user = verify_permission(content, db) + if not ok then + return utils.redirect(user) + end + if not config.cache then + template.cache = {} + end + local args = content.args + if type(args) ~= 'table' then + return utils.error_404(config.login_render) + end + local id = toint(args.id) + if not id then + return utils.error_404(config.login_render) + end + local exists = role.role_id_exists(db, id) + if not exists then + return utils.error_404(config.login_render) + end + return template.compile(template_path..'role-edit.html'){ + cdn = config.cdn, + id = exists.id, + name = exists.name, + token = user.token, + api_url = config.system_role_api, + locale = get_locale(Cookie.getCookie('CFLANG')) + } +end + +-- 用户管理API接口 +function system.role_response (content) + local db = config.db + local args = content.args + if type(args) ~= 'table' then + if not content.json then -- 可以用这个字段判断是否json请求 + return json_encode({code = 400, msg = '1. 错误的参数'}) + end + args = json_decode(content.body) + end + local token = args.token + if not token then + return json_encode({code = 400, msg = '2. 错误的参数'}) + end + -- 验证Token + local exists = user_token.token_exists(db, token) + if not exists then + return json_encode({code = 400, msg = '3. token不存在或权限不足'}) + end + local user_info = user.user_info(db, exists.uid) + if not user_info or user_info.is_admin == 0 then + return json_encode({code = 400, msg = '4. 用户权限不足'}) + end + if args.action == 'list' then + return json_encode({code = 0, count = role.role_count(db), data = role.role_list(db, args)}) + end + if args.action == 'add' then + if not args.name or not args.permissions then + return json_encode({code = 400, msg = '错误的role创建参数'}) + end + args.name = url_decode(args.name) + if role.role_name_exists(db, args.name) then + return json_encode({ code = 401, msg = "角色名已存在"}) + end + local name = role.role_add(db, args) + return json_encode({code = 0, msg = '添加成功'}) + end + if args.action == 'get_tree_list' then + return json_encode({code = 0, data = menu.menu_tree(db)}) + end + if args.action == 'get_veri_tree' then + local id = toint(args.id) + if not id then + return json_encode({code = 400, msg = "错误的参数"}) + end + local menus = menu.menu_tree(db) + if #menus <= 0 then + return json_encode({code = 0, data = json.empty_array}) + end + local permissions = role.role_permissions(db, id) + for _, menu in ipairs(menus) do + if role_already_selected(permissions, menu.id) then + menu.checkArr = {isChecked = 1} + end + end + return json_encode({code = 0, data = menus}) + end + if args.action == 'edit' then + args.id = toint(args.id) + if not args.id or not args.name or type(args.permissions) ~= 'table' then + return json_encode({code = 400, msg = '错误的role修改参数'}) + end + args.name = url_decode(args.name) + role.role_update(db, args) + return json_encode({code = 0, msg = '更新成功'}) + end + if args.action == 'delete' then + local id = toint(args.id) + if not id then + return json_encode({code = 400, msg = "1. 错误的参数"}) + end + local exists = role.role_id_exists(db, id) + if not exists then + return json_encode({code = 400, msg = "2. 该角色不存在"}) + end + if user_info.role == id then + return json_encode({code = 400, msg = "3. 不可删除此角色"}) + end + local ok = role.role_delete(db, id) + if not ok then + return json_encode({code = 400, msg = "4. 删除此角色失败"}) + end + return json_encode({code = 0, msg = "角色删除成功"}) + end + return json_encode({code = 500, msg = '恭喜您完美的错过了所有正确参数'}) +end + + +return system diff --git a/lualib/admin/http/system/user.lua b/lualib/admin/http/system/user.lua new file mode 100644 index 00000000..61b094ea --- /dev/null +++ b/lualib/admin/http/system/user.lua @@ -0,0 +1,242 @@ +local config = require 'admin.config' +local template = require "template" +local utils = require "admin.utils" +local user = require "admin.db.user" +local user_token = require "admin.db.token" +local role = require "admin.db.role" +local Cookie = require "admin.cookie" +local crypt = require "crypt" + +local json = require "json" +local json_encode = json.encode +local json_decode = json.decode + +local url = require "url" +local url_encode = url.encode +local url_decode = url.decode + +local type = type +local ipairs = ipairs +local os_date = os.date +local toint = math.tointeger + +local get_path = utils.get_path +local get_locale = utils.get_locale +local access_deny = utils.access_deny + +local template_path = 'lualib/admin/html/system/user/' + +local function verify_permission (content, db) + local token = Cookie.getCookie("CFTOKEN") + if not token then + return false, config.login_render + end + local exists = user_token.token_exists(db, token) + if not exists then -- Token不存在需要重新登录 + return false, config.login_render + end + local info = user.user_info(db, exists.uid) + if not info or info.is_admin ~= 1 then + return false, access_deny(get_path(content)) + end + info.token = exists.token + info.roles = role.role_permissions(db, info.role) + return true, info +end + +local system = {} + +-- 用户管理render +function system.user_render (content) + local db = config.db + local ok, user = verify_permission(content, db) + if not ok then + return utils.redirect(user) + end + if not config.cache then + template.cache = {} + end + return template.compile(template_path..'user.html'){ + cdn = config.cdn, + token = user.token, + api_url = config.system_user_api, + user_add_url = config.system_user_add_render, + user_edit_url = config.system_user_edit_render, + locale = get_locale(Cookie.getCookie('CFLANG')) + } +end + +-- 添加用户页面 +function system.user_add_render (content) + local db = config.db + local ok, user = verify_permission(content, db) + if not ok then + return utils.redirect(user) + end + if not config.cache then + template.cache = {} + end + return template.compile(template_path..'user-add.html'){ + cdn = config.cdn, + token = user.token, + api_url = config.system_user_api, + roles = role.role_list(db, {limit = 100}), + locale = get_locale(Cookie.getCookie('CFLANG')) + } +end + +-- 编辑用户 +function system.user_edit_render (content) + local db = config.db + local ok, opt = verify_permission(content, db) + if not ok then + return utils.redirect(opt) + end + local args = content.args + if not type(args) == 'table' and not args.id then + return utils.error_404(config.login_render) + end + local user = user.user_info(db, args.id or 0) + if not user then + return utils.error_404(config.login_render) + end + if not config.cache then + template.cache = {} + end + return template.compile(template_path..'user-edit.html'){ + cdn = config.cdn, + token = opt.token, + api_url = config.system_user_api, + user = user, + roles = role.role_list(db, {limit = 100}), + locale = get_locale(Cookie.getCookie('CFLANG')) + } +end + +-- 用户管理API接口 +function system.user_response (content) + local db = config.db + local args = content.args + if type(args) ~= 'table' then + return json_encode({code = 400, msg = '1. 错误的参数'}) + end + local token = args.token + if not token then + return json_encode({code = 400, msg = '2. 错误的参数'}) + end + -- 验证Token + local exists = user_token.token_exists(db, token) + if not exists then + return json_encode({code = 400, msg = '3. token不存在或权限不足'}) + end + local user_info = user.user_info(db, exists.uid) + if not user_info or user_info.is_admin ~= 1 then + return json_encode({code = 400, msg = '4. 用户权限不足'}) + end + local action = args.action + -- 查找用户(模糊) + if action == 'findUser' then + if not args.value or not args.condition then + return json_encode({code = 400, msg = '1. 无效的参数'}) + end + local users, count = user.find_user(db, args) + if not users or not count then + return json_encode({code = 400, msg = '2. 无效的参数'}) + end + return json_encode({code = 0, data = users, count = count}) + end + -- 清除所有用户登录状态 + if action == "clear_login" then + local ok = user_token.flush_all(db, exists.uid) + if not ok then + return json_encode({code = 500, msg = "清除登录状态失败"}) + end + return json_encode({code = 0, msg = "清除登录状态成功"}) + end + -- 添加用户 + if action == 'add' then + if not args.name or not args.username or not args.password then + return json_encode({code = 400, msg = '1. 用户信息不完善'}) + end + args.phone = toint(args.phone) + args.role = toint(args.role) + if not args.role or not args.email or not args.phone then + return json_encode({code = 400, msg = '2. 用户信息不完善'}) + end + if #args.username < 6 or #args.username > 20 or #args.password < 6 or #args.password > 20 then + return json_encode({code = 400, msg = '3. 用户名与密码需要在6~20字符之间'}) + end + args.name = utils.escape_script(url.decode(args.name)) + args.email = utils.escape_script(url.decode(args.email)) + args.username = utils.escape_script(url.decode(args.username)) + local exists = user.user_name_or_username_exists(db, args.name, args.username) + if exists then + return json_encode({code = 400, msg = '4. 用户已存在'}) + end + args.password = crypt.sha1(args.password, true) + local ok = user.user_add(db, args) + if not ok then + return json_encode({code = 401, msg = "5. 添加用户失败"}) + end + return json_encode({code = 0, msg = "添加成功"}) + end + -- 删除用户 + if action == 'delete' then + local uid = toint(args.id) + if not uid then + return json_encode({code = 400, msg = '1. 未知的用户ID'}) + end + if exists.uid == uid then + return json_encode({code = 401, msg = "2. 不能删除当前用户"}) + end + local exists = user.user_exists(db, nil, uid) + if not exists then + return json_encode({code = 403, msg = '3. 试图删除不存在的用户'}) + end + user.user_delete(db, uid) + user_token.token_delete(db, uid) -- 清除Token + return json_encode({code = 0, msg = "删除用户成功"}) + end + -- 获取用户列表 + if action == 'list' then + local users = user.user_list(db, args) + for _, user in ipairs(users) do + user.create_at = os_date("%Y-%m-%d %H:%M:%S", user.create_at) + user.update_at = os_date("%Y-%m-%d %H:%M:%S", user.update_at) + end + return json_encode({code = 0, msg = "SUCCESS", count = user.user_count(db), data = users}) + end + if action == 'edit' then + if not args.id or not args.role then + return json_encode({code = 400, msg = "1. 未知的用户与权限"}) + end + if not args.name then + return json_encode({code = 400, msg = "2. 未知的用户名"}) + end + args.name = utils.escape_script(url.decode(args.name)) + if not args.username or not args.password then + return json_encode({code = 400, msg = "3. 未知的账户与密码"}) + end + if #args.username < 6 or #args.username > 20 or #args.password < 6 or #args.password > 20 then + return json_encode({code = 401, msg = "4. 账户/密码应该为6-20个字符."}) + end + args.phone = toint(args.phone) + args.password = crypt.sha1(args.password, true) + args.username = utils.escape_script(url.decode(args.username)) + if not args.phone or not args.email then + return json_encode({code = 400, msg = "5. 无效的邮箱与手机号"}) + end + args.email = utils.escape_script(url.decode(args.email)) + local ok = user.user_update(db, args) + if not ok then + return json_encode({code = 401, msg = "6. 更新用户信息失败"}) + end + user_token.token_delete(db, args.id) -- 清除Token + return json_encode({code = 0, msg = "SUCCESS"}) + end + -- 如果没有action字段则返回500 + return json_encode({code = 500, msg = '恭喜您完美的给予了错误的参数'}) +end + + +return system diff --git a/lualib/admin/httpctx.lua b/lualib/admin/httpctx.lua new file mode 100644 index 00000000..28b50f74 --- /dev/null +++ b/lualib/admin/httpctx.lua @@ -0,0 +1,75 @@ +local class = require "class" +local Cookie = require "admin.cookie" + +local xml2lua = require "xml2lua" + +local json = require "json" +local json_encode = json.encode +local json_decode = json.decode + +local type = type +local split = string.sub +local find = string.find + +local ctx = class("ctx") + +function ctx:ctor (opt) + self._content = opt.content +end + +-- 获取请求path +function ctx:get_path () + return split(self._content.path, 1, (find(self._content.path, '?') or 0) - 1) +end + +-- 获取原始path +function ctx:get_raw_path () + return self._content.path +end + +-- 获取请求头部 +function ctx:get_headers () + return self._content.headers +end + +-- 获取请求方法 +function ctx:get_method () + return self._content.method +end + +-- 获取Cookie +function ctx:get_cookie (name) + return Cookie.getCookie(name) +end + +-- 获取上传的文件 +function ctx:get_files () + return self._content.files +end + +-- 获取请求参数表 +function ctx:get_args () + local args = self._content.args + if type(args) == 'table' then + return args + end + local body = self._content.body + if body then + local xml = self._content.xml + local json = self._content.json + if xml then + args = xml2lua.parser(body) + if type(args) == 'table' then + return args + end + end + if json then + args = json_decode(body) + if type(args) == 'table' then + return args + end + end + end +end + +return ctx diff --git a/lualib/admin/init.lua b/lualib/admin/init.lua new file mode 100644 index 00000000..d39bfeaa --- /dev/null +++ b/lualib/admin/init.lua @@ -0,0 +1,151 @@ +local http = require "httpd.http" +local template = require "template" +local utils = require "admin.utils" +local config = require "admin.config" +local locales = require "admin.locales" +local admin_db = require "admin.db" +local user_token = require "admin.db.token" +local login = require "admin.http.login" +local system = require "admin.http.system" +local profile = require "admin.http.profile" +local dashboard = require "admin.http.dashboard" + +local type = type +local ipairs = ipairs +local assert = assert +local toint = math.tointeger +local find = string.find +local get_path = utils.get_path + +local admin = {} + +-- 开启全局模板缓存 +function admin.cached() + config.cache = true +end + +-- 设置静态文件的域名与前缀 +function admin.static(domain) + config.cdn = domain +end + +-- Cookie时间 +function admin.cookie_timeout (timeout) + config.cookie_timeout = toint(timeout) or 86400 +end + +-- 添加某语言的字段 +function admin.add_locale_item(lang, locale_item) + local locale = locales[lang] + if locale and type(locale) == 'table' then + for _, item in ipairs(locale_item) do + locale[item[1]] = item[2] + end + end +end + +-- 修改全局默认语言 +function admin.set_locale (locale) + if type(locale) == 'string' then + config.locale = locale + end +end + +-- 设置仪表盘显示页 +function admin.init_home (url) + config.home = url or config.home +end + +-- 设置是否显示头部语言标签 +function admin.display_lang (display) + config.display_lang = display +end + +function admin.init_page (app, db) + config.app = assert(app, '初始化必须传入有效的http对象') + config.db = assert(db, '初始化必须传入有效的db对象') + + config.home = config.home or '/welcome.html' + + -- 所有/api/admin路径下的接口都需要进行token header验权. + app:before(function (content) + local path = get_path(content) + if find(path, '^[/]+api[/]+admin[/]*.+') then + -- 所有路由保持在这个路径之下会相对来说较为安全. + local token = content['headers']['token'] or content['headers']['Token'] + if not token then + return http.throw(401, '{"code":401,"msg":"Token was not exists"}') + end + -- 验证token是否有效; 无效的token在此进行过滤 + local exists = user_token.token_exists(db, token) + if not exists then + return http.throw(403, '{"code":401,"msg":"Token was verify failed"}') + end + end + return http.ok() + end) + + -- 注册登录页相关路由 + config.login_api = '/api/login' + config.login_render = '/admin' + app:api(config.login_api, login.response) + app:use(config.login_render, login.render) + + -- 后台首页路由 + config.dashboard = '/admin/dashboard' + app:use(config.dashboard, dashboard.render) + + -- 用户管理路由 + config.system_user_api = '/api/admin/system/user' + config.system_user_render = '/admin/system/user' + config.system_user_add_render = '/admin/system/user/add' + config.system_user_edit_render = '/admin/system/user/edit' + app:api(config.system_user_api, system.user_response) + app:use(config.system_user_render, system.user_render) + app:use(config.system_user_add_render, system.user_add_render) + app:use(config.system_user_edit_render, system.user_edit_render) + + -- 菜单管理 + config.system_menu_api = '/api/admin/system/menu' + config.system_menu_render = '/admin/system/menu' + config.system_menu_add_render = '/admin/system/menu/add' + config.system_menu_edit_render = '/admin/system/menu/edit' + app:api(config.system_menu_api, system.menu_response) + app:use(config.system_menu_render, system.menu_render) + app:use(config.system_menu_add_render, system.menu_add_render) + app:use(config.system_menu_edit_render, system.menu_edit_render) + + -- 导航管理 + config.system_header_api = '/api/admin/system/header' + config.system_header_render = '/admin/system/header' + config.system_header_add_render = '/admin/system/header/add' + config.system_header_edit_render = '/admin/system/header/edit' + app:api(config.system_header_api, system.header_response) + app:use(config.system_header_render, system.header_render) + app:use(config.system_header_add_render, system.header_add_render) + app:use(config.system_header_edit_render, system.header_edit_render) + + -- 权限管理 + config.system_role_api = '/api/admin/system/role' + config.system_role_render = '/admin/system/role' + config.system_role_add_render = '/admin/system/role/add' + config.system_role_edit_render = '/admin/system/role/edit' + app:api(config.system_role_api, system.role_response) + app:use(config.system_role_render, system.role_render) + app:use(config.system_role_add_render, system.role_add_render) + app:use(config.system_role_edit_render, system.role_edit_render) + + -- profile路由 + config.profile_api = '/api/admin/profile' + config.profile_render = '/admin/profile' + app:api(config.profile_api, profile.response) + app:use(config.profile_render, profile.render) + +end + +-- 初始化数据 +function admin.init_db () + return admin_db() +end + +return admin diff --git a/lualib/admin/locales/EN-US.lua b/lualib/admin/locales/EN-US.lua new file mode 100644 index 00000000..cea260bf --- /dev/null +++ b/lualib/admin/locales/EN-US.lua @@ -0,0 +1,186 @@ +return { + -- 错误页 + ['error.404.title'] = 'CFAdmin Error Page', + ['error.404.message'] = "can't find page or data, Please contact Administrator.", + + -- 登录页 + ['login.form.title'] = 'CFAdmin System', + ['login.form.username'] = 'Username', + ['login.form.password'] = 'Password', + ['login.form.submit'] = 'Click Login', + + -- 主页 + ['dashboard.header.title'] = 'CFAdmin System', + ['dashboard.header.logo'] = 'CFAdmin System', + ['dashboard.header.user.profile'] = 'Profile', + ['dashboard.header.user.quit'] = 'Logout', + + ['dashboard.header.langs'] = 'Select Lang', + ['dashboard.header.langs.us-english'] = 'English-America', + ['dashboard.header.langs.simplified-chinese'] = 'Simplified-Chinese', + + ['dashboard.header.profile.update_userinfo'] = 'Update Profile', + ['dashboard.header.profile.form.name'] = 'Name', + ['dashboard.header.profile.form.name.notice'] = 'User Display Name', + ['dashboard.header.profile.form.username'] = 'User Account', + ['dashboard.header.profile.form.username.notice'] = 'User Account', + ['dashboard.header.profile.form.email'] = 'Email', + ['dashboard.header.profile.form.phone'] = 'Phone', + + + ['dashboard.header.profile.update_password'] = 'Update Password', + ['dashboard.header.profile.form.cupassword'] = 'Old-PW', + ['dashboard.header.profile.form.password'] = 'New-PW', + -- 面包屑 + ['dashboard.crumbs.home'] = 'Dashboard', + ['dashboard.crumbs.close_all'] = 'Close All Page', + ['dashboard.crumbs.close_other'] = 'Close Other Page', + ['dashboard.crumbs.close_this'] = 'Close Current Page', + + -- 菜单栏 + ['dashboard.menu.home'] = 'Dashboard', + ['dashboard.menu.system'] = 'System Manage', + ['dashboard.menu.system.user'] = 'User Manage', + ['dashboard.menu.system.menu'] = 'Menu Manage', + ['dashboard.menu.system.header'] = 'NaviBar Manage', + ['dashboard.menu.system.role'] = 'Roles Manage', + + -- 用户管理 + ['dashboard.menu.user_manage.title'] = 'User Manage', + ['dashboard.menu.user_manage.table.uid'] = 'UID', + ['dashboard.menu.user_manage.table.name'] = 'Name', + ['dashboard.menu.user_manage.table.username'] = 'User Name', + ['dashboard.menu.user_manage.table.permission_name'] = 'Role Name', + ['dashboard.menu.user_manage.table.phone'] = 'Phone', + ['dashboard.menu.user_manage.table.email'] = 'Email', + ['dashboard.menu.user_manage.table.create_time'] = 'Create Time', + ['dashboard.menu.user_manage.table.update_time'] = 'Update Time', + ['dashboard.menu.user_manage.table.options'] = 'Options', + + ['dashboard.menu.user_manage.table.reflush'] = 'Reflush', + ['dashboard.menu.user_manage.table.clear_login'] = "Clear login status", + ['dashboard.menu.user_manage.table.adduser'] = 'Add User', + ['dashboard.menu.user_manage.table.edituser'] = 'Edit User', + + ['dashboard.menu.user_manage.table.options.edit'] = 'Edit', + ['dashboard.menu.user_manage.table.options.delete'] = 'Delete', + + ['dashboard.menu.user_manage.table.options.delete.confirm'] = 'confirm Delete it?', + ['dashboard.menu.user_manage.table.options.delete.confirm.success'] = 'Delet Success.', + + ['dashboard.menu.user_manage.table.search'] = "Search", + ['dashboard.menu.user_manage.table.search.condition'] = "Condition", + ['dashboard.menu.user_manage.table.search.condition.userid'] = "User ID", + ['dashboard.menu.user_manage.table.search.condition.username'] = "User Name", + ['dashboard.menu.user_manage.table.search.condition.useraccount'] = "User Account", + ['dashboard.menu.user_manage.table.search.condition.email'] = "User Email", + ['dashboard.menu.user_manage.table.search.condition.phone'] = "User Phone", + + -- 用户新增页面 + ['dashboard.menu.user_manage.form.user_add.name'] = 'Name', + ['dashboard.menu.user_manage.form.user_add.name.notice'] = 'User Name', + ['dashboard.menu.user_manage.form.user_add.username'] = 'User Name', + ['dashboard.menu.user_manage.form.user_add.username.notice'] = 'Login User Name', + ['dashboard.menu.user_manage.form.user_add.password'] = 'Password', + ['dashboard.menu.user_manage.form.user_add.password.notice'] = 'Password must between 6 ~ 20', + ['dashboard.menu.user_manage.form.user_add.role'] = 'Role', + ['dashboard.menu.user_manage.form.user_add.phone'] = 'Phone', + ['dashboard.menu.user_manage.form.user_add.email'] = 'Email', + + -- 用户编辑页面 + ['dashboard.menu.user_manage.form.user_edit.name'] = 'Name', + ['dashboard.menu.user_manage.form.user_edit.name.notice'] = 'User Name', + ['dashboard.menu.user_manage.form.user_edit.username'] = 'User Name', + ['dashboard.menu.user_manage.form.user_edit.username.notice'] = 'Login User Name', + ['dashboard.menu.user_manage.form.user_edit.password'] = 'Password', + ['dashboard.menu.user_manage.form.user_edit.role'] = 'Role', + ['dashboard.menu.user_manage.form.user_edit.password.notice'] = 'Password must between 6 ~ 20', + ['dashboard.menu.user_manage.form.user_edit.phone'] = 'Phone', + ['dashboard.menu.user_manage.form.user_edit.email'] = 'Email', + + ['dashboard.menu.user_manage.form.submit'] = 'Submit', + + -- 菜单管理 + ['dashboard.menu.menu_manage.title'] = 'Menu Manage', + ['dashboard.menu.menu_manage.table.id'] = 'ID', + ['dashboard.menu.menu_manage.table.name'] = 'Name', + ['dashboard.menu.menu_manage.table.url'] = 'Link', + ['dashboard.menu.menu_manage.table.icon'] = 'Icon', + ['dashboard.menu.menu_manage.table.options'] = 'Options', + + ['dashboard.menu.menu_manage.table.options.add'] = 'Add', + ['dashboard.menu.menu_manage.table.options.edit'] = 'Edit', + ['dashboard.menu.menu_manage.table.options.delete'] = 'Delete', + ['dashboard.menu.menu_manage.table.options.reflush'] = 'Reflush', + ['dashboard.menu.menu_manage.table.options.confirm'] = 'confirm delete this row?', + + ['dashboard.menu.menu_manage.menu_add.form.name'] = 'Menu Name', + ['dashboard.menu.menu_manage.menu_add.form.name.notice'] = 'Displaye Menu Name', + ['dashboard.menu.menu_manage.menu_add.form.url'] = 'Menu Link', + ['dashboard.menu.menu_manage.menu_add.form.url.notice'] = "Menu Open Link", + ['dashboard.menu.menu_manage.menu_add.form.icon'] = 'Menu Icon', + ['dashboard.menu.menu_manage.menu_add.form.icon.notice'] = 'Menu Left Icon', + + ['dashboard.menu.menu_manage.menu_add.form.submit'] = 'Submit', + ['dashboard.menu.menu_manage.menu_add.form.icon.help'] = 'View icon', + + ['dashboard.menu.menu_manage.menu_edit.form.name'] = 'Menu Name', + ['dashboard.menu.menu_manage.menu_edit.form.name.notice'] = 'Displaye Menu Name', + ['dashboard.menu.menu_manage.menu_edit.form.url'] = 'Menu Link', + ['dashboard.menu.menu_manage.menu_edit.form.url.notice'] = "Menu Open Link", + ['dashboard.menu.menu_manage.menu_edit.form.icon'] = 'Menu Icon', + ['dashboard.menu.menu_manage.menu_edit.form.icon.notice'] = 'Menu Left Icon', + + ['dashboard.menu.menu_manage.menu_edit.form.submit'] = 'Submit', + ['dashboard.menu.menu_manage.menu_edit.form.icon.help'] = 'View icon', + + -- 导航管理 + ['dashboard.menu.header_manage.title'] = 'NaviBar Manage', + -- ['dashboard.menu.header_manage.table.id'] = 'NaviBar ID', + ['dashboard.menu.header_manage.table.name'] = 'NaviBar Name', + ['dashboard.menu.header_manage.table.url'] = 'NaviBar Link', + ['dashboard.menu.header_manage.table.permission_name'] = 'NaviBar Permission', + ['dashboard.menu.header_manage.table.create_time'] = 'Create Time', + ['dashboard.menu.header_manage.table.update_time'] = 'Update Timer', + ['dashboard.menu.header_manage.table.options'] = 'Options', + + ['dashboard.menu.header_manage.table.reflush'] = 'Reflush', + ['dashboard.menu.header_manage.table.addnavi'] = 'Add NaviBar', + ['dashboard.menu.header_manage.table.editnavi'] = 'Edit NaviBar', + + ['dashboard.menu.header_manage.table.options.edit'] = 'Edit', + ['dashboard.menu.header_manage.table.options.delete'] = 'Delete', + + ['dashboard.menu.header_manage.table.options.delete.confirm'] = 'Confirm Delete?', + ['dashboard.menu.header_manage.table.options.delete.confirm.success'] = 'Delete Success', + + -- 导航新增页面 + ['dashboard.menu.header_manage.form.header_add.name'] = 'NavBar Name', + ['dashboard.menu.header_manage.form.header_add.name.notice'] = 'NavBar Name is required', + ['dashboard.menu.header_manage.form.header_add.url'] = 'NavBar URL', + ['dashboard.menu.header_manage.form.header_add.url.notice'] = 'NavBar Url is required', + + -- 导航编辑页面 + ['dashboard.menu.header_manage.form.header_edit.name'] = 'NavBar Name', + ['dashboard.menu.header_manage.form.header_edit.name.notice'] = 'NavBar Name is required', + ['dashboard.menu.header_manage.form.header_edit.url'] = 'NavBar URL', + ['dashboard.menu.header_manage.form.header_edit.url.notice'] = 'NavBar Url is required', + + ['dashboard.menu.header_manage.form.submit'] = 'Submit', + + -- 角色管理 + ['dashboard.menu.role_manage.title'] = 'Role Manager', + ['dashboard.menu.role_manage.table.name'] = 'Role Name', + ['dashboard.menu.role_manage.table.create_time'] = 'Create Time', + ['dashboard.menu.role_manage.table.update_time'] = 'Update Time', + ['dashboard.menu.role_manage.table.options'] = 'Options', + + ['dashboard.menu.role_manage.table.reflush'] = 'Reflush', + + ['dashboard.menu.role_manage.table.options.confirm'] = 'Confirm Delete This Role?', + + ['dashboard.menu.role_manage.table.options.add'] = 'Add Role', + ['dashboard.menu.role_manage.table.options.edit'] = 'Edit Role', + ['dashboard.menu.role_manage.table.options.delete'] = 'Delete Role', + +} diff --git a/lualib/admin/locales/ZH-CN.lua b/lualib/admin/locales/ZH-CN.lua new file mode 100644 index 00000000..3d720983 --- /dev/null +++ b/lualib/admin/locales/ZH-CN.lua @@ -0,0 +1,189 @@ +return { + -- 错误页 + ['error.404.title'] = 'cfadmin-错误页', + ['error.404.message'] = "页面或数据无法找到, 请联系管理员. ^_^.", + + -- 登录页 + ['login.form.title'] = 'cfadmin 后台管理系统', + ['login.form.username'] = '请输入用户名', + ['login.form.password'] = '请输入用户密码', + ['login.form.submit'] = '登录', + + -- 主页 + ['dashboard.header.logo'] = 'cfadmin 后台管理系统', + ['dashboard.header.title'] = 'cfadmin 后台管理系统', + ['dashboard.header.user.profile'] = '个人中心', + ['dashboard.header.user.quit'] = '注销', + + ['dashboard.header.langs'] = '选择语言', + ['dashboard.header.langs.us-english'] = '英语-美国', + ['dashboard.header.langs.simplified-chinese'] = '简体-中文', + + ['dashboard.header.profile.update_userinfo'] = '更新用户信息', + ['dashboard.header.profile.form.name'] = '名称', + ['dashboard.header.profile.form.name.notice'] = '用户显示名称', + ['dashboard.header.profile.form.username'] = '账户', + ['dashboard.header.profile.form.username.notice'] = '用户登录账户', + ['dashboard.header.profile.form.email'] = '邮箱', + ['dashboard.header.profile.form.phone'] = '手机', + + ['dashboard.header.profile.update_password'] = '修改用户密码', + ['dashboard.header.profile.form.cupassword'] = '当前密码', + ['dashboard.header.profile.form.password'] = '新密码', + + -- 面包屑 + ['dashboard.crumbs.home'] = '仪表盘', + ['dashboard.crumbs.close_all'] = '关闭所有页面', + ['dashboard.crumbs.close_this'] = '关闭当前页面', + ['dashboard.crumbs.close_other'] = '关闭其它页面', + + -- 菜单栏 + ['dashboard.menu.home'] = '仪表盘', + ['dashboard.menu.system'] = '系统管理', + ['dashboard.menu.system.user'] = '用户管理', + ['dashboard.menu.system.menu'] = '菜单栏管理', + ['dashboard.menu.system.header'] = '导航栏管理', + ['dashboard.menu.system.role'] = '角色管理', + + -- 用户管理 + ['dashboard.menu.user_manage.title'] = '用户管理', + ['dashboard.menu.user_manage.table.uid'] = '用户ID', + ['dashboard.menu.user_manage.table.name'] = '用户名称', + ['dashboard.menu.user_manage.table.username'] = '用户账户', + ['dashboard.menu.user_manage.table.permission_name'] = '角色', + ['dashboard.menu.user_manage.table.phone'] = '用户手机', + ['dashboard.menu.user_manage.table.email'] = '用户邮箱', + ['dashboard.menu.user_manage.table.create_time'] = '创建时间', + ['dashboard.menu.user_manage.table.update_time'] = '修改时间', + ['dashboard.menu.user_manage.table.options'] = '操作', + + ['dashboard.menu.user_manage.table.reflush'] = '刷新', + ['dashboard.menu.user_manage.table.clear_login'] = "清除登录状态", + ['dashboard.menu.user_manage.table.adduser'] = '添加用户', + ['dashboard.menu.user_manage.table.edituser'] = '编辑用户', + ['dashboard.menu.user_manage.table.true'] = '是', + ['dashboard.menu.user_manage.table.false'] = '否', + + ['dashboard.menu.user_manage.table.options.edit'] = '编辑', + ['dashboard.menu.user_manage.table.options.delete'] = '删除', + + ['dashboard.menu.user_manage.table.options.delete.confirm'] = '确认要删除么?', + ['dashboard.menu.user_manage.table.options.delete.confirm.success'] = '删除成功', + + ['dashboard.menu.user_manage.table.search'] = "搜索", + ['dashboard.menu.user_manage.table.search.condition'] = "搜索条件", + ['dashboard.menu.user_manage.table.search.condition.userid'] = "用户ID", + ['dashboard.menu.user_manage.table.search.condition.username'] = "用户名称", + ['dashboard.menu.user_manage.table.search.condition.useraccount'] = "用户账户", + ['dashboard.menu.user_manage.table.search.condition.email'] = "用户邮箱", + ['dashboard.menu.user_manage.table.search.condition.phone'] = "用户手机", + + -- 用户新增页面 + ['dashboard.menu.user_manage.form.user_add.name'] = '用户名称', + ['dashboard.menu.user_manage.form.user_add.name.notice'] = '用户显示名称', + ['dashboard.menu.user_manage.form.user_add.username'] = '用户账户', + ['dashboard.menu.user_manage.form.user_add.username.notice'] = '用户登录所用账户', + ['dashboard.menu.user_manage.form.user_add.password'] = '密码', + ['dashboard.menu.user_manage.form.user_add.password.notice'] = '密码必须在6~20位', + ['dashboard.menu.user_manage.form.user_add.role'] = '角色', + ['dashboard.menu.user_manage.form.user_add.phone'] = '手机', + ['dashboard.menu.user_manage.form.user_add.email'] = '邮箱', + + -- 用户编辑页面 + ['dashboard.menu.user_manage.form.user_edit.name'] = '用户名称', + ['dashboard.menu.user_manage.form.user_edit.name.notice'] = '用户显示名称', + ['dashboard.menu.user_manage.form.user_edit.username'] = '用户账户', + ['dashboard.menu.user_manage.form.user_edit.username.notice'] = '用户登录所用账户', + ['dashboard.menu.user_manage.form.user_edit.password'] = '密码', + ['dashboard.menu.user_manage.form.user_edit.password.notice'] = '密码必须在6~20位', + ['dashboard.menu.user_manage.form.user_edit.role'] = '角色', + ['dashboard.menu.user_manage.form.user_edit.phone'] = '手机', + ['dashboard.menu.user_manage.form.user_edit.email'] = '邮箱', + + ['dashboard.menu.user_manage.form.submit'] = '提交', + + -- 菜单管理 + ['dashboard.menu.menu_manage.title'] = '菜单管理', + ['dashboard.menu.menu_manage.table.id'] = 'ID', + ['dashboard.menu.menu_manage.table.name'] = '名称', + ['dashboard.menu.menu_manage.table.url'] = '链接', + ['dashboard.menu.menu_manage.table.icon'] = '图标', + ['dashboard.menu.menu_manage.table.options'] = '操作', + + ['dashboard.menu.menu_manage.table.options.add'] = '添加菜单', + ['dashboard.menu.menu_manage.table.options.edit'] = '编辑菜单', + ['dashboard.menu.menu_manage.table.options.delete'] = '删除菜单', + ['dashboard.menu.menu_manage.table.options.reflush'] = '刷新', + ['dashboard.menu.menu_manage.table.options.confirm'] = '确定要删除么?', + + ['dashboard.menu.menu_manage.menu_add.form.name'] = '菜单名称', + ['dashboard.menu.menu_manage.menu_add.form.name.notice'] = '菜单显示的名称', + ['dashboard.menu.menu_manage.menu_add.form.url'] = '菜单链接', + ['dashboard.menu.menu_manage.menu_add.form.url.notice'] = "菜单打开的链接", + ['dashboard.menu.menu_manage.menu_add.form.icon'] = '菜单图标', + ['dashboard.menu.menu_manage.menu_add.form.icon.notice'] = '菜单左侧图标', + + ['dashboard.menu.menu_manage.menu_add.form.submit'] = '确认增加', + ['dashboard.menu.menu_manage.menu_add.form.icon.help'] = '查看图标代码', + + ['dashboard.menu.menu_manage.menu_edit.form.name'] = '菜单名称', + ['dashboard.menu.menu_manage.menu_edit.form.name.notice'] = '菜单显示的名称', + ['dashboard.menu.menu_manage.menu_edit.form.url'] = '菜单链接', + ['dashboard.menu.menu_manage.menu_edit.form.url.notice'] = "菜单打开的链接", + ['dashboard.menu.menu_manage.menu_edit.form.icon'] = '菜单图标', + ['dashboard.menu.menu_manage.menu_edit.form.icon.notice'] = '菜单左侧图标', + + ['dashboard.menu.menu_manage.menu_edit.form.submit'] = '确认修改', + ['dashboard.menu.menu_manage.menu_edit.form.icon.help'] = '查看图标代码', + + -- 导航管理 + ['dashboard.menu.header_manage.title'] = '导航管理', + -- ['dashboard.menu.header_manage.table.id'] = '导航ID', + ['dashboard.menu.header_manage.table.name'] = '导航名称', + ['dashboard.menu.header_manage.table.url'] = '导航链接', + ['dashboard.menu.header_manage.table.create_time'] = '创建时间', + ['dashboard.menu.header_manage.table.update_time'] = '修改时间', + ['dashboard.menu.header_manage.table.options'] = '操作', + + ['dashboard.menu.header_manage.table.reflush'] = '刷新', + ['dashboard.menu.header_manage.table.addnavi'] = '添加导航', + ['dashboard.menu.header_manage.table.editnavi'] = '编辑导航', + ['dashboard.menu.header_manage.table.true'] = '是', + ['dashboard.menu.header_manage.table.false'] = '否', + + ['dashboard.menu.header_manage.table.options.edit'] = '编辑', + ['dashboard.menu.header_manage.table.options.delete'] = '删除', + + ['dashboard.menu.header_manage.table.options.delete.confirm'] = '确认要删除么?', + ['dashboard.menu.header_manage.table.options.delete.confirm.success'] = '删除成功', + + -- 导航新增页面 + ['dashboard.menu.header_manage.form.header_add.name'] = '导航名称', + ['dashboard.menu.header_manage.form.header_add.name.notice'] = '导航名称为必填项', + ['dashboard.menu.header_manage.form.header_add.url'] = '导航链接', + ['dashboard.menu.header_manage.form.header_add.url.notice'] = '导航链接为必填项', + + -- 导航编辑页面 + ['dashboard.menu.header_manage.form.header_edit.name'] = '导航名称', + ['dashboard.menu.header_manage.form.header_edit.name.notice'] = '导航名称为必填项', + ['dashboard.menu.header_manage.form.header_edit.url'] = '导航链接', + ['dashboard.menu.header_manage.form.header_edit.url.notice'] = '导航链接为必填项', + + ['dashboard.menu.header_manage.form.submit'] = '提交', + + -- 角色管理 + ['dashboard.menu.role_manage.title'] = '角色管理', + ['dashboard.menu.role_manage.table.name'] = '角色名称', + ['dashboard.menu.role_manage.table.create_time'] = '创建时间', + ['dashboard.menu.role_manage.table.update_time'] = '修改时间', + ['dashboard.menu.role_manage.table.options'] = '操作', + + ['dashboard.menu.role_manage.table.reflush'] = '刷新', + + ['dashboard.menu.role_manage.table.options.confirm'] = '确定要删除该角色吗?', + + ['dashboard.menu.role_manage.table.options.add'] = '添加角色', + ['dashboard.menu.role_manage.table.options.edit'] = '编辑角色', + ['dashboard.menu.role_manage.table.options.delete'] = '删除角色', + +} diff --git a/lualib/admin/locales/init.lua b/lualib/admin/locales/init.lua new file mode 100644 index 00000000..d81c54e3 --- /dev/null +++ b/lualib/admin/locales/init.lua @@ -0,0 +1,4 @@ +return { + ["ZH-CN"] = require "admin.locales.ZH-CN", -- 中文 + ["EN-US"] = require "admin.locales.EN-US", -- 英文 +} diff --git a/lualib/admin/token.lua b/lualib/admin/token.lua new file mode 100644 index 00000000..4057fe1c --- /dev/null +++ b/lualib/admin/token.lua @@ -0,0 +1,36 @@ +local config = require 'admin.config' + +local crypt = require 'crypt' +local xor_str = crypt.xor_str +local hexencode = crypt.hexencode +local hexdecode = crypt.hexdecode + +local sys = require "sys" +local now = sys.now +local match = string.match +local concat = table.concat + +local token = {} + +-- token加密与序列化 +function token.encode (str) + return hexencode(xor_str(str, config.secure)):upper() +end + +-- token解密与反序列化 +function token.decode (token) + return xor_str(hexdecode(token:lower()), config.secure) +end + +-- 生成 token +function token.generate (uid) + return token.encode(concat({uid, now()}, ':')) +end + +-- 解析token +-- function token.parser (token) +-- local str = token.decode(token) +-- return match(str, '([%d]+):([%d%.]+)') +-- end + +return token diff --git a/lualib/admin/utils/init.lua b/lualib/admin/utils/init.lua new file mode 100644 index 00000000..3647d9d3 --- /dev/null +++ b/lualib/admin/utils/init.lua @@ -0,0 +1,90 @@ +local template = require "template" +local config = require "admin.config" + +local type = type +local assert = assert +local ipairs = ipairs + +local split = string.sub +local find = string.find +local concat = table.concat + + +local utils = {} + +-- 页面验证失败 +function utils.access_deny (path) + return template.compile("lualib/admin/html/deny.html") { + path = path or "unknown", + cdn = config.cdn, + } +end + +-- 页面重定向 +function utils.redirect(path, args) + assert(path ~= '' or type(path) ~= 'string' , '试图传递一个非法的path') + assert(not args or type(args) == 'table' , '试图传递一个非法的args') + local tab, arguments = { }, nil + if args then + for _, arg in ipairs(args) do + tab[#tab+1] = concat(arg, '=') + end + end + local arguments = concat(tab, '&') + return template.compile('lualib/admin/html/redirect.html'){ + path = (path or config.github) .. arguments, + } +end + +-- 404错误页 +function utils.error_404(location) + return template.compile('lualib/admin/html/404.html'){ + cdn = config.cdn, + locale = config.locales[config.locale], + location = location or config.github, + } +end + +-- 获取页面url +function utils.get_path (content) + return split(content['path'], 1, (find(content['path'], '?') or 0) - 1) +end + +-- 获取语言 +function utils.get_locale (lang) + local locale = config.locales[lang] + if locale then + return locale + end + return config.locales[config.locale] +end + +-- 用户是否包含此权限 +function utils.user_have_permission (permissions, id) + for _, permission in ipairs(permissions) do + if permission.menu_id == id then + return true + end + end + return false +end + +-- 角色权限组是否已经有此权限 +function utils.role_already_selected (permissions, id) + for _, permission in ipairs(permissions) do + if permission.menu_id == id then + return true + end + end + return false +end + +-- 消除<标签>内容 +function utils.escape_script (str) + if type(str) == 'string' then + str = str:gsub('<[^><]*>', '') + end + return str +end + +return utils diff --git a/lualib/admin/view.lua b/lualib/admin/view.lua new file mode 100644 index 00000000..4edfbf1e --- /dev/null +++ b/lualib/admin/view.lua @@ -0,0 +1,124 @@ +local template = require "template" +local utils = require "admin.utils" +local Cookie = require "admin.cookie" +local config = require "admin.config" +local user = require "admin.db.user" +local httpctx = require "admin.httpctx" +local user_token = require "admin.db.token" +local permission = require "admin.db.permission" + +local log = require "logging" +local LOG = log:new { dump = true, path = "admin-view" } + +local type = type +local pcall = pcall +local assert = assert + +local get_path = utils.get_path +local redirect = utils.redirect +local get_locale = utils.get_locale +local access_deny = utils.access_deny + +-- 用户自定义view页面需要验权 +local function verify_permission (content, db) + local token = Cookie.getCookie("CFTOKEN") + if not token then + return false, redirect(config.login_render) + end + -- 无效token则需要登录 + local info = user_token.token_to_userinfo(db, token) + if not info then + return false, redirect(config.login_render) + end + -- 有效token需要验证访问权限 + if info.is_admin ~= 1 then + local path = get_path(content) + if path ~= config.home and not permission.user_have_menu_permission(db, info.id, path) then + return false, access_deny(path) + end + end + return true +end + +local view = {} + +-- 页面路由 +function view.use (path, f) + assert(type(path) == 'string', 'view use path failed.') + assert(type(f) == 'function', 'view use handle failed.') + local db, app = config.db, config.app + assert(db and app, "view.use need db session and http context.") + return app:use(path, function (content) + local ok, info = verify_permission(content, db) + if not ok then + return info + end + if not config.cache then + template.cache = {} + end + local ok, html = pcall(f, httpctx:new{content = content}, db) + if not ok then + LOG:ERROR(html) + end + return html + end) +end + +-- 接口路由 +function view.api (path, f) + assert(type(path) == 'string', 'view api path failed.') + assert(type(f) == 'function', 'view api handle failed.') + local db, app = config.db, config.app + assert(db and app, "view.api need db session and http context.") + return app:api(path, function (content) + local ok, res = pcall(f, httpctx:new{content = content}, db) + if not ok then + LOG:ERROR(res) + end + return res + end) +end + +-- 首页 +function view.home(path, f) + assert(type(path) == 'string', 'view use path failed.') + assert(type(f) == 'function', 'view use handle failed.') + config.home = path + local db, app = config.db, config.app + assert(db and app, "view.home need db session and http context.") + return app:use(path, function (content) + local token = Cookie.getCookie("CFTOKEN") + if not token then + return false, config.login_render + end + local info = user_token.token_to_userinfo(db, token) + if not info then + return redirect(config.login_render) + end + local ok, res = pcall(f, httpctx:new{content = content}, db) + if not ok then + LOG:ERROR(res) + end + return res + end) +end + +-- 获取当前用户语言表 +function view.get_locale () + return get_locale(Cookie.getCookie("CFLANG")) +end + +-- 获取静态文件前缀 +function view.get_cdn () + return config.cdn +end + +-- 模板渲染 +function view.template( ... ) + if not config.cache then + template.cache = {} + end + return template.compile(...) +end + +return view diff --git a/lualib/aio/init.lua b/lualib/aio/init.lua new file mode 100644 index 00000000..4d77cebb --- /dev/null +++ b/lualib/aio/init.lua @@ -0,0 +1,585 @@ +local class = require "class" + +local tcp = require "tcp" +local tcp_read = tcp.read +local tcp_close = tcp.close + +local sys = require "sys" +local new_tab = sys.new_tab + +local cf = require "cf" +local co_new = coroutine.create +local co_self = cf.self +local co_wait = cf.wait +local co_wakeup = cf.wakeup + +local laio = require "laio" +local aio_open = laio.open +local aio_stat = laio.stat +local aio_create = laio.create +local aio_flush = laio.flush +local aio_fflush = laio.fflush +local aio_remove = laio.remove +local aio_read = laio.read +local aio_write = laio.write +local aio_close = laio.close +local aio_rmdir = laio.rmdir +local aio_mkdir = laio.mkdir +local aio_rename = laio.rename +local aio_readdir = laio.readdir +local aio_readpath = laio.readpath +local aio_truncate = laio.truncate +local aio_popen = laio.popen +local aio_kill = laio.kill + +local type = type +local assert = assert +local toint = math.tointeger +local tconcat = table.concat + +local aio = new_tab(0, 128) + +local File = class("__AIO__") + +function File:ctor(opt) + self.fd = opt.fd + self.path = opt.path + self.stat = opt.stat + self.status = "open" +end + +-- 触发gc时检查是否回收self.fd +function File:__gc() + if self.fd and self.status == "open" then + return self:close() + end + return true +end + +-- 重置标志位 +function File:lseek(read_offset, write_offset) + if read_offset then + self:read_lseek(read_offset) + end + if write_offset then + self:write_lseek(write_offset) + end +end + +-- 重置read_offset; 这个方法一般情况下不会用到, 除非你非常明白自己在做什么. +function File:read_lseek(read_offset) + if toint(read_offset) and toint(read_offset) >= 0 then + self.read_offset = read_offset + end +end + +-- 重置write_offset; 这个方法一般情况下不会用到, 除非你非常明白自己在做什么. +function File:write_lseek(write_offset) + if toint(write_offset) and toint(write_offset) >= 0 then + self.write_offset = write_offset + end +end + +-- 读取文件指定大小内容; 除非调用read_lseek重置位置或有新内容写入, 否则超出文件长度后将会返回空字符串. +function File:read( bytes ) + if self.status == "closed" then + return nil, "File already Closed." + end + bytes = toint(bytes) + if not bytes or bytes <= 0 then + if bytes == 0 then + return "", 0 + end + return nil, "Invalid file read bytes." + end + self.__READ__ = assert(not self.__READ__, "[File:read/readall ERROR] : This method cannot be called concurrently in multiple coroutines.") + local stat, err = aio.stat(self.path) + if not stat then + self.__READ__ = nil + return nil, err + end + self.stat = stat + -- 这一段的意思是: 当存在offset则取offset, 否则将offset置0; 无特殊情况不需要改动此地方 + self.read_offset = stat.size - (stat.size - (toint(self.read_offset) and toint(self.read_offset) > 0 and toint(self.read_offset) or 0)) + local data, err = aio._read(self.fd, bytes, self.read_offset) + self.read_offset = self.read_offset + #data + self.__READ__ = nil + return data, err +end + +-- 读取文件所有内容; 除非调用read_lseek重置位置或有新内容写入, 否则超出文件长度后将会返回空字符串. +function File:readall() + if self.status == "closed" then + return nil, "File already Closed." + end + self.__READ__ = assert(not self.__READ__, "[File:read/readall ERROR] : This method cannot be called concurrently in multiple coroutines.") + local stat, err = aio.stat(self.path) + if not stat then + self.__READ__ = nil + return nil, err + end + self.stat = stat + -- 这一段的意思是: 当存在offset则取offset, 否则将offset置0; 无特殊情况不需要改动此地方 + self.read_offset = stat.size - (stat.size - (toint(self.read_offset) and toint(self.read_offset) > 0 and toint(self.read_offset) or 0)) + local data, err = aio._read(self.fd, toint(self.stat.size), self.read_offset) + self.read_offset = self.read_offset + #data + self.__READ__ = nil + return data, err +end + +-- 写入文件 +function File:write( data ) + if self.status == "closed" then + return nil, "File already Closed." + end + if type(data) ~= 'string' or data == "" then + return nil, "Invalid file write data." + end + self.__WRITE__ = assert(not self.__WRITE__, "[File:write ERROR] : This method cannot be called concurrently in multiple coroutines.") + -- 如果没有构建write_offset, 则需要通过stat构建; 否则永远只从尾部开始写入. + if not self.write_offset then + if not self.stat then + self.stat = aio.stat(self.path) + end + self.write_offset = self.stat.size + end + local size, err = aio._write(self.fd, data, self.write_offset) + if type(size) == 'number' then + self.stat.size = self.stat.size + size + self.write_offset = self.write_offset + size + end + self.__WRITE__ = nil + return size, err +end + +-- function + +-- 刷新缓存 +function File:flush() + if self.status == "closed" then + return nil, "File already Closed." + end + self.__FLUSH__ = assert(not self.__FLUSH__, "[File:flush ERROR] : This method cannot be called concurrently in multiple coroutines.") + local ok, err = aio.flush(self.fd) + self.__FLUSH__ = nil + return ok, err +end + +-- 清空文件 +function File:clean() + if self.status == "closed" then + return nil, "File already Closed." + end + self.__CLEAN__ = assert(not self.__CLEAN__, "[File:clean ERROR] : This method cannot be called concurrently in multiple coroutines.") + local ok, err = aio.truncate(self.path, 0) + if not ok then + self.__CLEAN__ = nil + return nil, err + end + local stat, err = aio.stat(self.path) + if type(stat) ~= 'table' then + self.__CLEAN__ = nil + return nil, err + end + if toint(self.read_offset) then + self.read_offset = 0 + end + if toint(self.write_offset) then + self.write_offset = 0 + end + self.stat = stat + self.__CLEAN__ = nil + return true +end + +-- 关闭文件描述符 +function File:close( ... ) + if self.status == "closed" then + return nil, "File already closed." + end + local fd = self.fd + self.fd = nil + self.status = "closed" + tcp_close(fd) +end + +-- 打开文件,并返回File(始终以rw模式打开, 没有则会创建) +function aio.open(filename) + local fd, err = aio._open(filename) + if not fd then + return nil, err + end + local stat, err = aio.stat(filename) + if not stat then + local t = new_tab(0, 3) + t.current_co = co_self() + t.event_co = co_new(function ( ok, err ) + aio[t] = nil + return co_wakeup(t.current_co, ok, err) + end) + aio[t] = true + aio_close(t.event_co, fd) + return co_wait() + end + return File:new { fd = fd, path = filename } +end + +-- 打开文件,并返回fd(始终以rw模式打开, 没有则会创建) +function aio._open(filename) + filename = assert(type(filename) == 'string' and filename ~= '' and filename ~= '.' and filename ~= '..' and filename, "Invalid filename.") + local t = new_tab(0, 3) + t.current_co = co_self() + t.event_co = co_new(function ( fd, err) + aio[t] = nil + return co_wakeup(t.current_co, fd, err) + end) + aio[t] = true + aio_open(t.event_co, filename) + return co_wait() +end + +-- 打开文件, 如果不存在则创建(返回File) +function aio.create(filename) + local fd, err = aio._create(filename) + if not fd then + return nil, err + end + local stat, err = aio.stat(filename) + if not stat then + local t = new_tab(0, 3) + t.current_co = co_self() + t.event_co = co_new(function ( ok, err ) + aio[t] = nil + return co_wakeup(t.current_co, ok, err) + end) + aio[t] = true + aio_close(t.event_co, fd) + return co_wait() + end + return File:new { fd = fd, path = filename, stat = stat } +end + +-- 打开文件, 如果存在则失败(返回fd) +function aio._create(filename) + filename = assert(type(filename) == 'string' and filename ~= '' and filename ~= '.' and filename ~= '..' and filename, "Invalid filename.") + local t = new_tab(0, 3) + t.current_co = co_self() + t.event_co = co_new(function ( fd, err) + aio[t] = nil + return co_wakeup(t.current_co, fd, err) + end) + aio[t] = true + aio_create(t.event_co, filename) + return co_wait() +end + +-- 读取指定字节 +function aio._read(fd, bytes, offset) + fd = assert(toint(fd) and toint(fd) >= 0 and toint(fd), "Invalid fd.") + bytes = assert(toint(bytes) and toint(bytes) >= 0 and toint(bytes), "Invalid read bytes.") + offset = assert(toint(offset) and toint(offset) >= 0 and toint(offset), "Invalid read offset.") + local t = new_tab(0, 3) + t.current_co = co_self() + t.event_co = co_new(function ( data, size ) + aio[t] = nil + return co_wakeup(t.current_co, data, size) + end) + aio[t] = true + aio_read(t.event_co, fd, bytes, offset) + return co_wait() +end + +-- 写入(追加)指定大小数据 +function aio._write(fd, data, offset) + fd = assert(toint(fd) and toint(fd) >= 0 and toint(fd), "Invalid fd.") + data = assert(type(data) == 'string' and data ~= '' and data, "Invalid write data.") + local t = new_tab(0, 3) + t.current_co = co_self() + t.event_co = co_new(function ( size, err ) + aio[t] = nil + return co_wakeup(t.current_co, size, err) + end) + aio[t] = true + aio_write(t.event_co, fd, data, offset) + return co_wait() +end + +-- 仅关闭fd +function aio._close(fd) + fd = assert(toint(fd) and toint(fd) >= 0 and toint(fd), "Invalid fd.") + local t = new_tab(0, 3) + t.current_co = co_self() + t.event_co = co_new(function ( ok, err ) + aio[t] = nil + return co_wakeup(t.current_co, ok, err) + end) + aio[t] = true + aio_close(t.event_co, fd) + return co_wait() +end + +-- 创建指定文件夹 +function aio.mkdir(dir) + dir = assert(type(dir) == 'string' and dir ~= '' and dir, "Invalid folder.") + local t = new_tab(0, 3) + t.current_co = co_self() + t.event_co = co_new(function ( ok, err ) + aio[t] = nil + return co_wakeup(t.current_co, ok, err) + end) + aio[t] = true + aio_mkdir(t.event_co, dir) + return co_wait() +end + +-- 删除指定文件夹 +function aio.rmdir(dir) + dir = assert(type(dir) == 'string' and dir ~= '' and dir, "Invalid folder.") + local t = new_tab(0, 3) + t.current_co = co_self() + t.event_co = co_new(function ( ok, err ) + aio[t] = nil + return co_wakeup(t.current_co, ok, err) + end) + aio[t] = true + aio_rmdir(t.event_co, dir) + return co_wait() +end + +-- 获取文件/文件夹状态 +function aio.attributes(path) + return aio.stat(path) +end + +-- 获取文件/文件夹状态 +function aio.stat(path) + local t = new_tab(0, 3) + t.current_co = co_self() + t.event_co = co_new(function ( list, err ) + aio[t] = nil + return co_wakeup(t.current_co, list, err) + end) + aio[t] = true + aio_stat(t.event_co, assert(type(path) == 'string' and path ~= '' and path, "Invalid path.")) + return co_wait() +end + +-- 获取文件夹下所有文件(文件夹) +function aio.dir(path) + path = assert(type(path) == 'string' and path ~= '' and path, "Invalid path.") + local t = new_tab(0, 3) + t.current_co = co_self() + t.event_co = co_new(function ( dirs ) + aio[t] = nil + return co_wakeup(t.current_co, dirs ) + end) + aio[t] = true + aio_readdir(t.event_co, path) + return co_wait() +end + +-- 创建指定文件 +function aio.rename(old_name, new_name) + old_name = assert(type(old_name) == 'string' and old_name ~= '' and old_name, "Invalid old_name.") + new_name = assert(type(new_name) == 'string' and new_name ~= '' and new_name, "Invalid new_name.") + local t = new_tab(0, 3) + t.current_co = co_self() + t.event_co = co_new(function ( ok ) + aio[t] = nil + return co_wakeup(t.current_co, ok) + end) + aio[t] = true + aio_rename(t.event_co, old_name, new_name) + return co_wait() +end + +-- 获取当前目录完整路径 +function aio.currentdir(...) + return aio.readpath(".") +end + +-- 获取指定目录完整路径 +function aio.readpath(path) + path = assert(type(path) == "string" and path ~= "" and path, "Invalid read path.") + local t = new_tab(0, 3) + t.current_co = co_self() + t.event_co = co_new(function ( path ) + aio[t] = nil + return co_wakeup(t.current_co, path ) + end) + aio[t] = true + aio_readpath(t.event_co, path) + return co_wait() +end + +-- 清空文件或者缩减文件内容到指定大小. 当length为0或者nil的时候将会清空文件. +-- 注意: 这个操作是非常危险的, 您需要非常清楚自己的做什么. +function aio.truncate(filename, length) + filename = assert(type(filename) == 'string' and filename ~= '' and filename, "Invalid filename.") + local t = new_tab(0, 3) + t.current_co = co_self() + t.event_co = co_new(function ( ok, err ) + aio[t] = nil + return co_wakeup(t.current_co, ok, err) + end) + aio[t] = true + aio_truncate(t.event_co, filename, (toint(length) and toint(length) > 0) and toint(length) or 0) + return co_wait() +end + +-- 刷新fd缓存 +function aio.flush(fd) + fd = assert(toint(fd) and toint(fd) >= 0 and toint(fd), "Invalid fd.") + local t = new_tab(0, 3) + t.current_co = co_self() + t.event_co = co_new(function ( ok, err ) + aio[t] = nil + return co_wakeup(t.current_co, ok, err) + end) + aio[t] = true + aio_flush(t.event_co, fd) + return co_wait() +end + +-- 刷新FILE指针缓存 +function aio.fflush(file) + local t = new_tab(0, 3) + t.current_co = co_self() + t.event_co = co_new(function ( ok, err ) + aio[t] = nil + return co_wakeup(t.current_co, ok, err) + end) + aio[t] = aio_fflush(t.event_co, file) + return co_wait() +end + +-- 移除文件或文件夹 +function aio.remove(filename) + local t = new_tab(0, 3) + t.current_co = co_self() + t.event_co = co_new(function ( ok, err ) + aio[t] = nil + return co_wakeup(t.current_co, ok, err) + end) + aio[t] = aio_remove(t.event_co, filename) + return co_wait() +end + +-- `pfile`对象 +local pfile = { __name = "__PFILE__" } + +pfile.__index = pfile + +function pfile:read(bytes) + if not self.fd then + return false, "fd closed." + end + bytes = toint(bytes) + if bytes and bytes > 0 then + return tcp_read(self.fd, bytes) + end + bytes = 65535 + local buffers = {} + while true do + local buf = tcp_read(self.fd, bytes) + if not buf then + self:close() + break + end + buffers[#buffers+1] = buf + end + return tconcat(buffers) +end + +function pfile:__gc() + self:close() +end + +function pfile:close() + if self.fd then + tcp_close(self.fd) + self.fd = nil + end +end + +---comment @`io.popen`的非阻塞版实现 +---@param command string @`command`是一个`string`类型的参数, 它是用于执行的shell命令; +---@param timeout number @`timeout`是一个`Number`类型的参数(可选), 可以指定合适的超时时间来控制进程运行时长. +---@return table|boolean @子进程在退出后此方法才会返回; 正常退出返回`pfile`对象, 异常退出与错误退出返回`false`与出错信息; +---@return nil|string @需要注意的是`pfile`对象必须开发者手动关闭, 否则可能会造成`fd`泄漏的问题. +function aio.popen(command, timeout) + local ok, obj, co_timer, killed + local co = co_self() + local co_cb = co_new(function (id) + -- 正常结束返回`0`, 异常结束返回`进程id`, 超时`kill`返回信号代码(9); + -- print("进程结束: ", id) + if co_timer then + co_timer:stop() + end + return co_wakeup(co, id == 0 and true or false) + end) + ok, obj = pcall(aio_popen, command, co_cb) + if not ok then + return false, "[AIO_POPEN ERROR] : " .. obj + end + co_timer = cf.timeout(tonumber(timeout), function () + aio_kill(obj.pid) + killed = true + co_timer = nil + end) + tcp_close(obj.pipe[2]) + local f = setmetatable({ pid = obj.pid, fd = obj.pipe[1] }, pfile) + -- 等待子进程运行结束: 如果运行失败则返回错误信息, 如果运行成功则返回需要自己读取与手动关闭的`pfile`对象. + if not co_wait() then + local errinfo = f:read "*a" + if killed then + errinfo = "command timeout killed." + end + f:close() + obj = nil + return false, "[AIO_POPEN ERROR] : " .. errinfo + end + obj = nil + return f +end + +---comment @`os.execute`的非阻塞版本实现, 它只执行期间也不会阻塞其它协程. +---@param command string @`command`是一个`string`类型的参数, 它是用于执行的shell命令; +---@param timeout number @`timeout`是一个`Number`类型的参数(可选), 可以指定合适的超时时间来控制进程运行时长. +function aio.execute(command, timeout) + local ok, obj, co_timer + local co = co_self() + local co_cb = co_new(function (id) + -- 正常结束返回`0`, 异常结束返回`进程id`, 超时`kill`返回信号代码(9); + -- print("进程结束: ", id) + if co_timer then + co_timer:stop() + end + return co_wakeup(co, id == 0 and true or false) + end) + ok, obj = pcall(aio_popen, command, co_cb) + if not ok then + return false, "[AIO_POPEN ERROR] : " .. obj + end + co_timer = cf.timeout(tonumber(timeout), function () + aio_kill(obj.pid) + co_timer = nil + end) + ok = co_wait() + local reader, writer = setmetatable({ fd = obj.pipe[1] }, pfile), setmetatable({ fd = obj.pipe[2] }, pfile) + writer:close() + print(reader:read "*a") + reader:close(); + obj = nil + return ok, "exit", ok and 0 or 1 +end + +---comment `kill`指定进程 +---@param pid integer @进程的`PID` +---@param signum integer @信号的`num` +function aio.kill(pid, signum) + aio_kill(pid, signum) + return true +end + +return aio diff --git a/lualib/cf/init.lua b/lualib/cf/init.lua index 5586e125..d1531ef2 100644 --- a/lualib/cf/init.lua +++ b/lualib/cf/init.lua @@ -1,54 +1,125 @@ local Co = require "internal.Co" -local self = Co.self -local fork = Co.spwan -local wait = Co.wait -local wakeup = Co.wakeup +local co_self = Co.self +local co_fork = Co.spawn +local co_wait = Co.wait +local co_wakeup = Co.wakeup local Timer = require "internal.Timer" -local at = Timer.at -local sleep = Timer.sleep +local time_at = Timer.at +local time_sleep = Timer.sleep local time_out = Timer.timeout +local type = type +local error = error +local ipairs = ipairs +local tonumber = tonumber +local loadfile = loadfile + +local loadfilex = package.searchers[2] + local cf = {} --- 创建一个由cf管理的超时器 +---comment 指定的`filename`文件内容为入口创建一个协程. +---@param filename string @可被`loadfile`或`require`加载的代码文件. +---@param ... any @可选传递的可变参. +function cf.run(filename, ...) + local f, errinfo = loadfile(filename, 'bt') + if not f then + f = loadfilex(filename) + if type(f) ~= 'function' then + return assert(nil, '\n' .. errinfo .. ' and ' .. f ) + end + end + return cf.fork(f, ...) +end + +---comment 创建一个由cf管理的超时器(只会触发一次) +---@param timeout number @`timeout`大于0才会创建定时器. +---@param func function @时间到期后将会调用此函数. +---@return Timer? @返回一个`timer`对象, 调用`timer:stop()`方法可停止. function cf.timeout(timeout, func) return time_out(timeout, func) end --- 创建一个由cf管理的循环定时器 -function cf.at(repeats, func) - return at(repeats, func) +---comment 创建一个由cf管理的循环定时器(需要手动停止) +---@param timeout number @`timeout`大于0才会创建定时器. +---@param func function @间隔时间到期后将会调用此函数. +---@return Timer? @返回一个`timer`对象, 调用`timer:stop()`方法可停止. +function cf.at(timeout, func) + return time_at(timeout, func) end --- 协程休眠指定时间 -function cf.sleep(time) - return sleep(time) +---comment 让出协程执行权 +local function yield () + local co = co_self() + co_fork(function () + co_wakeup(co) + end) + return co_wait() end +cf.yield = yield +---comment 让出当前协程执行权并休眠`timeout`秒 +---@param timeout number @`timeout`大于0才会创建定时器. +function cf.sleep(timeout) + if timeout and timeout > 0 then + return time_sleep(timeout) + end + return yield() +end +---comment 获取调用此方法的协程对象. +---@return thread +---@return boolean function cf.self () - return self() + return co_self() end --- 让出协程 +---comment 让出协程 function cf.wait() - return wait() + return co_wait() end --- 创建一个由cf框架调度的协程 +---comment 创建一个由cf框架调度的协程 +---@param func function @协程的执行的函数 +---@return thread 协程对象 function cf.fork(func, ...) - return fork(func, ...) + return co_fork(func, ...) end --- 唤醒一个由cf框架创建的协程 +---comment 唤醒一个由cf框架创建的协程 +---@param co thread @被唤醒的协程对象与需要传递给协程的参数 +---@return nil function cf.wakeup(co, ...) - return wakeup(co, ...) + return co_wakeup(co, ...) +end + +local function cb(f, ctx) + local ok, errinfo = pcall(f) + ctx.waits = ctx.waits - 1 + if ctx.waits == 0 then + co_wakeup(ctx.co) + end + if not ok then + return error(errinfo) + end end --- 使用cf内置dns来解析域名, version表示想得到ipv6回应还是ipv4回应 -function cf.resolve(domain, version) - -- TODO +---comment 并发执行多协程, 指定的协程都结束后返回. +---@param fn function @至少一个或多个任务函数 +---@return nil @始终不存在返回值. +function cf.join(fn, ...) + local qlist = {fn, ...} + local ctx = { co = co_self(), waits = nil } + local waits = 0 + for _, f in ipairs(qlist) do + if type(f) == 'function' then + co_fork(cb, f, ctx) + waits = waits + 1 + end + end + ctx.waits = waits + return co_wait() end return cf diff --git a/lualib/class/init.lua b/lualib/class/init.lua index f762fe41..72eda340 100644 --- a/lualib/class/init.lua +++ b/lualib/class/init.lua @@ -1,29 +1,107 @@ --- a minimal class implementation --- 一个精简版的类实现 -function class(cls_name) - local cls = { } - cls.__name = cls_name - cls.__index = cls - cls.__call = function (cls, ...) - local call = cls[cls.__name] - if call then - return call(cls, ...) - end - return +local pairs, type = pairs, type +local getmetatable = getmetatable +local setmetatable = setmetatable + +local V = tonumber(_VERSION:sub(4)) + +-- 这里定义的字段不会被主动继承 +local inheritance = { __name = true, __index = true, __metatable = true } + +---@class META +local Anonymous = { __name = "Anonymous", ctor = nil } + +---comment 索引自身 +Anonymous.__index = Anonymous + +---comment 字符串化 +function Anonymous.__tostring(M, ...) + if V and V > 5.3 then + return string.format("%s: %p", M.__name, M) + end + return M.__name +end + +local function class_init(M, ...) + if getmetatable(M) ~= Anonymous then + error("[class error]: You must use the `cls(...)` to create an object.", 2) + end + local obj = setmetatable({}, M) + local ctor = obj.ctor + if type(ctor) == 'function' then + ctor(obj, ...) + end + return obj +end + +---comment 使用cls(...)语法创建对象 +---@generic T : META +---@param M META +---@param ... any +---@return T +function Anonymous.__call(M, ...) + return class_init(M, ...) +end + +---comment 使用cls:new(...)语法创建对象 +---@generic T : META +---@param M META +---@param ... any +---@return T +---@deprecated +function Anonymous.new(M, ...) + return class_init(M, ...) +end + +---comment 此函数返回`Lua`类, 所有内部`class`实现均使用它. +---@param cname string? @自定义类名(可选) +---@param cmeta META? @继承的父类(可选) +---@return META +local function class_define(cname, cmeta) + local cls = {} + if cmeta and getmetatable(cmeta) == Anonymous then + for k, v in pairs(cmeta) do + if not inheritance[k] then + cls[k] = v + end + end + end + cls.__index = cls; cls.__name = cname; + return setmetatable(cls, Anonymous) +end + +---@class ClassWraper +local class = { __record = false } +class.__index = class + +function class:__call(cname, cmeta) + -- 记录类信息 + if self.__record then + local list = self.__list__ + if not self.__list__ then + list = {} + self.__list__ = list end - cls.new = function (c, ...) - if cls ~= c then - return print("Please use ':' to create new object :)") - end - local t = {} - if not c.ctor then - print("Can't find ctor to init.") - else - c.ctor(t, ...) - end - return setmetatable(t, cls) + local info = debug.getinfo(2) + if cname and cmeta then + list[#list+1] = string.format("class `%s`: public `%s` (%s:%s)", cname, tostring(cmeta):gsub(':(.*)', ''), info.short_src, info.currentline) + else + list[#list+1] = string.format("class `%s` (%s:%s)", cname and tostring(cname) or 'Unknown', info.short_src, info.currentline) end - return cls + end + -- 记录类信息 + return class_define(cname, cmeta) +end + +---comment 返回对象引用信息数组 +---@return table +function class:dump() + return self.__list__ +end + +---comment 当前服务对象注册记录 +---@param op boolean @默认为`false` +function class:enable_recorded(op) + self.__record = op end -return class \ No newline at end of file +return setmetatable({}, class) \ No newline at end of file diff --git a/lualib/class/meta.lua b/lualib/class/meta.lua new file mode 100644 index 00000000..a5eac0c0 --- /dev/null +++ b/lualib/class/meta.lua @@ -0,0 +1,26 @@ +local assert, type = assert, type +local getmetatable = getmetatable +local setmetatable = setmetatable + +--`Anonymous`是所有对象的基类. +local Anonymous = { __META__ = true, __name = "Anonymous" } + +Anonymous.__index = Anonymous + +Anonymous.new = function (M, ...) + assert(Anonymous == getmetatable(M), "[Lua-CLASS ERROR]: Must use `:` to create object.") + local ctor = M.ctor + local obj = setmetatable({}, M) + assert(ctor and type(ctor) == 'function' and ctor, "[Lua-CLASS ERROR]: Can't find `ctor` to init.")(obj, ...) + return obj +end + +Anonymous.__call = function (M, ...) + local meta = getmetatable(M) + if meta == Anonymous then + return M:new(...); + end + return assert(getmetatable(M) == Anonymous and M['__name'], "[Lua-CLASS ERROR]: Invalid class arguments.")(M, ...); +end + +return Anonymous \ No newline at end of file diff --git a/lualib/cloud/README.md b/lualib/cloud/README.md new file mode 100644 index 00000000..c3aa7152 --- /dev/null +++ b/lualib/cloud/README.md @@ -0,0 +1,27 @@ +# 云平台Lua SDK + + 框架提供一部分云平台服务调用的内置Lua版SDK. + +## 七牛 SDK + + * 存储服务 + + * 短信服务 + + * 内容审核 + + * 直播服务 + +## 腾讯 SDK + + * 位置服务 + +## paypal SDK + + * 支付业务 + +## 翻译SDK + + * 百度翻译 + + * 有道翻译 \ No newline at end of file diff --git a/lualib/cloud/location/amap.lua b/lualib/cloud/location/amap.lua new file mode 100644 index 00000000..6ccad098 --- /dev/null +++ b/lualib/cloud/location/amap.lua @@ -0,0 +1,71 @@ +local httpc = require "httpc" +local crypt = require "crypt" +local md5 = crypt.md5 +local urlencode = crypt.urlencode + +local pairs = pairs +local ipairs = ipairs +local sort = table.sort +local concat = table.concat + +--[[ + + 官网: https://lbs.amap.com + + 文档: https://lbs.amap.com/api/webservice/summary + +]] + +local amap = { __Version__ = 0.1, host = "https://restapi.amap.com" } + +-- sn签名 +local function sign(app_key, secret_key, opt) + local keys = {"key"} + for key, value in pairs(opt) do + keys[#keys+1] = key + end + sort(keys) + local args = {} + for _, key in ipairs(keys) do + if key == "key" then + args[#args+1] = "key=" .. app_key + else + args[#args+1] = key .. '=' .. urlencode(opt[key]) + end + end + args[#args+1] = "sig=" .. md5(concat(args, "&"), true) .. secret_key + return concat(args, "&") +end + +-- IP定位 +function amap.ip(key, secret_key, ip) + return httpc.get(amap.host .. "/v3/ip?" .. sign(key, secret_key, { ip = ip, output = "JSON"})) +end + +-- 天气预报 +function amap.weather(key, secret_key, city, extensions) + return httpc.get(amap.host .. "/v3/weather/weatherInfo?" .. sign(key, secret_key, { output = "JSON", city = city, extensions = extensions})) +end + +-- 坐标转换 +function amap.convert(key, secret_key, locations, coordsys) + return httpc.get(amap.host .. "/v3/assistant/coordinate/convert?" .. sign(key, secret_key, { output = "JSON", locations = locations, coordsys = coordsys })) +end + +-- 地理编码 +function amap.geo(key, secret_key, opt) + return httpc.get(amap.host .. "/v3/geocode/geo?" .. sign(key, secret_key, opt)) +end + +-- 行政区域查询 +function amap.district(key, secret_key, opt) + return httpc.get(amap.host .. "/v3/config/district?" .. sign(key, secret_key, opt)) +end + +-- 输入提示 +function amap.tips(key, secret_key, opt) + return httpc.get(amap.host .. "/v3/assistant/inputtips?" .. sign(key, secret_key, opt)) +end + + +return amap \ No newline at end of file diff --git a/lualib/cloud/location/baidu.lua b/lualib/cloud/location/baidu.lua new file mode 100644 index 00000000..dd8086d3 --- /dev/null +++ b/lualib/cloud/location/baidu.lua @@ -0,0 +1,87 @@ +local httpc = require "httpc" +local crypt = require "crypt" +local md5 = crypt.md5 +local urlencode = crypt.urlencode + +local pairs = pairs +local ipairs = ipairs +local sort = table.sort +local concat = table.concat + +--[[ + 官网: https://lbsyun.baidu.com + 文档地址: https://lbsyun.baidu.com/index.php?title=webapi +]] + +local baidu = { __Version__ = 0.1, host = "https://api.map.baidu.com" } + +local function sign(query_str, ak, sk, opt) + local keys = {} + for key, _ in pairs(opt) do + keys[#keys+1] = key + end + sort(keys) + local args = {} + for index, key in ipairs(keys) do + args[#args+1] = key .. "=" .. urlencode(opt[key]) + end + args[#args+1] = "ak=" .. ak + args[#args+1] = "sn=" .. md5(urlencode(query_str .. concat(args, "&") ..sk), true) + return concat(args, "&") +end + +-- 智能硬件定位 +function baidu.locapi(ak, sk, opt) + local path = "/locapi/v2?" + return httpc.get(baidu.host .. path .. sign(path, ak, sk, opt)) +end + +-- 实时路况 +function baidu.road(ak, sk, opt) + local path = "/traffic/v1/road?" + return httpc.get(baidu.host .. path .. sign(path, ak, sk, opt)) +end + +-- 坐标附近上车点 +function baidu.parking(ak, sk, opt) + local path = "/parking/search?" + return httpc.get(baidu.host .. path .. sign(path, ak, sk, opt)) +end + +-- 坐标系转换 +function baidu.convert(ak, sk, opt) + local path = "/geoconv/v1/?" + return httpc.get(baidu.host .. path .. sign(path, ak, sk, opt)) +end + +-- 位置时区 +function baidu.timezone(ak, sk, opt) + local path = "/timezone/v1?" + return httpc.get(baidu.host .. path .. sign(path, ak, sk, opt)) +end + +-- 位置检索 +function baidu.geosearch(ak, sk, opt) + local path = "/place/v2/search?" + return httpc.get(baidu.host .. path .. sign(path, ak, sk, opt)) +end + +-- 位置IP +function baidu.geoip(ak, sk, opt) + local path = "/location/ip?" + return httpc.get(baidu.host .. path .. sign(path, ak, sk, opt)) +end + +-- 坐标检索 +function baidu.geosuggestion(ak, sk, opt) + local path = "/place/v2/suggestion?" + return httpc.get(baidu.host .. path .. sign(path, ak, sk, opt)) +end + +-- 坐标编码 +function baidu.geo(ak, sk, opt) + local path = "/geocoding/v3/?" + return httpc.get(baidu.host .. path .. sign(path, ak, sk, opt)) +end + +return baidu \ No newline at end of file diff --git a/lualib/cloud/location/tencent.lua b/lualib/cloud/location/tencent.lua new file mode 100644 index 00000000..2803b2e0 --- /dev/null +++ b/lualib/cloud/location/tencent.lua @@ -0,0 +1,111 @@ +local httpc = require "httpc" +local crypt = require "crypt" + +local type = type +local pairs = pairs +local ipairs = ipairs +local assert = assert + +--[[ + 文档地址: https://lbs.qq.com/webservice_v1/index.html + 目前所有API接口仅支持SN码签名校验, 请自行在腾讯位置服务端`后台管理`->`key管理`中进行设置获取key并且生成sn码. + 所有接口数据均返回原生http code与json数据, 请开发者自行进行接口判断与json decode. +]] + +local function sign(sn, path, opt) + local key_sorts = {} + for key, value in pairs(opt) do + key_sorts[#key_sorts+1] = key + end + table.sort(key_sorts) + local args = {} + local signs = {} + for index, key in ipairs(key_sorts) do + signs[#signs+1] = key .. '=' .. opt[key] + args[#args+1] = {key, opt[key]} + end + local sig = crypt.md5(path .. '?' .. table.concat(signs, "&") .. sn, true) + args[#args+1] = {"sig", sig} + return args +end + +local tencent = { __Version__ = 0.1 } + +-- IP定位 +--[[ + ip : IP地址 +]] +function tencent.getIpLocation (accesskey, sn, ip) + return httpc.get("https://apis.map.qq.com/ws/location/v1/ip", nil, sign(sn, "/ws/location/v1/ip", { + ip = ip, key = accesskey + })) +end + +-- 获取行政规划区列表 +function tencent.getDistrictList (accesskey, sn) + return httpc.get("https://apis.map.qq.com/ws/district/v1/list", nil, sign(sn, "/ws/district/v1/list", {key = accesskey})) +end + +-- 获取指定行政规划区 +--[[ + id: 父级行政区划ID,缺省时则返回最顶级行政区划 +]] +function tencent.getDistrictChildren (accesskey, sn, id) + return httpc.get("https://apis.map.qq.com/ws/district/v1/getchildren", nil, sign(sn, "/ws/district/v1/getchildren", { + id = id, key = accesskey + })) +end + +-- 关键词猜测(补全) +--[[ + keyword: 搜索/联想/补全关键词 + region: 范围, 如: 广州 + region_fix : 是否固定范围. + location: 不支持 + page_index: 当前是第几页 + page_size: 每页返回数量 +]] +function tencent.searchSuggestion (accesskey, sn, keyword, region, region_fix, page_index, page_size) + return httpc.get("https://apis.map.qq.com/ws/place/v1/suggestion", nil, sign(sn, "/ws/place/v1/suggestion", { + key = accesskey, keyword = keyword, + page_index = page_index or 1, + page_size = page_size or 10, + region = region or '', + region_fix = region_fix or 1, + })) +end + +function tencent.searchPlace (accesskey, sn, keyword, boundary, page_index, page_size, order) + return httpc.get("https://apis.map.qq.com/ws/place/v1/search", nil, sign(sn, "/ws/place/v1/search", { + key = accesskey, + keyword = keyword, + boundary = boundary, + page_index = page_index, + page_size = page_size, + order = order, + })) +end + +-- 关键词搜索行政规划区 +--[[ + keyword: 行政区关键词 +]] +function tencent.searchDistrict (accesskey, sn, keyword) + return httpc.get("https://apis.map.qq.com/ws/district/v1/search", nil, sign(sn, "/ws/district/v1/search", { + key = accesskey, keyword = keyword + })) +end + +-- 距离计算(一对多) +--[[ + mode: driving 驾车, wakling 步行 + from: 起始经纬度 + to: 目的经纬度 +]] +function tencent.getDistance (accesskey, sn, opt) + return httpc.get("https://apis.map.qq.com/ws/distance/v1/", nil, sign(sn, "/ws/distance/v1/", { + key = accesskey, to = opt.to, from = opt.from, mode = opt.mode, + })) +end + +return tencent diff --git a/lualib/cloud/paypal/payment.lua b/lualib/cloud/paypal/payment.lua new file mode 100644 index 00000000..1d35adc2 --- /dev/null +++ b/lualib/cloud/paypal/payment.lua @@ -0,0 +1,62 @@ +local httpc = require "httpc" +local crypt = require "crypt" +local json = require "json" + +--[[ + 1. 申请token + 2. 创建订单 + 3. 用户确认支付 / 取消支付 + 4. 商家确认支付 / 取消支付 + 5. 交易完成 / 取消 +]] + +local Payment = { __Version__ = 0.1 } + +local function basic_auth(username, password) + return "Basic " .. crypt.base64encode(username .. ":" .. password) +end + +local function Bearer_auth (accesstoken) + return "Bearer " .. accesstoken +end + +-- 申请AccessToken +function Payment.getAccessToken (clientid, secret) + return httpc.post("https://api.sandbox.paypal.com/v1/oauth2/token", { + {"Accept", "application/json"}, {"Authorization", basic_auth(clientid, secret)} + }, { {"grant_type", "client_credentials"} }) +end + +-- 创建账单 +function Payment.createPayment (accesstoken, opt) + return httpc.json("https://api.sandbox.paypal.com/v1/payments/payment", { {"Authorization", Bearer_auth(accesstoken)} }, json.encode({ + intent = opt.intent, payer = opt.payer, + transactions = opt.transactions, + not_to_payer = opt.not_to_payer, + redirect_urls = opt.redirect_urls, + })) +end + +-- 确认支付 +function Payment.confirmPayment (accesstoken, payment_id, payer_id) + return httpc.json("https://api.sandbox.paypal.com/v1/payments/payment/".. payment_id .."/execute", { {"Authorization", Bearer_auth(accesstoken)} }, + json.encode({ payer_id = payer_id }) + ) +end + +-- 查询账单 +function Payment.getPaymentDetails (accesstoken, order_id) + return httpc.get("https://api.sandbox.paypal.com/v1/payments/payment/" .. order_id, { {"Authorization", Bearer_auth(accesstoken)} }) +end + +-- 批量查询账单 +function Payment.getPaymentsDetails (accesstoken, opt) + return httpc.get("https://api.sandbox.paypal.com/v1/payments/payment", { {"Authorization", Bearer_auth(accesstoken)} }, { + {"count", opt.count or 10}, -- 分页数量 + {"start_index", opt.start_index or 1}, -- 第几页 + {"sort_by", opt.sort_by or "create_time"}, -- 排序字段, + {"sort_order", opt.sort_order or "desc"}, -- 排序方式, + }) +end + +return Payment diff --git a/lualib/cloud/qiniu/censor.lua b/lualib/cloud/qiniu/censor.lua new file mode 100644 index 00000000..3e27bdea --- /dev/null +++ b/lualib/cloud/qiniu/censor.lua @@ -0,0 +1,34 @@ +local token = require "cloud.qiniu.token" +local httpc = require "httpc" +local json = require "json" + +local Censor = { __Version__ = 0.1, host = "ai.qiniuapi.com" } + +--[[ + 内容审核: image 图片审核, video 视频审核 +]] + +-- 图片内容审核 +function Censor.newImageCensor (AccessKey, SecretKey, image_uri, params) + local path = "/v3/image/censor" + local body = json.encode({ data = { uri = image_uri or "" }, params = params or { scenes = {"pulp", "terror", "politician"} , detail = true } }) + local Authorization = token.newAuthorization(AccessKey, SecretKey, { method = "POST", path = path, host = Censor.host, body = body}) + return httpc.json("https://" .. Censor.host .. path, { {"Authorization", Authorization} }, body) +end + +-- 视频内容审核 +function Censor.newVideoCensor (AccessKey, SecretKey, video_uri, params) + local path = "/v3/video/censor" + local body = json.encode({ data = { uri = video_uri or "" }, params = params or { scenes = {"pulp", "terror", "politician"} , cut_param = { interval_msecs = 5000 } } }) + local Authorization = token.newAuthorization(AccessKey, SecretKey, { method = "POST", path = path, host = Censor.host, body = body}) + return httpc.json("https://" .. Censor.host .. path, { {"Authorization", Authorization} }, body) +end + +-- 根据job_id检查视频审核结果 +function Censor.getVideoCensorResult (AccessKey, SecretKey, job_id) + local path = "/v3/jobs/video/" .. job_id + local Authorization = token.newAuthorization(AccessKey, SecretKey, { method = "GET", path = path, host = Censor.host }) + return httpc.get("https://" .. Censor.host .. path, { {"Authorization", Authorization} }) +end + +return Censor diff --git a/lualib/cloud/qiniu/crypt.lua b/lualib/cloud/qiniu/crypt.lua new file mode 100644 index 00000000..78b6cb15 --- /dev/null +++ b/lualib/cloud/qiniu/crypt.lua @@ -0,0 +1,13 @@ +local crypt = require "crypt" +local base64urlencode = crypt.base64urlencode +local base64urldecode = crypt.base64urldecode + +local Crypt = { + hmac_sha1 = crypt.hmac_sha1, + urlencode = crypt.urlencode, + urldecode = crypt.urldecode, + urlsafe_base64encode = base64urlencode, + urlsafe_base64decode = base64urldecode, +} + +return Crypt \ No newline at end of file diff --git a/lualib/cloud/qiniu/ocr.lua b/lualib/cloud/qiniu/ocr.lua new file mode 100644 index 00000000..7a66c3f3 --- /dev/null +++ b/lualib/cloud/qiniu/ocr.lua @@ -0,0 +1,15 @@ +local token = require "cloud.qiniu.token" +local httpc = require "httpc" +local json = require "json" + +local ocr = { __Version__ = 0.1, host = "ai.qiniuapi.com"} + +-- 身份证识别 +function ocr.idcard(AccessKey, SecretKey, uri) + local path = "/v1/ocr/idcard" + local body = json.encode { data = { uri = uri } } + local Authorization = token.newAuthorization (AccessKey, SecretKey, { method = "POST", path = path, host = ocr.host, body = body }) + return httpc.json("http://ai.qiniuapi.com/v1/ocr/idcard", { {"Authorization", Authorization} }, body) +end + +return ocr \ No newline at end of file diff --git a/lualib/cloud/qiniu/oss.lua b/lualib/cloud/qiniu/oss.lua new file mode 100644 index 00000000..37d56b55 --- /dev/null +++ b/lualib/cloud/qiniu/oss.lua @@ -0,0 +1,11 @@ +local token = require "cloud.qiniu.token" + +local oss = { __Version__ = 0.1, getUploadToken = token.getUploadToken, getDownloadToken = token.getDownloadToken } + +--[[ +此为七牛云对象存储服务的上传与下载Token生成库的原生lua实现. +此库实现了服务端根据指定算法生成临时上传/下载的授权Token后交由客户端上传文件, 服务端不负责具体业务. +具体使用方法请参考: https://developer.qiniu.com/kodo/manual/1644/security +]] + +return oss diff --git a/lualib/cloud/qiniu/sms.lua b/lualib/cloud/qiniu/sms.lua new file mode 100644 index 00000000..d4b10e8a --- /dev/null +++ b/lualib/cloud/qiniu/sms.lua @@ -0,0 +1,213 @@ +local token = require "cloud.qiniu.token" +local httpc = require "httpc" +local crypt = require "crypt" +local json = require "json" + +local type = type + +local sms = { __Version__ = 0.1, newAuthorization = token.newAuthorization } +--[[ +此为七牛云短信服务的lua版实现. +提供了包含短信的发送/记录查询/获取模板/删除模板/获取签名/删除签名/ +]] + +-- 获取短信模板列表 +function sms.getTemplates (AccessKey, SecretKey, opt) + if type(AccessKey) ~= 'string' or AccessKey == '' then + return nil, "invaild AccessKey." + end + if type(SecretKey) ~= 'string' or SecretKey == '' then + return nil, "invaild SecretKey." + end + local args + if opt then + args = {} + if opt.page then + args[#args+1] = 'page=' .. opt.page + end + if opt.page_size then + args[#args+1] = 'page_size=' .. opt.page_size + end + if #args == 0 then + args = nil + end + end + local Authorization = sms.newAuthorization(AccessKey, SecretKey, { method = 'GET', host = 'sms.qiniuapi.com', path = '/v1/template', query = args and '?' .. table.concat(args, '&') or nil }) + local code, ret = httpc.get('https://sms.qiniuapi.com/v1/template'..(args and '?'..table.concat(args, '&') or ''), {{ 'Authorization', Authorization }}) + if code ~= 200 then + return nil, ret + end + return code, json.decode(ret) +end + +-- 删除短信模板列表 +function sms.detTemplate (AccessKey, SecretKey, template_id) + if type(AccessKey) ~= 'string' or AccessKey == '' then + return nil, "invaild AccessKey." + end + if type(SecretKey) ~= 'string' or SecretKey == '' then + return nil, "invaild SecretKey." + end + if type(template_id) ~= 'string' or template_id == '' then + return nil, "invaild template_id." + end + local Authorization = sms.newAuthorization(AccessKey, SecretKey, { method = 'DELETE', host = 'sms.qiniuapi.com', path = '/v1/template/' .. template_id }) + local code, ret = httpc.delete("https://sms.qiniuapi.com" .. '/v1/template/' .. template_id, {{'Authorization', Authorization}}) + if code ~= 200 then + return nil, ret + end + return code, ret +end + +-- 获取短信签名列表 +function sms.getSignatures (AccessKey, SecretKey, opt) + if type(AccessKey) ~= 'string' or AccessKey == '' then + return nil, "invaild AccessKey." + end + if type(SecretKey) ~= 'string' or SecretKey == '' then + return nil, "invaild SecretKey." + end + local args + if opt then + args = {} + if opt.page then + args[#args+1] = 'page=' .. opt.page + end + if opt.page_size then + args[#args+1] = 'page_size=' .. opt.page_size + end + if #args == 0 then + args = nil + end + end + local Authorization = sms.newAuthorization(AccessKey, SecretKey, { method = 'GET', host = 'sms.qiniuapi.com', path = '/v1/signature', query = args and '?' .. table.concat(args, '&') or nil }) + local code, ret = httpc.get('https://sms.qiniuapi.com/v1/signature'..(args and '?'..table.concat(args, '&') or ''), {{ 'Authorization', Authorization }}) + if code ~= 200 then + return nil, ret + end + return code, json.decode(ret) +end + +-- 删除短信签名列表 +function sms.delSignature (AccessKey, SecretKey, signature_id) + if type(AccessKey) ~= 'string' or AccessKey == '' then + return nil, "invaild AccessKey." + end + if type(SecretKey) ~= 'string' or SecretKey == '' then + return nil, "invaild SecretKey." + end + if type(signature_id) ~= 'string' or signature_id == '' then + return nil, "invaild signature_id." + end + local Authorization = sms.newAuthorization(AccessKey, SecretKey, { method = 'DELETE', host = 'sms.qiniuapi.com', path = '/v1/signature/' .. signature_id }) + local code, ret = httpc.delete("https://sms.qiniuapi.com" .. '/v1/signature/' .. signature_id, {{'Authorization', Authorization}}) + if code ~= 200 then + return nil, ret + end + return code, json.decode(ret) +end + +-- 获取短信发送记录 +function sms.getSMSRecord (AccessKey, SecretKey, opt) + if type(AccessKey) ~= 'string' or AccessKey == '' then + return nil, "invaild AccessKey." + end + if type(SecretKey) ~= 'string' or SecretKey == '' then + return nil, "invaild SecretKey." + end + local args + if opt then + args = {} + if opt.page then + args[#args+1] = 'page=' .. opt.page + end + if opt.page_size then + args[#args+1] = 'page_size=' .. opt.page_size + end + if opt.mobile then + args[#args+1] = 'mobile=' .. opt.mobile + end + if opt.status then + args[#args+1] = 'status=' .. opt.status + end + if opt.start then + args[#args+1] = 'start=' .. opt.start + end + if opt['end'] then + args[#args+1] = 'end=' .. opt['end'] + end + if opt.type then + args[#args+1] = 'type=' .. opt.type + end + if opt.job_id then + args[#args+1] = 'job_id=' .. opt.job_id + end + if opt.message_id then + args[#args+1] = 'message_id=' .. opt.message_id + end + if opt.template_id then + args[#args+1] = 'template_id=' .. opt.template_id + end + if #args == 0 then + args = nil + end + end + local Authorization = sms.newAuthorization(AccessKey, SecretKey, { method = 'GET', host = 'sms.qiniuapi.com', path = '/v1/messages', query = args and '?' .. table.concat(args, '&') or nil }) + local code, ret = httpc.get('https://sms.qiniuapi.com/v1/messages'..(args and '?'..table.concat(args, '&') or ''), {{ 'Authorization', Authorization}}) + if code ~= 200 then + return nil, ret + end + return code, json.decode(ret) +end + +-- 发送国内短信 +function sms.sendSMS (AccessKey, SecretKey, opt) + if type(AccessKey) ~= 'string' or AccessKey == '' then + return nil, "invaild AccessKey." + end + if type(SecretKey) ~= 'string' or SecretKey == '' then + return nil, "invaild SecretKey." + end + if type(opt) ~= 'table' or not next(opt) then + return nil, "invaild opt arguments." + end + if type(opt.template_id) ~= 'string' or type(opt.mobiles) ~= 'table' then + return nil, "invaild template_id or mobiles." + end + local body = json.encode({ mobiles = opt.mobiles, template_id = opt.template_id, parameters = opt.parameters }) + local Authorization = sms.newAuthorization(AccessKey, SecretKey, { + method = 'POST', host = 'sms.qiniuapi.com', path = '/v1/message', body = body, + }) + local code, ret = httpc.json('https://sms.qiniuapi.com/v1/message', { { 'Authorization', Authorization } }, body) + if code ~= 200 then + return nil, ret + end + return code, json.decode(ret) +end + +-- 发送国际短信 +function sms.sendOverseaSMS (AccessKey, SecretKey, opt) + if type(AccessKey) ~= 'string' or AccessKey == '' then + return nil, "invaild AccessKey." + end + if type(SecretKey) ~= 'string' or SecretKey == '' then + return nil, "invaild SecretKey." + end + if type(opt) ~= 'table' or not next(opt) then + return nil, "invaild opt arguments." + end + if type(opt.template_id) ~= 'string' or type(opt.mobile) ~= 'string' then + return nil, "invaild template_id or mobiles." + end + local body = json.encode({ mobile = opt.mobile, template_id = opt.template_id, parameters = opt.parameters }) + local Authorization = sms.newAuthorization(AccessKey, SecretKey, { + method = 'POST', host = 'sms.qiniuapi.com', path = '/v1/message/oversea', body = body, + }) + local code, ret = httpc.json('https://sms.qiniuapi.com/v1/message/oversea', { { 'Authorization', Authorization } }, body) + if code ~= 200 then + return nil, ret + end + return code, json.decode(ret) +end + +return sms diff --git a/lualib/cloud/qiniu/stream.lua b/lualib/cloud/qiniu/stream.lua new file mode 100644 index 00000000..258ae17b --- /dev/null +++ b/lualib/cloud/qiniu/stream.lua @@ -0,0 +1,246 @@ +local token = require "cloud.qiniu.token" +local httpc = require "httpc" +local json = require "json" + +local crypt = require "crypt" +local hmac_sha1 = crypt.hmac_sha1 +local urlencode = crypt.urlencode +local base64encode = crypt.base64encode + +local type = type +local time = os.time +local concat = table.concat + +local Stream = { __Version__ = 0.1, domain = "https://pili.qiniuapi.com" } + +-- 创建流 +function Stream.createStream (AccessKey, SecretKey, hub, key) + if type(AccessKey) ~= 'string' or AccessKey == '' then + return nil, "invaild AccessKey." + end + if type(SecretKey) ~= 'string' or SecretKey == '' then + return nil, "invaild SecretKey." + end + if type(hub) ~= 'string' or hub == '' then + return nil, "invaild hub." + end + if type(key) ~= 'string' or key == '' then + return nil, "invaild key." + end + local path = "/v2/hubs/" .. hub .. "/streams" + local Authorization, QboxAuthorization = token.getAccessToken(AccessKey, SecretKey, {path = path}) + return httpc.json(Stream.domain .. path, { {"Authorization", QboxAuthorization } }, json.encode({ {"key", key} })) +end + +-- 查询流 +function Stream.queryStream (AccessKey, SecretKey, hub, key) + if type(AccessKey) ~= 'string' or AccessKey == '' then + return nil, "invaild AccessKey." + end + if type(SecretKey) ~= 'string' or SecretKey == '' then + return nil, "invaild SecretKey." + end + if type(hub) ~= 'string' or hub == '' then + return nil, "invaild hub." + end + if type(key) ~= 'string' or key == '' then + return nil, "invaild key." + end + local path = "/v2/hubs/" .. hub .. "/streams/" .. base64encode(key) + local Authorization, QboxAuthorization = token.getAccessToken(AccessKey, SecretKey, { path = path }) + return httpc.get(Stream.domain.. path, { {"Authorization", QboxAuthorization}, {"Content-Type", "application/x-www-form-urlencoded"} }) +end + +-- 查询流列表 +function Stream.queryStreams (AccessKey, SecretKey, hub, opt) + if type(AccessKey) ~= 'string' or AccessKey == '' then + return nil, "invaild AccessKey." + end + if type(SecretKey) ~= 'string' or SecretKey == '' then + return nil, "invaild SecretKey." + end + if type(hub) ~= 'string' or hub == '' then + return nil, "invaild hub." + end + local args = {} + local querys + if opt.liveonly then + args[#args+1] = {"liveonly", "true"} + end + if opt.prefix then + args[#args+1] = {"prefix", opt.prefix} + end + if opt.marker then + args[#args+1] = {"marker", opt.marker} + end + if opt.limit then + args[#args+1] = {"limit", opt.limit} + end + if #args > 1 then + local query = {} + for _, item in ipairs(args) do + query[#query+1] = urlencode(item[1]) .. '=' .. urlencode(item[2]) + end + querys = concat(query, "&") + end + local path = "/v2/hubs/" .. hub .. "/streams" .. (querys and '?' .. querys or '') + local Authorization, QboxAuthorization = token.getAccessToken(AccessKey, SecretKey, { path = path, args = args }) + return httpc.get(Stream.domain.. path, { {"Authorization", QboxAuthorization}, {"Content-Type", "application/x-www-form-urlencoded"} }) +end + +-- 禁止推流 +function Stream.disableStream (AccessKey, SecretKey, hub, key) + if type(AccessKey) ~= 'string' or AccessKey == '' then + return nil, "invaild AccessKey." + end + if type(SecretKey) ~= 'string' or SecretKey == '' then + return nil, "invaild SecretKey." + end + if type(hub) ~= 'string' or hub == '' then + return nil, "invaild hub." + end + if type(key) ~= 'string' or key == '' then + return nil, "invaild key." + end + local path = "/v2/hubs/" .. hub .. "/streams/" .. base64encode(key) .. '/disabled' + local Authorization, QboxAuthorization = token.getAccessToken(AccessKey, SecretKey, { path = path }) + return httpc.json(Stream.domain.. path, { {"Authorization", QboxAuthorization} }, json.encode({ {"disabledTill", key} })) +end + +-- 获取指定流相关信息 +function Stream.getStreamLive (AccessKey, SecretKey, hub, key) + if type(AccessKey) ~= 'string' or AccessKey == '' then + return nil, "invaild AccessKey." + end + if type(SecretKey) ~= 'string' or SecretKey == '' then + return nil, "invaild SecretKey." + end + if type(hub) ~= 'string' or hub == '' then + return nil, "invaild hub." + end + if type(key) ~= 'string' or key == '' then + return nil, "invaild key." + end + local path = "/v2/hubs/" .. hub .. "/streams/" .. base64encode(key) .. '/live' + local Authorization, QboxAuthorization = token.getAccessToken(AccessKey, SecretKey, { path = path }) + return httpc.get(Stream.domain.. path, { {"Authorization", QboxAuthorization }, {"Content-Type", "application/x-www-form-urlencoded"} }) +end + +-- 批量获取指定流相关信息 +function Stream.getStreamLives (AccessKey, SecretKey, hub, lives) + if type(AccessKey) ~= 'string' or AccessKey == '' then + return nil, "invaild AccessKey." + end + if type(SecretKey) ~= 'string' or SecretKey == '' then + return nil, "invaild SecretKey." + end + if type(hub) ~= 'string' or hub == '' then + return nil, "invaild hub." + end + if type(lives) ~= 'table' or (#lives <= 0 or #lives > 100) then + return nil, "invaild lives. 0 < lives < 100" + end + local path = "/v2/hubs/" .. hub .. "/livestreams" + local Authorization, QboxAuthorization = token.getAccessToken(AccessKey, SecretKey, { path = path }) + return httpc.json(Stream.domain.. path, { {"Authorization", QboxAuthorization } }, json.encode(lives)) +end + +-- 查询直播流历史记录 +function Stream.getStreamHistory (AccessKey, SecretKey, hub, key, opt) + if type(AccessKey) ~= 'string' or AccessKey == '' then + return nil, "invaild AccessKey." + end + if type(SecretKey) ~= 'string' or SecretKey == '' then + return nil, "invaild SecretKey." + end + if type(hub) ~= 'string' or hub == '' then + return nil, "invaild hub." + end + if type(key) ~= 'string' or key == '' then + return nil, "invaild key." + end + local args = "start=" .. (type(opt) == 'table' and opt['start'] or 0) .. "&" .. "end=" .. (type(opt) == 'table' and opt['end'] or 0) + local path = "/v2/hubs/" .. hub .. "/streams/" .. base64encode(key) .. '/historyactivity?' .. args + local Authorization, QboxAuthorization = token.getAccessToken(AccessKey, SecretKey, { path = path, args = args }) + return httpc.get(Stream.domain.. path, { {"Authorization", QboxAuthorization }, {"Content-Type", "application/x-www-form-urlencoded"} }) +end + +-- 生成rtmp推流地址 +function Stream.rtmpPublishUrl (AccessKey, SecretKey, domain, hub, key, expire) + if type(AccessKey) ~= 'string' or AccessKey == '' then + return nil, "invaild AccessKey." + end + if type(SecretKey) ~= 'string' or SecretKey == '' then + return nil, "invaild SecretKey." + end + if type(domain) ~= 'string' or domain == '' then + return nil, "invaild domain." + end + if type(hub) ~= 'string' or hub == '' then + return nil, "invaild hub." + end + if type(key) ~= 'string' or key == '' then + return nil, "invaild key." + end + local path = '/' .. hub .. '/' .. key .. '?' .. ( expire and 'e='..expire or '' ) + local token = AccessKey .. ':' .. base64encode(hmac_sha1(SecretKey, path)) + return "rtmp://" .. domain .. path .. ( expire and '&token=' .. token or 'token=' .. token ) +end + +-- 获取rtmp观播地址 +function Stream.rtmpSubscribeUrl (domain, hub, key) + if type(domain) ~= 'string' or domain == '' then + return nil, "invaild domain." + end + if type(hub) ~= 'string' or hub == '' then + return nil, "invaild hub." + end + if type(key) ~= 'string' or key == '' then + return nil, "invaild key." + end + return concat({'rtmp://' .. domain, hub, key}, "/") +end + +-- 获取HLS观播地址 +function Stream.hlsSubscribeUrl (domain, hub, key) + if type(domain) ~= 'string' or domain == '' then + return nil, "invaild domain." + end + if type(hub) ~= 'string' or hub == '' then + return nil, "invaild hub." + end + if type(key) ~= 'string' or key == '' then + return nil, "invaild key." + end + return concat({'http://' .. domain, hub, key..".m3u8"}, "/") +end + +-- 获取HDL观播地址 +function Stream.hdlSubscribeUrl (domain, hub, key) + if type(domain) ~= 'string' or domain == '' then + return nil, "invaild domain." + end + if type(hub) ~= 'string' or hub == '' then + return nil, "invaild hub." + end + if type(key) ~= 'string' or key == '' then + return nil, "invaild key." + end + return concat({'http://' .. domain, hub, key..".flv"}, "/") +end + +-- 直播快照地址 +function Stream.getSnapshot (domain, hub, key) + if type(domain) ~= 'string' or domain == '' then + return nil, "invaild domain." + end + if type(hub) ~= 'string' or hub == '' then + return nil, "invaild hub." + end + if type(key) ~= 'string' or key == '' then + return nil, "invaild key." + end + return concat({'http://' .. domain, hub, key..".jpg"}, "/") +end + +return Stream diff --git a/lualib/cloud/qiniu/token.lua b/lualib/cloud/qiniu/token.lua new file mode 100644 index 00000000..4667056a --- /dev/null +++ b/lualib/cloud/qiniu/token.lua @@ -0,0 +1,118 @@ +local json = require "json" +local crypt = require "cloud.qiniu.crypt" +local hmac_sha1 = crypt.hmac_sha1 +local urlencode = crypt.urlencode +local base64encode = crypt.urlsafe_base64encode + +local type = type +local next = next +local ipairs = ipairs +local os_time = os.time +local find = string.find +local toint = math.tointeger +local concat = table.concat + +local function build_sign (path, args, body) + local req = path + if type(args) == 'table' then + local query = {} + for _, item in ipairs(args) do + query[#query+1] = urlencode(item[1]) .. '=' .. urlencode(item[2]) + end + if #query > 0 then + req = req .. '?' .. concat(query, "&") + end + end + return req .. '\n' .. (type(body) == 'string' and body or '') +end + +local Token = { __Version__ = 0.1 } + +-- 检查url是否合法 +local function check_domain (domain) + if type(domain) == 'string' and find(domain, "http[s]?://([%w]+)") then + return domain + end +end + +-- 管理凭证 +--[[ + opt.path : 请求路径(必填) + opt.args : 请求参数(选填) + opt.body : 请求体内容(选填) +]] +function Token.getAccessToken (AccessKey, SecretKey, opt) + if type(AccessKey) ~= 'string' or AccessKey == '' then + return nil, "invaild AccessKey." + end + if type(SecretKey) ~= 'string' or SecretKey == '' then + return nil, "invaild SecretKey." + end + if type(opt) ~= 'table' or not next(opt) then + return nil, "invaild opt." + end + local sign_str = build_sign(opt.path, opt.args, opt.body) + local hash_sign = hmac_sha1(SecretKey, sign_str) + local Authorization = AccessKey .. ':' .. base64encode(hash_sign) + return Authorization, "QBox " .. Authorization +end + +-- Auth凭证 +function Token.newAuthorization (AccessKey, SecretKey, opt) + local auth = opt.method .. ' ' .. opt.path + if opt.query then + auth = auth .. opt.query + end + auth = auth .. '\nHost: ' .. opt.host + if opt.body then + auth = auth .. '\nContent-Type: application/json\n\n' .. opt.body + else + auth = auth .. '\n\n' + end + return 'Qiniu'.. ' ' .. AccessKey .. ':' .. base64encode(hmac_sha1(SecretKey, auth)) +end + +-- 上传凭证 +function Token.getUploadToken (AccessKey, SecretKey, roles) + if type(AccessKey) ~= 'string' or AccessKey == '' then + return nil, "invaild AccessKey." + end + if type(SecretKey) ~= 'string' or SecretKey == '' then + return nil, "invaild SecretKey." + end + if type(roles) ~= 'table' or not next(roles) then + return nil, "invaild roles." + end + if type(roles.bucket) ~= 'string' or roles.bucket == '' then + return nil, "invaild roles.bucket." + end + local Policy = { + scope = concat({roles.bucket, roles.prefix}, ':'), -- 上传可自行是否加入上传前缀 + deadline = toint(roles.deadline) or os_time() + 180, -- 默认超时时间为3分钟 + callbackUrl = check_domain(roles.callbackurl), -- 回调地址 + callbackBody = check_domain(roles.callbackbody), -- 回调地址内容 + insertOnly = toint(roles.insertonly) == 1 and 1 or 0, -- 是否可以写入覆盖 + fsizeMin = toint(roles.fsizemin), -- 限定上传文件大小最小值(单位Byte) + fsizeLimit = toint(roles.fsizeLimit), -- 限定上传文件大小最大值(单位Byte), 超过限制上传文件大小的最大值会返回 413 状态码. + fileType = toint(roles.filetype) ~= 0 and 1 or 0, -- 文件存储类型(0 为普通存储,1 为低频存储) 默认为: 0 + mimeLimit = type(roles.mimeLimit) == 'string' and roles.mimeLimit or nil, -- 限制文件类型 + } + local encodedPutPolicy = base64encode(json.encode(Policy)) + return AccessKey .. ':' .. base64encode(hmac_sha1(SecretKey, encodedPutPolicy)) .. ':' .. encodedPutPolicy +end + +-- 下载凭证 +function Token.getDownloadToken (AccessKey, SecretKey, opt) + if type(AccessKey) ~= 'string' or AccessKey == '' then + return nil, "invaild AccessKey." + end + if type(SecretKey) ~= 'string' or SecretKey == '' then + return nil, "invaild SecretKey." + end + if type(opt) ~= 'table' or not opt.url or not toint(opt.expires) then + return nil, "invaild roles." + end + return concat({ opt.url .. '?'..'e='.. opt.expires, 'token='.. AccessKey .. ':' .. base64encode(hmac_sha1(SecretKey, opt.url)) }, '&') +end + +return Token diff --git a/lualib/cloud/tencent/ai.lua b/lualib/cloud/tencent/ai.lua new file mode 100644 index 00000000..869b6f34 --- /dev/null +++ b/lualib/cloud/tencent/ai.lua @@ -0,0 +1,66 @@ +local httpc = require "httpc" +local crypt = require "crypt" +local md5 = crypt.md5 +local urlencode = crypt.urlencode + +local modf = math.modf +local now = require"sys".now +local time = os.time +local fmt = string.format + +local sort = table.sort +local concat = table.concat + + +local function sign(AppID, AppKey, opt) + opt["app_id"] = AppID + opt["time_stamp"] = time() + opt['nonce_str'] = fmt("%x", modf(now() * 100)) + opt["sign"] = "" + local keys = {} + for k, v in pairs(opt) do + keys[#keys+1] = k + end + sort(keys) + local args = {} + local sign_list = {} + for _, key in ipairs(keys) do + local k, v = key, opt[key] + if v ~= '' then + args[#args+1] = {k, v} + sign_list[#sign_list+1] = k .. '=' .. urlencode(v) + end + end + sign_list[#sign_list+1] = "app_key=" .. AppKey + args[#args+1] = {"sign", md5(concat(sign_list, "&"), true):upper()} + return args +end + +local ai = { __Version__ = 0.1, host = "https://api.ai.qq.com"} + +-- 智能闲聊 +function ai.chat(AppID, AppKey, session, text) + return httpc.post(ai.host .. "/fcgi-bin/nlp/nlp_textchat", nil, sign(AppID, AppKey, { session = session, question = text })) +end + +-- 文本转换为语音 +function ai.text_to_voice(AppID, AppKey, opt) + return httpc.post(ai.host .. "/fcgi-bin/aai/aai_tts", nil, sign(AppID, AppKey, opt)) +end + +-- 语种识别 +function ai.text_detect(AppID, AppKey, force, langs, text) + return httpc.post(ai.host .. "/fcgi-bin/nlp/nlp_textdetect", nil, sign(AppID, AppKey, { force = force or 0, candidate_langs = langs or "zh|en|jp|kr", text = text })) +end + +-- 文本翻译 +function ai.text_translate(AppID, AppKey, lang_code, text) + return httpc.post(ai.host .. "/fcgi-bin/nlp/nlp_texttrans", nil, sign(AppID, AppKey, { type = lang_code, text = text })) +end + +-- 语音翻译 +function ai.voice_translate(AppID, AppKey, lang_code, text) + return httpc.post(ai.host .. "/fcgi-bin/nlp/nlp_texttrans", nil, sign(AppID, AppKey, { type = lang_code, text = text })) +end + +return ai \ No newline at end of file diff --git a/lualib/cloud/tencent/location.lua b/lualib/cloud/tencent/location.lua new file mode 100644 index 00000000..6af4210b --- /dev/null +++ b/lualib/cloud/tencent/location.lua @@ -0,0 +1 @@ +return require "cloud.location.tencent" diff --git a/lualib/cloud/translate/baidu.lua b/lualib/cloud/translate/baidu.lua new file mode 100644 index 00000000..acf915aa --- /dev/null +++ b/lualib/cloud/translate/baidu.lua @@ -0,0 +1,73 @@ +local httpc = require "httpc" +local crypt = require "crypt" +local md5 = crypt.md5 + +local type = type +local pairs = pairs +local time = os.time +local random = math.random +local concat = table.concat + + +local baidu = { __Version__ = 0.1, host = "https://fanyi-api.baidu.com/api/trans/vip/translate"} + +--[[ +文档地址 :http://api.fanyi.baidu.com/api/trans/product/apidoc + +语言简写 名称 +auto 自动检测 +zh 中文 +en 英语 +yue 粤语 +wyw 文言文 +jp 日语 +kor 韩语 +fra 法语 +spa 西班牙语 +th 泰语 +ara 阿拉伯语 +ru 俄语 +pt 葡萄牙语 +de 德语 +it 意大利语 +el 希腊语 +nl 荷兰语 +pl 波兰语 +bul 保加利亚语 +est 爱沙尼亚语 +dan 丹麦语 +fin 芬兰语 +cs 捷克语 +rom 罗马尼亚语 +slo 斯洛文尼亚语 +swe 瑞典语 +hu 匈牙利语 +cht 繁体中文 +vie 越南语 +]] + +-- 签名 +local function sign(app_id, app_key, salt, opt) + local sig = md5(concat({app_id, opt.q, salt, app_key}), true) + local args = {{"appid", app_id}, {"salt", salt}, {"sign", sig}} + for key, value in pairs(opt) do + args[#args+1] = {key, value} + end + return args +end + +-- 翻译接口 +function baidu.translate(app_id, app_key, opt) + assert(type(app_id) == 'string' and app_id ~= '', "invalid baidu translate app_id.") + assert(type(app_key) == 'string' and app_key ~= '', "invalid baidu translate app_key.") + assert(type(opt) == 'table' and type(opt.q) == 'string' and opt.q ~= '' , "invalid baidu translate opt.") + return httpc.post(baidu.host, nil, sign(app_id, app_key, random(1, time()), { + q = opt.q, -- 必填(其它选填) + to = opt.to or "en", + from = opt.from or "auto", + tts = opt.tts and 0 or 1, + dict = opt.dict and 0 or 1, + })) +end + +return baidu \ No newline at end of file diff --git a/lualib/cloud/translate/youdao.lua b/lualib/cloud/translate/youdao.lua new file mode 100644 index 00000000..6d2356b7 --- /dev/null +++ b/lualib/cloud/translate/youdao.lua @@ -0,0 +1,52 @@ +local httpc = require "httpc" + +local type = type + +--[[ + 免费的有道词典接口, 采用https安全传输. +]] + +local youdao = { __Version__ = 0.1, host = "https://fanyi.youdao.com/translate"} + +-- 中文 >> 英语 +youdao.ZH_CN2EN = "ZH_CN2EN" +-- 中文 >> 日语 +youdao.ZH_CN2JA = "ZH_CN2JA" +-- 中文 >> 韩语 +youdao.ZH_CN2KR = "ZH_CN2KR" +-- 中文 >> 法语 +youdao.ZH_CN2FR = "ZH_CN2FR" +-- 中文 >> 俄语 +youdao.ZH_CN2RU = "ZH_CN2RU" +-- 中文 >> 西语 +youdao.ZH_CN2SP = "ZH_CN2SP" +-- 英语 >> 中文 +youdao.EN2ZH_CN = "EN2ZH_CN" +-- 日语 >> 中文 +youdao.JA2ZH_CN = "JA2ZH_CN" +-- 韩语 >> 中文 +youdao.KR2ZH_CN = "KR2ZH_CN" +-- 法语 >> 中文 +youdao.FR2ZH_CN = "FR2ZH_CN" +-- 俄语 >> 中文 +youdao.RU2ZH_CN = "RU2ZH_CN" +-- 西语 >> 中文 +youdao.SP2ZH_CN = "SP2ZH_CN" + +-- 转换 +function youdao.translate(translate_type, translate_text) + translate_type = youdao[translate_type] + if type(translate_type) ~= 'string' then + translate_type = "AUTO" + end + if type(translate_text) ~= 'string' or translate_text == '' then + return nil, "invalid translate_text." + end + return httpc.get(youdao.host, nil, { + {"doctype", "json"}, + {"i", translate_text}, + {"type", translate_type}, + }) +end + +return youdao \ No newline at end of file diff --git a/lualib/crypt/aes.lua b/lualib/crypt/aes.lua new file mode 100644 index 00000000..2d19a795 --- /dev/null +++ b/lualib/crypt/aes.lua @@ -0,0 +1,115 @@ +local CRYPT = require "lcrypt" + +local aesenc = CRYPT.aes_enc +local aesdec = CRYPT.aes_dec + +local hexencode = CRYPT.hexencode +local hexdecode = CRYPT.hexdecode + +local base64encode = CRYPT.base64encode +local base64decode = CRYPT.base64decode + +---@class CRYPT +---@field aes_128_ecb_encrypt fun(key:string, text:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_192_ecb_encrypt fun(key:string, text:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_256_ecb_encrypt fun(key:string, text:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_128_cbc_encrypt fun(key:string, text:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_192_cbc_encrypt fun(key:string, text:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_256_cbc_encrypt fun(key:string, text:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_128_cfb_encrypt fun(key:string, text:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_192_cfb_encrypt fun(key:string, text:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_256_cfb_encrypt fun(key:string, text:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_128_ofb_encrypt fun(key:string, text:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_192_ofb_encrypt fun(key:string, text:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_256_ofb_encrypt fun(key:string, text:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_128_ctr_encrypt fun(key:string, text:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_192_ctr_encrypt fun(key:string, text:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_256_ctr_encrypt fun(key:string, text:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_128_ecb_decrypt fun(key:string, cipher:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_192_ecb_decrypt fun(key:string, cipher:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_256_ecb_decrypt fun(key:string, cipher:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_128_cbc_decrypt fun(key:string, cipher:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_192_cbc_decrypt fun(key:string, cipher:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_256_cbc_decrypt fun(key:string, cipher:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_128_cfb_decrypt fun(key:string, cipher:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_192_cfb_decrypt fun(key:string, cipher:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_256_cfb_decrypt fun(key:string, cipher:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_128_ofb_decrypt fun(key:string, cipher:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_192_ofb_decrypt fun(key:string, cipher:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_256_ofb_decrypt fun(key:string, cipher:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_128_ctr_decrypt fun(key:string, cipher:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_192_ctr_decrypt fun(key:string, cipher:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_256_ctr_decrypt fun(key:string, cipher:string, iv:string, hex:boolean|'base64'?, padding:CRYPT.AES.Padding?):string +---@field aes_128_gcm_encrypt fun(key:string, text:string, iv:string, aad:string, tag_len:integer?, hex:boolean|'base64'?):string +---@field aes_192_gcm_encrypt fun(key:string, text:string, iv:string, aad:string, tag_len:integer?, hex:boolean|'base64'?):string +---@field aes_256_gcm_encrypt fun(key:string, text:string, iv:string, aad:string, tag_len:integer?, hex:boolean|'base64'?):string +---@field aes_128_gcm_decrypt fun(key:string, cipher:string, iv:string, aad:string, tag_len:integer?, hex:boolean|'base64'?):string +---@field aes_192_gcm_decrypt fun(key:string, cipher:string, iv:string, aad:string, tag_len:integer?, hex:boolean|'base64'?):string +---@field aes_256_gcm_decrypt fun(key:string, cipher:string, iv:string, aad:string, tag_len:integer?, hex:boolean|'base64'?):string +local AES = {} + +---@enum (key) CRYPT.AES.Padding +local padding_map = { + [0] = CRYPT.AES_PADDING_ZERO, + [7] = CRYPT.AES_PADDING_PKCS7, + [923] = CRYPT.AES_PADDING_ANSI923, + [7816] = CRYPT.AES_PADDING_ISO7816, +} + +local bits = { 128, 192, 256 } + +local list = { "ecb", "cbc", "cfb", "ofb", "ocb", "ctr" } + +for _, name in ipairs(list) do + for _, bit in ipairs(bits) do + local nid = 'EVP_aes_' .. bit .. '_' .. name + AES['aes_' .. bit .. '_' .. name .. '_encrypt'] = function (key, text, iv, hex, padding) + local data, errinfo = aesenc(CRYPT[nid], key, text, iv, padding_map[padding or 7]) + if not data then + return nil, errinfo + end + if hex then + data = hex == 'base64' and base64encode(data) or hexencode(data) + end + return data + end + AES['aes_' .. bit .. '_' .. name .. '_decrypt'] = function (key, cipher, iv, hex, padding) + if hex then + cipher = hex == 'base64' and base64decode(cipher) or hexdecode(cipher) + end + return aesdec(CRYPT[nid], key, cipher, iv, padding_map[padding or 7]) + end + end +end + +local list_ex = { "ccm", "gcm" } + +for _, name in ipairs(list_ex) do + for _, bit in ipairs(bits) do + local nid = 'EVP_aes_' .. bit .. '_' .. name + AES['aes_' .. bit .. '_' .. name .. '_encrypt'] = function (key, text, iv, aad, taglen, hex) + local data, errinfo = aesenc(CRYPT[nid], key, text, iv, nil, aad, taglen) + if not data then + return nil, errinfo + end + if hex then + data = hex == 'base64' and base64encode(data) or hexencode(data) + end + return data + end + AES['aes_' .. bit .. '_' .. name .. '_decrypt'] = function (key, cipher, iv, aad, taglen, hex) + if hex then + cipher = hex == 'base64' and base64decode(cipher) or hexdecode(cipher) + end + return aesdec(CRYPT[nid], key, cipher, iv, nil, aad, taglen) + end + end +end + +-- 初始化函数 +return function (t) + for k, v in pairs(AES) do + t[k] = v + end + return AES +end \ No newline at end of file diff --git a/lualib/crypt/b64.lua b/lualib/crypt/b64.lua new file mode 100644 index 00000000..a26f8f5d --- /dev/null +++ b/lualib/crypt/b64.lua @@ -0,0 +1,43 @@ +local CRYPT = require "lcrypt" +local base64encode = CRYPT.base64encode +local base64decode = CRYPT.base64decode + +local B64 = {} + +---comment 普通BASE64编码 +---@param text string @等待编码的内容 +---@param nopadding boolean @是否保留填充字符(默认保留) +---@return string @返回编码后的内容 +function B64.base64encode(text, nopadding) + return base64encode(text, false, nopadding) +end + +---comment 普通BASE64解码 +---@param text string @等待解码的内容 +---@return string @返回解码后的内容 +function B64.base64decode(text) + return base64decode(text, false) +end + +---comment URL安全的BASE64编码 +---@param text string @等待编码的内容 +---@param nopadding boolean @是否保留填充字符(默认保留) +---@return string @返回编码后的内容 +function B64.base64urlencode(text, nopadding) + return base64encode(text, true, nopadding) +end + +---comment URL安全的BASE64解码 +---@param text string @等待解码的内容 +---@return string @返回解码后的内容 +function B64.base64urldecode(text) + return base64decode(text, true) +end + +-- 初始化函数 +return function (t) + for k, v in pairs(B64) do + t[k] = v + end + return B64 +end \ No newline at end of file diff --git a/lualib/crypt/checksum.lua b/lualib/crypt/checksum.lua new file mode 100644 index 00000000..f77e39d1 --- /dev/null +++ b/lualib/crypt/checksum.lua @@ -0,0 +1,26 @@ +local CRYPT = require "lcrypt" +local crc32 = CRYPT.crc32 +local crc64 = CRYPT.crc64 +local adler32 = CRYPT.adler32 + +local CHECKSUM = {} + +function CHECKSUM.crc32(text) + return crc32(text) +end + +function CHECKSUM.crc64(text) + return crc64(text) +end + +function CHECKSUM.adler32(text) + return adler32(text) +end + +-- 初始化函数 +return function (t) + for k, v in pairs(CHECKSUM) do + t[k] = v + end + return CHECKSUM +end \ No newline at end of file diff --git a/lualib/crypt/des.lua b/lualib/crypt/des.lua new file mode 100644 index 00000000..2c6a4122 --- /dev/null +++ b/lualib/crypt/des.lua @@ -0,0 +1,177 @@ +local CRYPT = require "lcrypt" +local desencode = CRYPT.desencode +local desdecode = CRYPT.desdecode +local des_encrypt = CRYPT.des_encrypt +local des_decrypt = CRYPT.des_decrypt +local base64encode = CRYPT.base64encode +local base64decode = CRYPT.base64decode +local hexdecode = CRYPT.hexdecode +local hexencode = CRYPT.hexencode + +local DES = {} + +function DES.desencode (key, text, hex) + local hash = desencode(key, text) + if hash and hex then + return hexencode(hash) + end + return hash +end + +function DES.desdecode (key, text, hex) + if hex then + text = hexdecode(text) + end + return desdecode(key, text) +end + +function DES.desx_encrypt(key, text, iv, b64) + local hash = des_encrypt(0, key, text, iv) + if b64 then + hash = base64encode(hash) + end + return hash +end + +function DES.desx_decrypt(key, cipher, iv, b64) + if b64 then + cipher = base64decode(cipher) + end + return des_decrypt(0, key, cipher, iv) +end + +function DES.desx_cbc_encrypt(key, text, iv, b64) + return DES.desx_encrypt(key, text, iv, b64) +end + +function DES.desx_cbc_decrypt(key, cipher, iv, b64) + return DES.desx_decrypt(key, cipher, iv, b64) +end + +function DES.des_cbc_encrypt(key, text, iv, b64) + local hash = des_encrypt(1, key, text, iv) + if b64 then + hash = base64encode(hash) + end + return hash +end + +function DES.des_cbc_decrypt(key, cipher, iv, b64) + if b64 then + cipher = base64encode(cipher) + end + return des_decrypt(1, key, cipher, iv) +end + +function DES.des_ecb_encrypt(key, text, iv, b64) + local hash = des_encrypt(2, key, text, iv) + if b64 then + hash = base64encode(hash) + end + return hash +end + +function DES.des_ecb_decrypt(key, cipher, iv, b64) + if b64 then + cipher = base64encode(cipher) + end + return des_decrypt(2, key, cipher, iv) +end + +function DES.des_cfb_encrypt(key, text, iv, b64) + local hash = des_encrypt(3, key, text, iv) + if b64 then + hash = base64encode(hash) + end + return hash +end + +function DES.des_cfb_decrypt(key, cipher, iv, b64) + if b64 then + cipher = base64encode(cipher) + end + return des_decrypt(3, key, cipher, iv) +end + +function DES.des_ofb_encrypt(key, text, iv, b64) + local hash = des_encrypt(4, key, text, iv) + if b64 then + hash = base64encode(hash) + end + return hash +end + +function DES.des_ofb_decrypt(key, cipher, iv, b64) + if b64 then + cipher = base64encode(cipher) + end + return des_decrypt(4, key, cipher, iv) +end + +function DES.des_ede_encrypt(key, text, iv, b64) + local hash = des_encrypt(5, key, text, iv) + if b64 then + hash = base64encode(hash) + end + return hash +end + +function DES.des_ede_decrypt(key, cipher, iv, b64) + if b64 then + cipher = base64encode(cipher) + end + return des_decrypt(5, key, cipher, iv) +end + +function DES.des_ede3_encrypt(key, text, iv, b64) + local hash = des_encrypt(6, key, text, iv) + if b64 then + hash = base64encode(hash) + end + return hash +end + +function DES.des_ede3_decrypt(key, cipher, iv, b64) + if b64 then + cipher = base64encode(cipher) + end + return des_decrypt(6, key, cipher, iv) +end + +function DES.des_ede_ecb_encrypt(key, text, iv, b64) + local hash = des_encrypt(7, key, text, iv) + if b64 then + hash = base64encode(hash) + end + return hash +end + +function DES.des_ede_ecb_decrypt(key, cipher, iv, b64) + if b64 then + cipher = base64encode(cipher) + end + return des_decrypt(7, key, cipher, iv) +end + +function DES.des_ede3_ecb_encrypt(key, text, iv, b64) + local hash = des_encrypt(8, key, text, iv) + if b64 then + hash = base64encode(hash) + end + return hash +end + +function DES.des_ede3_ecb_decrypt(key, cipher, iv, b64) + if b64 then + cipher = base64encode(cipher) + end + return des_decrypt(8, key, cipher, iv) +end + +-- 初始化函数 +return function (t) + for k, v in pairs(DES) do + t[k] = v + end + return DES +end \ No newline at end of file diff --git a/lualib/crypt/dh.lua b/lualib/crypt/dh.lua new file mode 100644 index 00000000..485cce83 --- /dev/null +++ b/lualib/crypt/dh.lua @@ -0,0 +1,21 @@ +local CRYPT = require "lcrypt" +local dhsecret = CRYPT.dhsecret +local dhexchange = CRYPT.dhexchange + +local DH = {} + +function DH.dhsecret (...) + return dhsecret(...) +end + +function DH.dhexchange (...) + return dhexchange(...) +end + +-- 初始化函数 +return function (t) + for k, v in pairs(DH) do + t[k] = v + end + return DH +end \ No newline at end of file diff --git a/lualib/crypt/hmac.lua b/lualib/crypt/hmac.lua new file mode 100644 index 00000000..bf10435a --- /dev/null +++ b/lualib/crypt/hmac.lua @@ -0,0 +1,149 @@ +local CRYPT = require "lcrypt" +local hmac64 = CRYPT.hmac64 +local hmac_hash = CRYPT.hmac_hash +local hmac_md2 = CRYPT.hmac_md2 +local hmac_md4 = CRYPT.hmac_md4 +local hmac_md5 = CRYPT.hmac_md5 +local hmac64_md5 = CRYPT.hmac64_md5 + +local hmac_sha1 = CRYPT.hmac_sha1 +local hmac_sha224 = CRYPT.hmac_sha224 +local hmac_sha256 = CRYPT.hmac_sha256 +local hmac_sha384 = CRYPT.hmac_sha384 +local hmac_sha512 = CRYPT.hmac_sha512 +local hmac_ripemd160 = CRYPT.hmac_ripemd160 +local hmac_pbkdf2 = CRYPT.hmac_pbkdf2 + +local hexencode = CRYPT.hexencode + + +local md_map = { + md5 = CRYPT.EVP_md5, + sha128 = CRYPT.EVP_sha128, + sha224 = CRYPT.EVP_sha224, + sha256 = CRYPT.EVP_sha256, + sha384 = CRYPT.EVP_sha384, + sha512 = CRYPT.EVP_sha512, +} + +local HMAC = {} + +function HMAC.hmac_pbkdf2(mdname, password, salt, iter, hex) + local hash = hmac_pbkdf2(md_map[mdname], password, salt, iter) + if hash and hex then + return hexencode(hash) + end + return hash +end + +function HMAC.hmac64(key, text, hex) + local hash = hmac64(key, text) + if hash and hex then + return hexencode(hash) + end + return hash +end + +function HMAC.hmac64_md5(key, text, hex) + local hash = hmac64_md5(key, text) + if hash and hex then + return hexencode(hash) + end + return hash +end + +function HMAC.hmac_hash(key, text, hex) + local hash = hmac_hash(key, text) + if hash and hex then + return hexencode(hash) + end + return hash +end + +function HMAC.hmac_md2(key, text, hex) + local hash = hmac_md2(key, text) + if hash and hex then + return hexencode(hash) + end + return hash +end + +function HMAC.hmac_md4(key, text, hex) + local hash = hmac_md4(key, text) + if hash and hex then + return hexencode(hash) + end + return hash +end + +function HMAC.hmac_md5(key, text, hex) + local hash = hmac_md5(key, text) + if hash and hex then + return hexencode(hash) + end + return hash +end + +function HMAC.hmac_sha1(key, text, hex) + local hash = hmac_sha1(key, text) + if hash and hex then + return hexencode(hash) + end + return hash +end + +function HMAC.hmac_sha128(key, text, hex) + local hash = hmac_sha1(key, text) + if hash and hex then + return hexencode(hash) + end + return hash +end + +function HMAC.hmac_sha224(key, text, hex) + local hash = hmac_sha224(key, text) + if hash and hex then + return hexencode(hash) + end + return hash +end + +function HMAC.hmac_sha256(key, text, hex) + local hash = hmac_sha256(key, text) + if hash and hex then + return hexencode(hash) + end + return hash +end + +function HMAC.hmac_sha384(key, text, hex) + local hash = hmac_sha384(key, text) + if hash and hex then + return hexencode(hash) + end + return hash +end + +function HMAC.hmac_sha512(key, text, hex) + local hash = hmac_sha512(key, text) + if hash and hex then + return hexencode(hash) + end + return hash +end + +function HMAC.hmac_ripemd(key, text, hex) + local hash = hmac_ripemd160(key, text) + if hash and hex then + return hexencode(hash) + end + return hash +end + +-- 初始化函数 +return function (t) + for k, v in pairs(HMAC) do + t[k] = v + end + return HMAC +end \ No newline at end of file diff --git a/lualib/crypt/id.lua b/lualib/crypt/id.lua new file mode 100644 index 00000000..5534b21b --- /dev/null +++ b/lualib/crypt/id.lua @@ -0,0 +1,30 @@ +local CRYPT = require "lcrypt" +local uuid = CRYPT.uuid +local guid = CRYPT.guid + +local sys = require "sys" +local now = sys.now +local hostname = sys.hostname + +local modf = math.modf + +local ID = {} + +-- UUID v4实现 +function ID.uuid() + return uuid() +end + +-- hash(主机名)-时间戳-微秒-(1~65535的随机数) +function ID.guid(host) + local hi, lo = modf(now()) + return guid(host or hostname(), hi, lo * 1e4 // 1) +end + +-- 初始化函数 +return function (t) + for k, v in pairs(ID) do + t[k] = v + end + return ID +end \ No newline at end of file diff --git a/lualib/crypt/init.lua b/lualib/crypt/init.lua new file mode 100644 index 00000000..c8f39cbf --- /dev/null +++ b/lualib/crypt/init.lua @@ -0,0 +1,48 @@ +local CRYPT = require "lcrypt" + +local crypt = { + -- HEX编码/解码 + hexencode = CRYPT.hexencode, + hexdecode = CRYPT.hexdecode, + -- URL编码/解码 + urlencode = CRYPT.urlencode, + urldecode = CRYPT.urldecode, +} + +-- UUID与GUID +require "crypt.id"(crypt) + +-- 安全哈希与摘要算法 +require "crypt.sha"(crypt) + +-- 哈希消息认证码算法 +require "crypt.hmac"(crypt) + +-- 冗余校验算法 +require "crypt.checksum"(crypt) + +-- Base64编码/解码算法 +require "crypt.b64"(crypt) + +-- RC4算法 +require "crypt.rc4"(crypt) + +-- AES对称加密算法 +require "crypt.aes"(crypt) + +-- DES对称加密算法 +require "crypt.des"(crypt) + +-- 密钥交换算法 +require "crypt.dh"(crypt) + +-- 商用国密算法 +require "crypt.sm"(crypt) + +-- 非对称加密算法 +require "crypt.rsa"(crypt) + +-- 一些特殊算法 +require "crypt.utils"(crypt) + +return crypt \ No newline at end of file diff --git a/lualib/crypt/rc4.lua b/lualib/crypt/rc4.lua new file mode 100644 index 00000000..9246a2f8 --- /dev/null +++ b/lualib/crypt/rc4.lua @@ -0,0 +1,31 @@ +local CRYPT = require "lcrypt" +local hexencode = CRYPT.hexencode +local hexdecode = CRYPT.hexdecode +local rc4 = CRYPT.rc4 + +local RC4 = {} + +-- `RC4`加密 +function RC4.rc4_encrypt(key, text, hex) + local hash = rc4(key, text) + if hash and hex then + return hexencode(hash) + end + return hash +end + +-- `RC4`解密 +function RC4.rc4_decrypt(key, cipher, hex) + if cipher and hex then + cipher = hexdecode(cipher) + end + return rc4(key, cipher) +end + +-- 初始化函数 +return function (t) + for k, v in pairs(RC4) do + t[k] = v + end + return RC4 +end \ No newline at end of file diff --git a/lualib/crypt/rsa.lua b/lualib/crypt/rsa.lua new file mode 100644 index 00000000..cac83c33 --- /dev/null +++ b/lualib/crypt/rsa.lua @@ -0,0 +1,196 @@ +local CRYPTO = require "lcrypt" + +local hexencode = CRYPTO.hexencode +local hexdecode = CRYPTO.hexdecode +local base64encode = CRYPTO.base64encode +local base64decode = CRYPTO.base64decode + +-- 填充方式 +local RSA_NO_PADDING = CRYPTO.RSA_NO_PADDING +local RSA_PKCS1_PADDING = CRYPTO.RSA_PKCS1_PADDING +local RSA_PKCS1_OAEP_PADDING = CRYPTO.RSA_PKCS1_OAEP_PADDING + +local rsa_public_key_encode = CRYPTO.rsa_public_key_encode +local rsa_private_key_encode = CRYPTO.rsa_private_key_encode +local rsa_private_key_decode = CRYPTO.rsa_private_key_decode + +-- 当前支持的签名与验签方法 +local rsa_sign = CRYPTO.rsa_sign +local rsa_verify = CRYPTO.rsa_verify + +-- 当前支持的签名与验签 +local rsa_algorithms = { + ["md5"] = CRYPTO.nid_md5, + ["sha1"] = CRYPTO.nid_sha1, + ["sha128"] = CRYPTO.nid_sha1, + ["sha224"] = CRYPTO.nid_sha224, + ["sha256"] = CRYPTO.nid_sha256, + ["sha384"] = CRYPTO.nid_sha384, + ["sha512"] = CRYPTO.nid_sha512, +} + +-- 加密后的格式 +local rsa_padding = { + ["oaep"] = RSA_PKCS1_OAEP_PADDING, + ["pkcs1"] = RSA_PKCS1_PADDING, + ["nopadding"] = RSA_NO_PADDING, +} + +local function rsa_pub_enc(text, pkey, b64, padding) + local cipher = rsa_public_key_encode(text, pkey, rsa_padding[padding] or rsa_padding['pkcs1']) + if cipher and b64 then + return base64encode(cipher) + end + return cipher +end + +local function rsa_pri_enc(text, pkey, b64, padding, pw) + local cipher = rsa_private_key_encode(text, pkey, rsa_padding[padding] or rsa_padding['pkcs1'], pw) + if cipher and b64 then + return base64encode(cipher) + end + return cipher +end + +local function rsa_pri_dec(cipher, pkey, b64, padding, pw) + if b64 then + cipher = base64decode(cipher) + end + return rsa_private_key_decode(cipher, pkey, rsa_padding[padding] or rsa_padding['pkcs1'], pw) +end + +-- local function rsa_pub_dec(cipher, pkey, b64, padding) +-- if b64 then +-- cipher = base64decode(cipher) +-- end +-- return rsa_public_key_decode(cipher, pkey, rsa_padding[padding] or rsa_padding['pkcs1']) +-- end + +---@class crypto +local RSA = {} + +---------------- 私钥加密/解密 -------------------- + +---comment `RSA`私钥加密(`pkcs1`格式); 成功返回加密后的文本, 失败返回`false`与错误信息. +---@param text string @待加密的文本 +---@param prikey string @私钥内容或者私钥所在路径 +---@param b64? boolean @将加密后的内容进行`BASE64`编码 +---@param pw? string @如果有密码则填入. +function RSA.rsa_private_key_encode(text, prikey, b64, pw) + return rsa_pri_enc(text, prikey, b64 and true or false, 'pkcs1', pw) +end + +---comment `RSA`私钥加密(`oaep`格式); 成功返回加密后的文本, 失败返回`false`与错误信息. +---@param text string @待加密的文本 +---@param prikey string @私钥内容或者私钥所在路径 +---@param b64? boolean @将加密后的内容进行`BASE64`编码 +---@param pw? string @如果有密码则填入. +function RSA.rsa_private_key_oaep_padding_encode(text, prikey, b64, pw) + return rsa_pri_enc(text, prikey, b64 and true or false, 'oaep', pw) +end + +---comment `RSA`私钥加密(`nopadding`); 成功返回加密后的文本, 失败返回`false`与错误信息. +---@param text string @待加密的文本 +---@param prikey string @私钥内容或者私钥所在路径 +---@param b64 boolean @将加密后的内容进行`BASE64`编码 +---@param pw? string @如果有密码则填入. +function RSA.rsa_private_key_no_padding_encode(text, prikey, b64, pw) + return rsa_pri_enc(text, prikey, b64 and true or false, 'nopadding', pw) +end + +---comment `RSA`私钥解密(`pkcs1`格式); 成功返回解密后的明文, 失败返回`false`与错误信息. +---@param cipher string @已加密的文本 +---@param prikey string @私钥内容或者私钥所在路径 +---@param b64? boolean @内容进行`BASE64`解码 +---@param pw? string @如果有密码则填入. +function RSA.rsa_private_key_decode(cipher, prikey, b64, pw) + return rsa_pri_dec(cipher, prikey, b64 and true or false, 'pkcs1', pw) +end + +---comment `RSA`私钥解密(`oaep`格式); 成功返回解密后的明文, 失败返回`false`与错误信息. +---@param cipher string @已加密的文本 +---@param prikey string @私钥内容或者私钥所在路径 +---@param b64? boolean @内容进行`BASE64`解码 +---@param pw? string @如果有密码则填入. +function RSA.rsa_private_key_oaep_padding_decode(cipher, prikey, b64, pw) + return rsa_pri_dec(cipher, prikey, b64 and true or false, 'oaep', pw) +end + +---comment `RSA`私钥解密(`nopadding`); 成功返回解密后的明文, 失败返回`false`与错误信息. +---@param cipher string @已加密的文本 +---@param prikey string @私钥内容或者私钥所在路径 +---@param b64? boolean @内容进行`BASE64`解码 +---@param pw? string @如果有密码则填入. +function RSA.rsa_private_key_no_padding_decode(cipher, prikey, b64, pw) + return rsa_pri_dec(cipher, prikey, b64 and true or false, 'nopadding', pw) +end + +---------------- 公钥加密/解密 -------------------- + +---comment `RSA`公钥加密(`pkcs1`格式); 成功返回加密后的文本, 失败返回`false`与错误信息. +---@param text string @待加密的文本 +---@param pubkey string @公钥内容或者公钥所在路径 +---@param b64? boolean @将加密后的内容进行`BASE64`编码 +function RSA.rsa_public_key_encode(text, pubkey, b64) + return rsa_pub_enc(text, pubkey, b64 and true or false, 'pkcs1') +end + +---comment `RSA`公钥加密(`oaep`格式); 成功返回加密后的文本, 失败返回`false`与错误信息. +---@param text string @待加密的文本 +---@param pubkey string @公钥内容或者公钥所在路径 +---@param b64? boolean @将加密后的内容进行`BASE64`编码 +function RSA.rsa_public_key_oaep_padding_encode(text, pubkey, b64) + return rsa_pub_enc(text, pubkey, b64 and true or false, 'oaep') +end + +---comment `RSA`公钥加密(`nopadding`); 成功返回加密后的文本, 失败返回`false`与错误信息. +---@param text string @待加密的文本 +---@param pubkey string @公钥内容或者公钥所在路径 +---@param b64? boolean @将加密后的内容进行`BASE64`编码 +function RSA.rsa_public_key_no_padding_encode(text, pubkey, b64) + return rsa_pub_enc(text, pubkey, b64 and true or false, 'nopadding') +end + +---------------------------------------------------------------------------------------------------- + +---comment `RSA`签名函数(目前支持:`md5`、`sha128` ~ `sha512`) +---@param text string @待签名的明文 +---@param prikey string @私钥内容或者所在路径 +---@param alg "md5"|"sha128"|"sha224"|"sha256"|"sha384"|"sha512" @签名算法(例如: `"md5"`) +---@param hex? 'base64' | boolean @签名是否编码(可选) +function RSA.rsa_sign(text, prikey, alg, hex) + local sign = rsa_sign(text, prikey, rsa_algorithms[alg] or rsa_algorithms['md5']) + if sign and hex then + if hex == 'base64' then + sign = base64encode(sign) + else + sign = hexencode(sign) + end + end + return sign +end + +---comment `RSA`验签函数(目前支持:`md5`、`sha128` ~ `sha512`) +---@param text string @待签名的明文 +---@param pubkey string @公钥内容或者所在路径 +---@param sign string @待对比的签名内容 +---@param alg "md5"|"sha128"|"sha224"|"sha256"|"sha384"|"sha512" @签名算法(例如: `"md5"`) +---@param hex? 'base64' | boolean @`sign`是否解码(可选) +function RSA.rsa_verify(text, pubkey, sign, alg, hex) + if hex then + if hex == 'base64' then + sign = base64decode(sign) + else + sign = hexdecode(sign) + end + end + return rsa_verify(text, pubkey, sign, rsa_algorithms[alg] or rsa_algorithms['md5']) +end + +-- 初始化函数 +return function (t) + for k, v in pairs(RSA) do + t[k] = v + end + return RSA +end \ No newline at end of file diff --git a/lualib/crypt/sha.lua b/lualib/crypt/sha.lua new file mode 100644 index 00000000..24d3a770 --- /dev/null +++ b/lualib/crypt/sha.lua @@ -0,0 +1,101 @@ +local CRYPT = require "lcrypt" +local md2 = CRYPT.md2 +local md4 = CRYPT.md4 +local md5 = CRYPT.md5 +local sha1 = CRYPT.sha1 +local sha224 = CRYPT.sha224 +local sha256 = CRYPT.sha256 +local sha384 = CRYPT.sha384 +local sha512 = CRYPT.sha512 +local ripemd160 = CRYPT.ripemd160 +local hexencode = CRYPT.hexencode + +local SHA = {} + +function SHA.md2(text, hex) + local hash = md2(text) + if hash and hex then + return hexencode(hash) + end + return hash +end + +function SHA.md4(text, hex) + local hash = md4(text) + if hash and hex then + return hexencode(hash) + end + return hash +end + +function SHA.md5(text, hex) + local hash = md5(text) + if hash and hex then + return hexencode(hash) + end + return hash +end + +function SHA.sha1(text, hex) + local hash = sha1(text) + if hash and hex then + return hexencode(hash) + end + return hash +end + +function SHA.sha128(text, hex) + local hash = sha1(text) + if hash and hex then + return hexencode(hash) + end + return hash +end + +function SHA.sha224(text, hex) + local hash = sha224(text) + if hash and hex then + return hexencode(hash) + end + return hash +end + +function SHA.sha256(text, hex) + local hash = sha256(text) + if hash and hex then + return hexencode(hash) + end + return hash +end + +function SHA.sha384(text, hex) + local hash = sha384(text) + if hash and hex then + return hexencode(hash) + end + return hash +end + +function SHA.sha512(text, hex) + local hash = sha512(text) + if hash and hex then + return hexencode(hash) + end + return hash +end + +function SHA.ripemd(text, hex) + local hash = ripemd160(text) + if hash and hex then + return hexencode(hash) + end + return hash +end + +-- 初始化函数 +return function (t) + for k, v in pairs(SHA) do + t[k] = v + end + return SHA +end \ No newline at end of file diff --git a/lualib/crypt/sm.lua b/lualib/crypt/sm.lua new file mode 100644 index 00000000..d3a7e2f9 --- /dev/null +++ b/lualib/crypt/sm.lua @@ -0,0 +1,139 @@ +local CRYPT = require "lcrypt" +local sm3 = CRYPT.sm3 +local hmac_sm3 = CRYPT.hmac_sm3 +local sm2keygen = CRYPT.sm2keygen +local sm2sign = CRYPT.sm2sign +local sm2verify = CRYPT.sm2verify +local hexencode = CRYPT.hexencode +local base64encode = CRYPT.base64encode +local base64decode = CRYPT.base64decode + +local sm4_cbc_encrypt = CRYPT.sm4_cbc_encrypt +local sm4_cbc_decrypt = CRYPT.sm4_cbc_decrypt + +local sm4_ecb_encrypt = CRYPT.sm4_ecb_encrypt +local sm4_ecb_decrypt = CRYPT.sm4_ecb_decrypt + +local sm4_ofb_encrypt = CRYPT.sm4_ofb_encrypt +local sm4_ofb_decrypt = CRYPT.sm4_ofb_decrypt + +local sm4_ctr_encrypt = CRYPT.sm4_ctr_encrypt +local sm4_ctr_decrypt = CRYPT.sm4_ctr_decrypt + +local SM = {} + +-- 哈希 +function SM.sm3(str, hex) + local hash = sm3(str) + if hash and hex then + return hexencode(hash) + end + return hash +end + +-- 摘要 +function SM.hmac_sm3 (key, text, hex) + local hash = hmac_sm3(key, text) + if hash and hex then + return hexencode(hash) + end + return hash +end + +-- 生成SM2私钥、公钥 +function SM.sm2keygen(pri_path, pub_path) + return sm2keygen(pri_path, pub_path) +end + +-- SM3WithSM2签名 +function SM.sm2sign(pri_path, text, b64) + local sign = sm2sign(pri_path, text) + if b64 then + sign = base64encode(sign) + end + return sign +end + +-- SM3WithSM2验签 +function SM.sm2verify(pub_path, text, sign, b64) + if b64 then + sign = base64decode(sign) + end + return sm2verify(pub_path, text, sign) +end + +-- SM4分组加密算法之CBC +function SM.sm4_cbc_encrypt(key, text, iv, b64) + local cipher = sm4_cbc_encrypt(key, text, iv) + if b64 then + cipher = base64encode(cipher) + end + return cipher +end + +-- SM4分组加密算法之CBC +function SM.sm4_cbc_decrypt(key, cipher, iv, b64) + if b64 then + cipher = base64decode(cipher) + end + return sm4_cbc_decrypt(key, cipher, iv) +end + +-- SM4分组加密算法之ECB +function SM.sm4_ecb_encrypt(key, text, iv, b64) + local cipher = sm4_ecb_encrypt(key, text, iv) + if b64 then + cipher = base64encode(cipher) + end + return cipher +end + +-- SM4分组解密算法之ECB +function SM.sm4_ecb_decrypt(key, cipher, iv, b64) + if b64 then + cipher = base64decode(cipher) + end + return sm4_ecb_decrypt(key, cipher, iv) +end + +-- SM4分组加密算法之OFB +function SM.sm4_ofb_encrypt(key, text, iv, b64) + local cipher = sm4_ofb_encrypt(key, text, iv) + if b64 then + cipher = base64encode(cipher) + end + return cipher +end + +-- SM4分组解密算法之OFB +function SM.sm4_ofb_decrypt(key, cipher, iv, b64) + if b64 then + cipher = base64decode(cipher) + end + return sm4_ofb_decrypt(key, cipher, iv) +end + +-- SM4分组加密算法之CTR +function SM.sm4_ctr_encrypt(key, text, iv, b64) + local cipher = sm4_ctr_encrypt(key, text, iv) + if b64 then + cipher = base64encode(cipher) + end + return cipher +end + +-- SM4分组解密算法之CTR +function SM.sm4_ctr_decrypt(key, cipher, iv, b64) + if b64 then + cipher = base64decode(cipher) + end + return sm4_ctr_decrypt(key, cipher, iv) +end + +-- 初始化函数 +return function (t) + for k, v in pairs(SM) do + t[k] = v + end + return SM +end \ No newline at end of file diff --git a/lualib/crypt/utils.lua b/lualib/crypt/utils.lua new file mode 100644 index 00000000..7736bc06 --- /dev/null +++ b/lualib/crypt/utils.lua @@ -0,0 +1,52 @@ +local CRYPT = require "lcrypt" +local get_cert_sn = CRYPT.get_cert_sn +local xor_str = CRYPT.xor_str +local hashkey = CRYPT.hashkey +local randomkey = CRYPT.randomkey +local hexencode = CRYPT.hexencode + +local UTILS = {} + +function UTILS.xor_str (text, key, hex) + local hash = xor_str(text, key) + if hash and hex then + return hexencode(hash) + end + return hash +end + +function UTILS.randomkey(hex) + local hash = randomkey(8) + if hash and hex then + return hexencode(hash) + end + return hash +end + +function UTILS.randomkey_ex(byte, hex) + local hash = randomkey(byte) + if hash and hex then + return hexencode(hash) + end + return hash +end + +function UTILS.hashkey (key, hex) + local hash = hashkey(key) + if hash and hex then + return hexencode(hash) + end + return hash +end + +function UTILS.get_cert_sn(cert) + return get_cert_sn(cert) +end + +-- 初始化函数 +return function (t) + for k, v in pairs(UTILS) do + t[k] = v + end + return UTILS +end diff --git a/lualib/csv.lua b/lualib/csv.lua new file mode 100644 index 00000000..c53f5a69 --- /dev/null +++ b/lualib/csv.lua @@ -0,0 +1,79 @@ +local concat = table.concat +local type = type + +local csv = {} + +-- 读取并且解析CSV文件, 格式如下: +--[[ + [1] = [Name1, Name2, Nam3, ..., NameN] + [2] = [Value1, Value2, Value3, ..., ValueN] + . + .. + ... + [N] = [Value1, Value2, Value3, ..., ValueN] +]] +-- 规则1: 第一行为所有字段的名称, 第二行开始到第N行是内容; +-- 规则2: 每行不允许出现空格与逗号引号等特殊字符, 空格字符将会被删除掉; +-- 规则3: 打开csv文件失败将返回nil与一个err错误信息. +function csv.loadfile (path) + if type(path) ~= 'string' or path == '' then + return nil, "invalid args." + end + local file, err = io.open(path, "r") + if not file then + return nil, err + end + local tab = {} + for line in file:lines() do + local items = {} + for item in line:gmatch("([^,\r\n]+)") do + if item and item ~= '' then + items[#items + 1] = item:gsub("[ \"']", "") + end + end + if #items > 0 then + tab[#tab + 1] = items + end + end + file:close() + return tab +end + +-- 规则同上 +function csv.writefile (path, t) + if type(path) ~= 'string' or path == '' or type(t) ~= 'table' or #t < 1 then + return nil, "invalid args." + end + local file, err = io.open(path, "w") + if not file then + return nil, err + end + file:setvbuf("full", 1 << 20) + for index = 1, #t do + local contents = t[index] + if type(contents) == 'table' then + file:write(concat(contents, ',') .. '\n') + end + end + file:flush() + file:close() + return true +end + +function csv.loadstring (str) + if type(str) ~= 'string' or str == '' then + return nil, "invalid args." + end + local tab = {} + local index = 1 + for line in str:gmatch("([^\r\n]-)\r\n") do + local items = {} + for s in line:gmatch("([^, \r\n]+)") do + items[#items+1] = s + end + tab[#tab+1] = items + end + return tab +end + +return csv diff --git a/lualib/datetime/init.lua b/lualib/datetime/init.lua new file mode 100644 index 00000000..523a9e45 --- /dev/null +++ b/lualib/datetime/init.lua @@ -0,0 +1,133 @@ +local type = type +local assert = assert + +local sys = require "sys" +local os_now = sys.now +local os_time = os.time +local os_date = os.date +local difftime = os.difftime + +local match = string.match +local fmt = string.format + +local abs = math.abs +local toint = math.tointeger + +local datetime = {} + +--[[ +时间日期格式化符号: +%y 两位数的年份表示(00-99) +%Y 四位数的年份表示(000-9999) +%m 月份(01-12) +%d 月内中的一天(0-31) +%H 24小时制小时数(0-23) +%I 12小时制小时数(01-12) +%M 分钟数(00=59) +%S 秒(00-59) + +%a 本地简化星期名称 +%A 本地完整星期名称 +%b 本地简化的月份名称 +%B 本地完整的月份名称 +%c 本地相应的日期表示和时间表示 +%j 年内的一天(001-366) +%p 本地A.M.或P.M.的等价符 +%U 一年中的星期数(00-53)星期天为星期的开始 +%w 星期(0-6),星期天为星期的开始 +%W 一年中的星期数(00-53)星期一为星期的开始 +%x 本地相应的日期表示 +%X 本地相应的时间表示 +%Z 当前时区的名称 +%% %号本身 + +例如:  "Tue May 31 17:46:55 +0800 2011" 对应 "%a %b %d %H:%M:%S %Z %Y" +0800为中国的时区代码(东八区) +]] + +-- DATETIME 时间格式 +function datetime.datetime(timestamp) + return os_date("%F %X", timestamp or os_time()) +end + +-- ISO 8601表示法 - 1 +function datetime.iso8601( timestamp ) + return os_date("%FT%X", timestamp or os_time()) .. datetime.timezone2() +end + +-- ISO 8601表示法 - 2 +function datetime.iso8601_2( timestamp ) + return os_date("%Y%m%dT%H%M%S", timestamp or os_time()) .. datetime.timezone2() +end + +-- 格林威治时间 +function datetime.greenwich( timestamp ) + return os_date("%a, %d %b %Y %X GMT", timestamp or os_time()) +end + +-- 本地时间表示法 +function datetime.localtime( timestamp ) + return os_date("%c", timestamp or os_time()) +end + +-- 当前所在时区 +function datetime.timezone() + local time = difftime(os_time(), os_time(os_date("!*t"))) + return toint(time // 3600) +end + +-- 当前所在时区 +function datetime.timezone2() + local time = difftime(os_time(), os_time(os_date("!*t"))) + if abs(time) ~= time then + return fmt("-%02d:00", abs(time // 3600)) + end + return fmt("+%02d:00", toint(time // 3600)) +end + +-- 凌晨 - 秒级时间戳 +function datetime.dawn(timestamp) + local time = os_date("*t", timestamp) + return os_time { year = time.year, month = time.month, day = time.day, hour = 0, min = 0, sec = 0 } +end + +-- 午夜 - 秒级时间戳 +function datetime.midnight(timestamp) + local time = os_date("*t", timestamp) + return os_time { year = time.year, month = time.month, day = time.day, hour = 23, min = 59, sec = 59 } +end + +-- 毫秒级时间戳 +function datetime.mtime() + return toint((os_now() * 1e3) // 1) +end + +-- 秒级时间戳 +function datetime.time() + return os_time() +end + +-- DATETIME 转换为秒级时间戳 +function datetime.from_timestamp(dt) + assert(type(dt) == 'string' and dt ~= '', "Invalide datetime format.") + local year, month, day, hour, min, sec = match(dt, '([%d]-)[%-/]?([%d]-)[%-/]?([%d]-)[T ]+([%d]+):([%d]+):([%d]+)') + return os_time { year = year, month = month, day = day, hour = hour, min = min, sec = sec } +end + +-- DATETIME 转换为毫秒级时间戳 +function datetime.from_timestamp2(dt) + assert(type(dt) == 'string' and dt ~= '', "Invalide datetime format.") + local year, month, day, hour, min, sec, ms = match(dt, '([%d]-)[%-/]?([%d]-)[%-/]?([%d]-)[T ]+([%d]+):([%d]+):([%d]+)[%., ]+([%d]+)') + local ms = ms:sub(1, 3) + if #ms < 3 then + local ms = toint(ms) + if ms < 100 then + if ms < 10 then + ms = ms * 10 + end + ms = ms * 10 + end + end + return toint(os_time { year = year, month = month, day = day, hour = hour, min = min, sec = sec } * 1e3 + ms) +end + +return datetime \ No newline at end of file diff --git a/lualib/elasticsearch/init.lua b/lualib/elasticsearch/init.lua new file mode 100644 index 00000000..7693db8f --- /dev/null +++ b/lualib/elasticsearch/init.lua @@ -0,0 +1,394 @@ +local cf = require "cf" +local httpc = require "httpc" + +local json = require "json" +local json_decode = json.decode +local json_encode = json.encode + +local type = type + +local tabconcat = table.concat + +---comment 返回`HTTP`请求响应 +---@param self ElasticSearch @`ElasticSearch`对象 +---@param http_method string @`HTTP`方法 +---@param path string @`HTTP`路径 +---@return table? @成功返回`table`, 出错返回`false` +---@return string? @成功返`table`, 失败返回错误信息 +local function search_request(self, http_method, path, ...) + local code, body = httpc[http_method](self._domain .. path, ...) + if type(code) == 'nil' then + cf.sleep(0.1) + return search_request(self, http_method, path, ...) + end + ---@cast body string + return json_decode(body) +end + +local class = require "class" + +---@class ElasticSearch : META +local ElasticSearch = class("Lua.ElasticSearch") + +function ElasticSearch:ctor(opt) + if type(opt) ~= 'table' then + error("[es error]: Invalid ElasticSearch configure.") + end + if type(opt.domain) ~= 'string' or opt.domain == '' then + error("[es error]: need domain.", 2) + end + -- 如果有设置集群认证模式 + self:set_authorization(opt.username, opt.password) + self._sql = type(opt.sql) == 'string' and opt.sql or nil + self._pool = opt.pool -- 使用连接池 + self._domain = opt.domain +end + +function ElasticSearch:set_authorization(username, password) + self._authorization = { {"Content-Type", "application/json"} } + if username and password then + local key, value = httpc.basic_authorization(username, password) + self._authorization[#self._authorization+1] = {key, value} + end +end + +function ElasticSearch:connect() + return self:login() +end + +function ElasticSearch:login() + local code, body = httpc.get(self._domain .. '/', self._authorization, {}, 5) + if code ~= 200 then + return false, '[es error]: login failed.' + end + ---@cast body string + local tab = json_decode(body) + if tab and tab.version.distribution ~= 'opensearch' and tab.version.number then + self._ver = math.tointeger(tab.version.number:match("(%d+)")) + end + -- var_dump(tab) + return true +end + +---comment 查看索引设置 +---@param index string | table @索引名 +function ElasticSearch:get_setting(index) + if type(index) ~= 'string' then + if type(index) ~= 'table' or #index < 1 then + return false, "[es error]: index was invalid." + end + index = tabconcat(index, ',') + end + return search_request(self, 'get', '/' .. index .. '/_settings', self._authorization) +end + +---comment 修改索引设置 +---@param index string @索引名 +---@param document table @规则文档 +function ElasticSearch:set_setting(index, document) + if type(index) ~= 'string' then + return false, "[es error]: index was invalid." + end + if type(document) ~= 'table' then + return false, "[es error]: document was invalid." + end + return search_request(self, 'put', '/' .. index .. '/_settings', self._authorization, json_encode(document)) +end + +---comment 主动关闭分页用的游标(`cursor`) +---@param cursor string @游标(`cursor`) +function ElasticSearch:nocursor(cursor) + if type(cursor) ~= 'string' or cursor == '' then + return false, "[es error]: `cursor` was invalid." + end + local url = self._sql + if not url then + if self._ver then + if self._ver >= 7 then + url = '/_sql/close' + else + url = '/_xpack/sql/close' + end + else + url = '/_plugins/_sql/close' -- OpenSearch + end + else + url = url .. '/close' + end + return search_request(self, 'post', url, self._authorization, json_encode{cursor = cursor}) +end + +---comment 使用`SQL`的语法查询文档 +---@param document table @指定的查询文档,如`{"query":"select * from test"}` +---@param fields table? @(可选)字段类型(返回游标的搜索时需要指定) +---@param nowrap boolean? @(可选)指定为`true`则返回原始内容与结构. +function ElasticSearch:sql(document, fields, nowrap) + if type(document) ~= 'table' then + return false, "[es error]: document was invalid." + end + local url = self._sql + if not url then + if self._ver then + if self._ver >= 7 then + url = '/_sql?format=json' + else + url = '/_xpack/sql?format=json' + end + else + url = '/_plugins/_sql' -- OpenSearch + end + end + local tab, errinfo = search_request(self, 'post', url, self._authorization, json_encode(document)) + if not tab then + return false, errinfo + end + -- 返回原始内容 + if nowrap then + return tab + end + local rows, columns = tab.rows or tab.datarows, tab.columns or tab.schema or fields + if not rows or not columns then + return tab + end + -- 最终返回内容同关系型数据结构 + local results = { } + if #columns < 1 then + return results + end + for i = 1, #rows do + local item = {} + local row = rows[i] + for j = 1, #row do + item[columns[j].alias or columns[j].name] = row[j] + end + results[i] = item + end + if tab.cursor then + return results, { cursor = tab.cursor, fields = columns } + end + return results +end + +---comment 查询文档 +---@param index string | table @索引名 +---@param document table @查询文档 +function ElasticSearch:query(index, document) + if type(index) == 'table' then + index = tabconcat(index, ',') + elseif type(index) ~= 'string' then + return false, "[es error]: `index` was invalid." + end + local query + if type(document) == 'table' then + local errinfo + query, errinfo = json_encode(document) + if not query then + return false, errinfo + end + end + return search_request(self, 'post', '/' .. index .. '/_search', self._authorization, query or '{}') +end + +---comment 更新文档 +---@param index string | table @索引名 +---@param document table @自定义文档(支持脚本) +---@param id string | integer @指定文档`ID` +function ElasticSearch:update(index, document, id) + if type(index) == 'table' then + index = tabconcat(index, ',') + elseif type(index) ~= 'string' then + return false, "[es error]: `index` was invalid." + end + local url = '/' .. index .. '/_update/' .. id + if self._ver and self._ver < 7 then + url = '/' .. index .. '/_doc/' .. id .. '/_update' + end + return search_request(self, 'post', url, self._authorization, json_encode(document)) +end + +---comment 条件更新文档 +---@param index string | table @索引名 +---@param query table @更新规则 +function ElasticSearch:update_by_query(index, query) + if type(index) == 'table' then + index = tabconcat(index, ',') + elseif type(index) ~= 'string' then + return false, "[es error]: `index` was invalid." + end + return search_request(self, 'post', '/' .. index .. '/_update_by_query', self._authorization, json_encode(query)) +end + +---comment 删除文档 +---@param index string @索引名 +---@param id string | integer @指定文档`ID` +function ElasticSearch:delete(index, id) + if type(index) ~= 'string' then + return false, "[es error]: `index` was invalid." + end + if not id then + return false, "[es error]: `_id` was invalid." + end + return search_request(self, 'delete', '/' .. index .. '/_doc/' .. id, self._authorization) +end + +---comment 条件删除文档 +---@param index string | table @索引名 +---@param query table @删除规则 +function ElasticSearch:delete_by_query(index, query) + if type(index) == 'table' then + index = tabconcat(index, ',') + elseif type(index) ~= 'string' then + return false, "[es error]: `index` was invalid." + end + return search_request(self, 'post', '/' .. index .. '/_delete_by_query', self._authorization, json_encode(query)) +end + +---comment 插入文档 +---@param index string @索引名 +---@param document table @文档内容 +---@param id string | integer? @指定文档`ID`(可选) +function ElasticSearch:insert(index, document, id) + if type(index) ~= 'string' then + return false, '[es error]: Invalid `insert` index' + end + if type(document) ~= 'table' then + return false, '[es error]: Invalid `insert` document' + end + return search_request(self, 'post', '/' .. index .. '/_doc/' .. (id or ''), self._authorization, json_encode(document)) +end + +---comment `ElasticSearch`批量操作 +---@param index string @指定索引名 +---@param body table @指定操作类型 +function ElasticSearch:bulk(index, body) + if type(index) ~= 'string' then + error('es error: `index` was invalid.', 2) + end + if type(body) ~= 'table' or (#body & 0x02 == 0) then + error('es error: `body` was invalid.', 2) + end + local query = { } + for i = 1, #body, 2 do + -- local action, document = body[i], body[i+1] + query[#query+1] = tabconcat { json_encode(body[i]), '\n', json_encode(body[i+1]), '\n' } + end + return search_request(self, 'post', '/_bulk', self._authorization, tabconcat(query)) +end + +---@class ElasticSearch.Indices.Action.info +---@field index string|table? @指定索引 +---@field alias string? @指定别名 +---@field filter table? @指定过滤器 +---@field indices string[]? @索引数组 +---@field aliases string[]? @别名数组 +---@field is_write_index boolean? @指定索引写入 + +---@class ElasticSearch.Indices.Actions +---@field add ElasticSearch.Indices.Action.info? @增加别名 +---@field remove ElasticSearch.Indices.Action.info? @删除别名 + +---comment `ElasticSearch`的别名操作 +---@param actions ElasticSearch.Indices.Actions[] @指定操作 +function ElasticSearch:alias(actions) + if type(actions) ~= 'table' then + error('[es error]: `actions` was invalid', 2) + end + return search_request(self, 'post', '/_aliases', self._authorization, json_encode { actions = actions }) +end + +---@class ElasticSearch.Indices.GetQuery +---@field source boolean? @返回结果是否包含`_source`字段 +---@field refresh boolean? @查询前是否先刷新相关分片数据 +---@field version integer? @查询指定版本号的数据 +---@field preference string? @指定查询的`preference`分片的数据 +---@field includes table | string | string[]? @指定`_source`字段内的结果名称 + +---comment 根据`ID`查询指定文档 +---@param index string @索引名称 +---@param id integer | string @文档`ID` +---@param opt ElasticSearch.Indices.GetQuery? @查询参数 +function ElasticSearch:get(index, id, opt) + if type(opt) ~= 'table' then + opt = { } + end + + local mode = '/_doc/' + if type(opt['source']) ~= 'nil' then + mode = '/_source/' + end + + local args = { } + + if type(opt['includes']) == 'table' then + local includes = opt['includes'] + ---@cast includes table + args[#args+1] = { '_source_includes', tabconcat(includes, ',') } + elseif type(opt['includes']) == 'string' then + args[#args+1] = { '_source_includes', opt['includes'] } + end + + if type(opt['refresh']) ~= 'nil' then + args[#args+1] = { 'refresh', opt['refresh'] and 'true' or 'false' } + end + + return search_request(self, 'get', '/' .. index .. mode .. id, self._authorization, args) +end + +---comment 刷新一个或多个索引 +---@param index string | string[] @索引名称 +function ElasticSearch:flush(index) + if type(index) == 'table' then + index = tabconcat(index, ',') + elseif type(index) ~= 'string' then + error('[es error]: `indices` was invalid', 2) + end + return search_request(self, 'post', '/' .. index .. '/_flush/', self._authorization) +end + +---comment 获取一个或多个索引统计信息 +---@param index string | string[] @索引名称 +function ElasticSearch:count(index) + if type(index) == 'table' then + index = tabconcat(index, ',') + elseif type(index) ~= 'string' then + error('[es error]: `indices` was invalid', 2) + end + return search_request(self, 'post', '/' .. index .. '/_count/', self._authorization) +end + +---comment 使用默认分词器对指定文本`text`进行分词, 也可以通过传递`analyzer`参数来指定分词器 +---@param text string @分词的文本 +---@param analyzer string? @分词器名称 +function ElasticSearch:analyze(text, analyzer) + if type(text) ~= 'string' then + error('[es error]: `text` was invalid', 2) + end + if analyzer and type(analyzer) ~= 'string' then + error('[es error]: `analyzer` was invalid', 2) + end + return search_request(self, 'post', '/_analyze', self._authorization, { analyzer = analyzer, text = text }) +end + +---comment 创建`Ingest pipeline` +---@param id string @`pipeline ID` +---@param document table @`pipeline`配置 +function ElasticSearch:create_pipeline(id, document) + if type(id) ~= 'string' then + error('[es error]: `pipeline id` was invalid', 2) + end + if type(document) ~= 'table' then + error('[es error]: `document` was invalid', 2) + end + return search_request(self, 'put', '/_ingest/pipeline/' .. id, self._authorization, document) +end + +---comment 删除`Ingest pipeline` +---@param id string @`pipeline ID`(传入`*`可以删除所有) +function ElasticSearch:remove_pipeline(id) + if type(id) ~= 'string' then + error('[es error]: `pipeline id` was invalid', 2) + end + return search_request(self, 'delete', '/_ingest/pipeline/' .. id, self._authorization) +end + +return ElasticSearch \ No newline at end of file diff --git a/lualib/httpc/class.lua b/lualib/httpc/class.lua new file mode 100644 index 00000000..efb2a9cc --- /dev/null +++ b/lualib/httpc/class.lua @@ -0,0 +1,420 @@ +local hc = require "httpc" +local hc_multi_request = hc.multi_request + +local class = require "class" + +local ua = require "httpc.ua" +local protocol = require "httpc.protocol" +local sock_new = protocol.sock_new +local sock_send = protocol.sock_send +local sock_connect = protocol.sock_connect +local httpc_response = protocol.httpc_response +local splite_protocol = protocol.splite_protocol +local build_raw_req = protocol.build_raw_req +local build_get_req = protocol.build_get_req +local build_post_req = protocol.build_post_req +local build_json_req = protocol.build_json_req +local build_file_req = protocol.build_file_req +local build_xml_req = protocol.build_xml_req +local build_put_req = protocol.build_put_req +local build_delete_req = protocol.build_delete_req +local build_basic_authorization = protocol.build_basic_authorization + +local type = type +local assert = assert +local tonumber = tonumber + +local upper = string.upper + +local httpc = class("httpc") + +function httpc:ctor (opt) + self.reconnect = true + self.timeout = opt.timeout or 15 + self.server = ua.get_user_agent() +end + +function httpc:basic_authorization( ... ) + return build_basic_authorization(...) +end + +-- 设置超时时间 +function httpc:set_timeout(timeout) + if tonumber(timeout) and tonumber(timeout) and tonumber(timeout) > 0 then + self.timeout = tonumber(timeout) + return self + end +end + +-- 添加外置socket +function httpc:set_socket(sock) + if type(sock) == 'table' then + self.sock = sock + return self + end +end + +-- 关闭重试 +function httpc:disable_reconnect() + self.reconnect = false + return self +end + +-- 检查域名和端口是否一致. +function httpc:check_domain(opt) + if self.domain and self.domain ~= opt.domain then + return false + end + if self.port and self.port ~= opt.port then + return false + end + return true +end + +function httpc:send_request(opt, data) + self.doing = assert(not self.doing, "httpc class cannot be used by multiple coroutines at the same time.") + -- 创建链接或重连 + if not self.sock then + if not self.reconnect then + self.doing = nil + return nil, "httpc class can't connect to server 1 : " .. self.domain + end + local sock = sock_new():timeout(self.timeout) + if not sock_connect(sock, opt.protocol, opt.domain, opt.port) then + sock:close() + self.doing = nil + return nil, "httpc class can't connect to server : " .. self.domain + end + self.sock = sock + end + -- 发送请求数据 + if not sock_send(self.sock, opt.protocol, data) then + self.sock:close() + self.sock = nil + if not self.reconnect then + self.doing = nil + return nil, "httpc class can't connect to server 2 : " .. self.domain + end + local ok, err + local sock = sock_new():timeout(self.timeout) + ok, err = sock_connect(sock, opt.protocol, opt.domain, opt.port) + if not ok then + sock:close() + self.doing = nil + return ok, err + end + ok, err = sock_send(sock, opt.protocol, data) + if not ok then + sock:close() + self.doing = nil + return nil, err + end + self.sock = sock + end + self.doing = nil + return true +end + +-- 读取响应 +function httpc:read_response(opt) + self.doing = assert(not self.doing, "class cannot be used by multiple coroutines at the same time.") + local code, msg, headers = httpc_response(self.sock, opt.protocol) + if not code then + self.sock:close() + self.sock = nil + end + self.doing = nil + return code, msg, headers +end + +function httpc:raw( parameter ) + local opt, err = splite_protocol(parameter.domain) + if not opt then + return nil, err + end + + if not self:check_domain(opt) then + return nil, "Invalid httpc domain or port." + end + + local method = type(parameter.method) == 'string' and upper(parameter.method) or nil + assert( method and ( + method == 'GET' or + method == 'POST' or + method == 'DELETE' or + method == 'PUT' + ),"invalide http method.") + + -- GET方法禁止传递body + if method == "GET" then + parameter.body = nil + end + + -- POST/PUT方法禁止传递args + if method == "POST" or method == "PUT" or method == "DELETE" then + parameter.args = nil + end + + opt.method = method + opt.body = parameter.body + opt.args = parameter.args + opt.headers = parameter.headers + + local REQ = build_raw_req(opt) + + local ok, err = self:send_request(opt, REQ) + if not ok then + self.sock:close() + self.sock = nil + return false, err + end + + return self:read_response(opt) +end + +-- get 请求 +function httpc:get (domain, headers, args, timeout) + local opt, err = splite_protocol(domain) + if not opt then + return nil, err + end + + if not self:check_domain(opt) then + return nil, "Invalid httpc domain or port." + end + + if tonumber(timeout) and tonumber(timeout) > 0 then + self.timeout = timeout + end + + opt.args = args + opt.headers = headers + opt.server = self.server + + self.domain = opt.domain + self.port = opt.port + + local REQ = build_get_req(opt) + + local ok, err = self:send_request(opt, REQ) + if not ok then + return false, err + end + + return self:read_response(opt) +end + +-- post 请求 +function httpc:post (domain, headers, body, timeout) + local opt, err = splite_protocol(domain) + if not opt then + return nil, err + end + + if not self:check_domain(opt) then + return nil, "Invalid httpc domain or port." + end + + if tonumber(timeout) and tonumber(timeout) > 0 then + self.timeout = timeout + end + + opt.body = body + opt.headers = headers + opt.server = self.server + + self.domain = opt.domain + self.port = opt.port + + local REQ = build_post_req(opt) + + local ok, err = self:send_request(opt, REQ) + if not ok then + return false, err + end + + return self:read_response(opt) +end + +-- delete 请求 +function httpc:delete (domain, headers, body, timeout) + local opt, err = splite_protocol(domain) + if not opt then + return nil, err + end + + if not self:check_domain(opt) then + return nil, "Invalid httpc domain or port." + end + + if tonumber(timeout) and tonumber(timeout) > 0 then + self.timeout = timeout + end + + opt.body = body + opt.headers = headers + opt.server = self.server + + self.domain = opt.domain + self.port = opt.port + + local REQ = build_delete_req(opt) + + local ok, err = self:send_request(opt, REQ) + if not ok then + return false, err + end + + return self:read_response(opt) +end + +-- put 请求 +function httpc:put (domain, headers, body, timeout) + local opt, err = splite_protocol(domain) + if not opt then + return nil, err + end + + if not self:check_domain(opt) then + return nil, "Invalid httpc domain or port." + end + + if tonumber(timeout) and tonumber(timeout) > 0 then + self.timeout = timeout + end + + opt.body = body + opt.headers = headers + opt.server = self.server + + self.domain = opt.domain + self.port = opt.port + + local REQ = build_put_req(opt) + + local ok, err = self:send_request(opt, REQ) + if not ok then + return false, err + end + + return self:read_response(opt) +end + +-- json 请求 +function httpc:json (domain, headers, json, timeout) + + local opt, err = splite_protocol(domain) + if not opt then + return nil, err + end + + if not self:check_domain(opt) then + return nil, "Invalid httpc domain or port." + end + + if tonumber(timeout) and tonumber(timeout) > 0 then + self.timeout = timeout + end + + assert(type(json) == "string" or type(json) == "table", "attempted passed a invalid json string or table.") + + opt.json = json + opt.headers = headers + opt.server = self.server + + self.domain = opt.domain + self.port = opt.port + + local REQ = build_json_req(opt) + + local ok, err = self:send_request(opt, REQ) + if not ok then + return false, err + end + + return self:read_response(opt) +end + +-- xml 请求 +function httpc:xml (domain, headers, xml, timeout) + + local opt, err = splite_protocol(domain) + if not opt then + return nil, err + end + + if not self:check_domain(opt) then + return nil, "Invalid httpc domain or port." + end + + if tonumber(timeout) and tonumber(timeout) > 0 then + self.timeout = timeout + end + + assert(type(xml) == "string" or type(xml) == "table", "attempted passed a invalid xml string or table.") + + opt.xml = xml + opt.headers = headers + opt.server = self.server + + self.domain = opt.domain + self.port = opt.port + + local REQ = build_xml_req(opt) + + local ok, err = self:send_request(opt, REQ) + if not ok then + return false, err + end + + return self:read_response(opt) +end + +-- file 请求 +function httpc:file (domain, headers, files, timeout) + local opt, err = splite_protocol(domain) + if not opt then + return nil, err + end + + if not self:check_domain(opt) then + return nil, "Invalid httpc domain or port." + end + + if tonumber(timeout) and tonumber(timeout) > 0 then + self.timeout = timeout + end + + assert(type(files) == "table", "attempted passed a invalid file.") + + opt.files = files + opt.headers = headers + opt.server = self.server + + self.domain = opt.domain + self.port = opt.port + + local REQ = build_file_req(opt) + + local ok, err = self:send_request(opt, REQ) + if not ok then + return false, err + end + + return self:read_response(opt) +end + +-- 异步请求 +function httpc:multi_request (opt) + return hc_multi_request(opt) +end + +-- 关闭连接 +function httpc:close () + if self.sock then + self.sock:close() + self.sock = nil + end +end + +return httpc diff --git a/lualib/httpc/init.lua b/lualib/httpc/init.lua index 6b8981ee..b255ae85 100644 --- a/lualib/httpc/init.lua +++ b/lualib/httpc/init.lua @@ -1,435 +1,275 @@ -local class = require "class" -local tcp = require "internal.TCP" -local HTTP = require "protocol.http" - -local FILEMIME = HTTP.FILEMIME -local RESPONSE_PROTOCOL_PARSER = HTTP.RESPONSE_PROTOCOL_PARSER -local RESPONSE_HEADER_PARSER = HTTP.RESPONSE_HEADER_PARSER - -local random = math.random -local find = string.find -local match = string.match -local split = string.sub -local splite = string.gmatch -local spliter = string.gsub -local lower = string.lower -local insert = table.insert -local concat = table.concat -local toint = math.tointeger +local sys = require "sys" +local now = sys.now + +local cf = require "cf" +local cf_join = cf.join + +local ua = require "httpc.ua" +local protocol = require "httpc.protocol" +local sock_new = protocol.sock_new +local sock_send = protocol.sock_send +local sock_connect = protocol.sock_connect +local httpc_response = protocol.httpc_response +local splite_protocol = protocol.splite_protocol +local build_raw_req = protocol.build_raw_req +local build_get_req = protocol.build_get_req +local build_post_req = protocol.build_post_req +local build_json_req = protocol.build_json_req +local build_file_req = protocol.build_file_req +local build_xml_req = protocol.build_xml_req +local build_put_req = protocol.build_put_req +local build_delete_req = protocol.build_delete_req +local build_basic_authorization = protocol.build_basic_authorization + local type = type local assert = assert -local ipairs = ipairs -local tostring = tostring +local lower = string.lower +local upper = string.upper +local tunpack = table.unpack +local tinsert = table.insert -local CRLF = '\x0d\x0a' -local CRLF2 = '\x0d\x0a\x0d\x0a' +local __TIMEOUT__ = 15 -local fmt = string.format +local methods = { get = true, post = true, put = true, delete = true, xml = true, json = true, file = true} + +local function http_requese(sock, opt, req, parameter) + local ok, err + if parameter and parameter.cert_path and parameter.key_path then + sock:ssl_set_privatekey(parameter.key_path) + sock:ssl_set_certificate(parameter.cert_path) + if not sock:ssl_set_verify() then + sock:close() + return nil, "SSL privatekey and certfile verify failed." + end + end + ok, err = sock_connect(sock, opt.protocol, opt.domain, opt.port) + if not ok then + sock:close() + return ok, err + end + ok, err = sock_send(sock, opt.protocol, req) + if not ok then + sock:close() + return ok, err + end + local code, msg, headers = httpc_response(sock, opt.protocol) + sock:close() + return code, msg, headers +end -local SERVER = "cf/0.1" +local function raw( parameter ) + local opt, err = splite_protocol(parameter.domain) + if not opt then + return nil, err + end -local __TIMEOUT__ = 15 + local method = assert(type(parameter.method) == 'string' and methods[lower(parameter.method)] and upper(parameter.method), "[HTTPC ERROR]: invalide http method.") + parameter.method = method -local httpc = {} - -local function httpc_response(IO, SSL) - if not IO then - return nil, "Can't used this method before other httpc method.." - end - local CODE, HEADER, BODY - local Content_Length - local content = {} - local times = 0 - while 1 do - local data, len - if SSL == "http" then - data, len = IO:recv(1024) - else - data, len = IO:ssl_recv(1024) - end - if not data then - IO:close() - return nil, "A peer of remote server close this connection." - end - insert(content, data) - local DATA = concat(content) - local posA, posB = find(DATA, CRLF2) - if posB then - CODE = RESPONSE_PROTOCOL_PARSER(split(DATA, 1, posB)) - HEADER = RESPONSE_HEADER_PARSER(split(DATA, 1, posB)) - if not CODE or not HEADER then - IO:close() - return nil, "can't resolvable protocol." - end - local Content_Length = toint(HEADER['Content-Length'] or HEADER['content-length']) - local chunked = HEADER['Transfer-Encoding'] - if Content_Length then - if (#DATA - posB) == Content_Length then - IO:close() - return CODE, split(DATA, posB+1, #DATA) - end - local content = {split(DATA, posB+1, #DATA)} - while 1 do - local data, len - if SSL == "http" then - data, len = IO:recv(1024) - else - data, len = IO:ssl_recv(1024) - end - if not data then - IO:close() - return CODE, SSL.."[Content_Length] A peer of remote server close this connection." - end - insert(content, data) - local DATA = concat(content) - if Content_Length == #DATA then - IO:close() - return CODE, DATA - end - end - end - if chunked and chunked == "chunked" then - local content = {} - if #DATA > posB then - local DATA = split(DATA, posB+1, #DATA) - if find(DATA, CRLF2) then - local body = {} - for hex, block in splite(DATA, "([%w]*)\r\n(.-)\r\n") do - local len = toint(fmt("0x%s", hex)) - if len and len == #block then - if len == 0 and block == '' then - IO:close() - return CODE, concat(body) - end - insert(body, block) - end - end - end - insert(content, DATA) - end - while 1 do - local data, len - if SSL == "http" then - data, len = IO:recv(1024) - else - data, len = IO:ssl_recv(1024) - end - if not data then - IO:close() - return CODE, SSL.."[chunked] A peer of remote server close this connection A." - end - insert(content, data) - local DATA = concat(content) - if find(DATA, CRLF2) then - local body = {} - for hex, block in splite(DATA, "([%a%d]*)\r\n(.-)\r\n") do - local len = toint(fmt("0x%s", hex)) - if len and len == #block then - if len == 0 and block == '' then - IO:close() - return CODE, concat(body) - end - insert(body, block) - end - end - end - end - end - end - end -end + -- GET方法禁止传递body + if parameter.method == "GET" then + parameter.body = nil + end + + -- POST/PUT方法禁止传递args + if parameter.method == "POST" or parameter.method == "PUT" or parameter.method == "DELETE" then + parameter.args = nil + end + + opt.method = method + opt.body = parameter.body + opt.args = parameter.args + opt.headers = parameter.headers -local function IO_CONNECT(IO, PROTOCOL, DOAMIN, PORT) - local PORT = tonumber(PORT) - if PROTOCOL == "http" then - if not PORT or PORT > 65536 or PORT < 1 then - PORT = 80 - end - local ok, err = IO:connect(DOAMIN, PORT) - if not ok then - IO:close() - return false, 'httpc 连接失败: '.. DOAMIN ..',' .. PORT - end - return true - end - if PROTOCOL == "https" then - if not PORT or PORT > 65536 or PORT < 1 then - PORT = 443 - end - local ok = IO:ssl_connect(DOAMIN, PORT) - if not ok then - IO:close() - return false, 'httpc ssl连接失败: '.. DOAMIN ..',' .. PORT - end - return true - end - IO:close() - return nil, "IO_CONNECT error! unknow PROTOCOL: "..tostring(PROTOCOL) + return http_requese(sock_new():timeout(parameter.timeout or __TIMEOUT__), opt, build_raw_req(opt), parameter) end -local function IO_SEND(IO, PROTOCOL, DATA) - if PROTOCOL == "http" then - local ok = IO:send(DATA) - if not ok then - IO:close() - return nil, "httpc request get method error" - end - return true - end - if PROTOCOL == "https" then - local ok = IO:ssl_send(DATA) - if not ok then - IO:close() - return nil, "httpc ssl request get method error" - end - return true - end - IO:close() - return nil, "IO_SEND error! unknow PROTOCOL: "..tostring(PROTOCOL) +---comment HTTP[S] `GET`请求方法 +---@param domain string @合法的`http[s]`域名. +---@param headers table> @合法的请求头部, 格式:`{ {"key1", "value1"}, ... }`. +---@param args table> @合法的请求内容, 格式:`{ {"key1", "value1"}, ... }`. +---@param timeout number @此请求最长等待时间. +---@return integer? @http协议请求状态码, 如果是连接失败或等待超时则为`nil`. +---@return string? @服务器的响应内容. +---@return table? @服务器的响应头部. +local function get(domain, headers, args, timeout) + local opt, err = splite_protocol(domain) + if not opt then + return nil, err + end + + opt.args = args + opt.headers = headers + opt.server = ua.get_user_agent() + + return http_requese(sock_new():timeout(timeout or __TIMEOUT__), opt, build_get_req(opt)) end +---comment HTTP[S] `POST`请求方法 +---@param domain string @合法的`http[s]`域名. +---@param headers table> @合法的请求头部, 格式:`{ {"key1", "value1"}, ... }`. +---@param body table> @合法的请求内容, 格式:`{ {"key1", "value1"}, ... }`. +---@param timeout number @此请求最长等待时间. +---@return integer? @http协议请求状态码, 如果是连接失败或等待超时则为`nil`. +---@return string? @服务器的响应内容. +---@return table? @服务器的响应头部. +local function post(domain, headers, body, timeout) + local opt, err = splite_protocol(domain) + if not opt then + return nil, err + end + + opt.body = body + opt.headers = headers + opt.server = ua.get_user_agent() + + return http_requese(sock_new():timeout(timeout or __TIMEOUT__), opt, build_post_req(opt)) +end -local function splite_protocol(domain) - local PROTOCOL, DOMAIN, PATH = match(domain, '(http[s]?)://([^/]+)([/]?.*)') - if not PROTOCOL or PROTOCOL == '' or not DOMAIN or DOMAIN == '' then - return nil, "Invaild protocol" - end - if not PATH or PATH == '' then - PATH = '/' - end - local times = 0 - for colon in splite(DOMAIN, ":") do - times = times + 1 - end - local PORT - if times == 1 then - DOMAIN, PORT = match(DOMAIN, "(.+):([%d]+)") - elseif times > 1 then - local domain, port = match(DOMAIN, "%[(.+)%][:]?([%d]*)") - if domain and port then - DOMAIN, PORT = domain, port - end - end - return PROTOCOL, DOMAIN, PORT, PATH +---comment HTTP[S] `DELETE`请求方法 +---@param domain string @合法的`http[s]`域名. +---@param headers table> @合法的请求头部, 格式:`{ {"key1", "value1"}, ... }`. +---@param body table> @合法的请求内容, 格式:`{ {"key1", "value1"}, ... }`. +---@param timeout number @此请求最长等待时间. +---@return integer? @http协议请求状态码, 如果是连接失败或等待超时则为`nil`. +---@return string? @服务器的响应内容. +---@return table? @服务器的响应头部. +local function delete(domain, headers, body, timeout) + + local opt, err = splite_protocol(domain) + if not opt then + return nil, err + end + + opt.body = body + opt.headers = headers + opt.server = ua.get_user_agent() + + return http_requese(sock_new():timeout(timeout or __TIMEOUT__), opt, build_delete_req(opt)) end +---comment HTTP[S] `PUT`请求方法 +---@param domain string @合法的`http[s]`域名. +---@param headers table> @合法的请求头部, 格式:`{ {"key1", "value1"}, ... }`. +---@param body table> @合法的请求内容, 格式:`{ {"key1", "value1"}, ... }`. +---@param timeout number @此请求最长等待时间. +---@return integer? @http协议请求状态码, 如果是连接失败或等待超时则为`nil`. +---@return string? @服务器的响应内容. +---@return table? @服务器的响应头部. +local function put(domain, headers, body, timeout) + + local opt, err = splite_protocol(domain) + if not opt then + return nil, err + end + + opt.body = body + opt.headers = headers + opt.server = ua.get_user_agent() + + return http_requese(sock_new():timeout(timeout or __TIMEOUT__), opt, build_put_req(opt)) +end --- HTTP GET -function httpc.get(domain, HEADER, ARGS, TIMEOUT) - - local PROTOCOL, DOMAIN, PORT, PATH = splite_protocol(domain) - local port - if type(PORT) == 'number' and (port ~= 80 or port ~= 443) then - port = ":"..PORT - else - port = "" - end - - local request = { - fmt("GET %s HTTP/1.1", PATH), - fmt("Host: %s", DOMAIN..':'..port), - 'Accept: */*', - 'Accept-Encoding: identity', - fmt("Connection: keep-alive"), - fmt("User-Agent: %s", SERVER), - } - if ARGS and type(ARGS) == "table" then - local args = {} - for _, arg in ipairs(ARGS) do - assert(#arg == 2, "args need key[1]->value[2] (2 values)") - insert(args, arg[1]..'='..arg[2]) - end - request[1] = fmt("GET %s HTTP/1.1", PATH..'?'..concat(args, "&")) - end - if HEADER and type(HEADER) == "table" then - for _, header in ipairs(HEADER) do - assert(lower(header[1]) ~= 'content-length', "please don't give Content-Length") - assert(#header == 2, "HEADER need key[1]->value[2] (2 values)") - insert(request, header[1]..': '..header[2]) - end - end - insert(request, CRLF) - local REQ = concat(request, CRLF) - - local IO = tcp:new():timeout(TIMEOUT or __TIMEOUT__) - local ok, err = IO_CONNECT(IO, PROTOCOL, DOMAIN, PORT) - if not ok then - return ok, err - end - local ok, err = IO_SEND(IO, PROTOCOL, REQ) - if not ok then - return ok, err - end - return httpc_response(IO, PROTOCOL) +---comment HTTP[S] `POST`请求方法(使用`JSON`作为请求体) +---@param domain string @合法的`http[s]`域名. +---@param headers table> @合法的请求头部, 格式:`{ {"key1", "value1"}, ... }`. +---@param json table | string @可序列化的`table`或合法的`json`字符串. +---@param timeout number @此请求最长等待时间. +---@return integer? @http协议请求状态码, 如果是连接失败或等待超时则为`nil`. +---@return string? @服务器的响应内容. +---@return table? @服务器的响应头部. +local function json(domain, headers, json, timeout) + + local opt, err = splite_protocol(domain) + if not opt then + return nil, err + end + + opt.json = json + opt.headers = headers + opt.server = ua.get_user_agent() + + return http_requese(sock_new():timeout(timeout or __TIMEOUT__), opt, build_json_req(opt)) end --- HTTP POST -function httpc.post(domain, HEADER, BODY, TIMEOUT) - - local PROTOCOL, DOMAIN, PORT, PATH = splite_protocol(domain) - local port - if type(PORT) == 'number' and (port ~= 80 or port ~= 443) then - port = ":"..PORT - else - port = "" - end - - local request = { - fmt("POST %s HTTP/1.1\r\n", PATH), - fmt("Host: %s\r\n", DOMAIN..':'..port), - 'Accept: */*\r\n', - 'Accept-Encoding: identity\r\n', - fmt("Connection: keep-alive\r\n"), - fmt("User-Agent: %s\r\n", SERVER), - 'Content-Type: application/x-www-form-urlencoded\r\n', - } - if HEADER and type(HEADER) == "table" then - for _, header in ipairs(HEADER) do - assert(string.lower(header[1]) ~= 'content-length', "please don't give Content-Length") - assert(#header == 2, "HEADER need key[1]->value[2] (2 values)") - insert(request, header[1]..': '..header[2]..CRLF) - end - end - insert(request, CRLF) - - if BODY and type(BODY) == "table" then - local body = {} - for _, b in ipairs(BODY) do - assert(#b == 2, "if BODY is TABLE, BODY need key[1]->value[2] (2 values)") - insert(body, fmt("%s=%s", b[1], b[2])) - end - insert(request, concat(body, "&")) - insert(request, #request - 2, fmt("Content-Length: %s\r\n", #request[#request])) - end - if BODY and type(BODY) == "string" then - insert(request, #request, fmt("Content-Length: %s\r\n", #BODY)) - insert(request, BODY) - end - - local REQ = concat(request) - - local IO = tcp:new():timeout(TIMEOUT or __TIMEOUT__) - local ok, err = IO_CONNECT(IO, PROTOCOL, DOMAIN, PORT) - if not ok then - return ok, err - end - local ok, err = IO_SEND(IO, PROTOCOL, REQ) - if not ok then - return ok, err - end - return httpc_response(IO, PROTOCOL) +---comment HTTP[S] `POST`请求方法(使用`XML`作为请求体) +---@param domain string @合法的`http[s]`域名. +---@param headers table> @合法的请求头部, 格式:`{ {"key1", "value1"}, ... }`. +---@param xml table | string @可序列化的`table`或合法的`json`字符串. +---@param timeout number @此请求最长等待时间. +---@return integer? @http协议请求状态码, 如果是连接失败或等待超时则为`nil`. +---@return string? @服务器的响应内容. +---@return table? @服务器的响应头部. +local function xml(domain, headers, xml, timeout) + + local opt, err = splite_protocol(domain) + if not opt then + return nil, err + end + + opt.xml = xml + opt.headers = headers + opt.server = ua.get_user_agent() + + return http_requese(sock_new():timeout(timeout or __TIMEOUT__), opt, build_xml_req(opt)) end -function httpc.json(domain, HEADER, JSON, TIMEOUT) - - local PROTOCOL, DOMAIN, PORT, PATH = splite_protocol(domain) - local port - if type(PORT) == 'number' and (port ~= 80 or port ~= 443) then - port = ":"..PORT - else - port = "" - end - - assert(type(JSON) == "string", "Please passed A vaild json string.") - - local request = { - fmt("POST %s HTTP/1.1\r\n", PATH), - fmt("Host: %s\r\n", DOMAIN..':'..port), - 'Accept: */*\r\n', - 'Accept-Encoding: identity\r\n', - fmt("Connection: keep-alive\r\n"), - fmt("User-Agent: %s\r\n", SERVER), - fmt("Content-Length: %s\r\n", #JSON), - 'Content-Type: application/json\r\n', - } - if HEADER and type(HEADER) == "table" then - for _, header in ipairs(HEADER) do - assert(lower(header[1]) ~= 'content-length', "please don't give Content-Length") - assert(#header == 2, "HEADER need key[1]->value[2] (2 values)") - insert(request, header[1]..': '..header[2]..CRLF) - end - end - - insert(request, CRLF) - insert(request, JSON) - - local REQ = concat(request) - - local IO = tcp:new():timeout(TIMEOUT or __TIMEOUT__) - local ok, err = IO_CONNECT(IO, PROTOCOL, DOMAIN, PORT) - if not ok then - return ok, err - end - local ok, err = IO_SEND(IO, PROTOCOL, REQ) - if not ok then - return ok, err - end - return httpc_response(IO, PROTOCOL) +---comment HTTP[S] `POST`请求方法(使用`File`作为请求体) +---@param domain string @合法的`http[s]`域名. +---@param headers table> @合法的请求头部, 格式:`{ {"key1", "value1"}, ... }`. +---@param files table @合法的文件内容: { } +---@param timeout number @此请求最长等待时间. +---@return integer? @http协议请求状态码, 如果是连接失败或等待超时则为`nil`. +---@return string? @服务器的响应内容. +---@return table? @服务器的响应头部. +local function file(domain, headers, files, timeout) + + local opt, err = splite_protocol(domain) + if not opt then + return nil, err + end + + opt.files = files + opt.headers = headers + opt.server = ua.get_user_agent() + + return http_requese(sock_new():timeout(timeout or __TIMEOUT__), opt, build_file_req(opt)) end -function httpc.file(domain, HEADER, FILES, TIMEOUT) - - local PROTOCOL, DOMAIN, PORT, PATH = splite_protocol(domain) - local port - if type(PORT) == 'number' and (port ~= 80 or port ~= 443) then - port = ":"..PORT - else - port = "" - end - - local request = { - fmt("POST %s HTTP/1.1\r\n", PATH), - fmt("Host: %s\r\n", DOMAIN..':'..port), - 'Accept: */*\r\n', - 'Accept-Encoding: identity\r\n', - fmt("Connection: keep-alive\r\n"), - fmt("User-Agent: %s\r\n", SERVER), - } - - if HEADER and type(HEADER) == "table" then - for _, header in ipairs(HEADER) do - assert(lower(header[1]) ~= 'content-length', "please don't give Content-Length") - assert(#header == 2, "HEADER need key[1]->value[2] (2 values)") - insert(request, header[1]..': '..header[2]..'\r\n') - end - end - - if FILES then - local bd = random(1000000000, 9999999999) - local boundary_start = fmt("------CFWebService%d", bd) - local boundary_end = fmt("------CFWebService%d--", bd) - insert(request, fmt('Content-Type: multipart/form-data; boundary=----CFWebService%s\r\n', bd)) - local body = {} - for _, file in ipairs(FILES) do - insert(body, boundary_start) - local header = "" - if file.name and file.filename then - header = fmt(' name="%s"; filename="%s"', file.name, file.filename) - end - insert(body, fmt('Content-Disposition: form-data;%s', header)) - insert(body, fmt('Content-Type: %s\r\n', FILEMIME(file.type or '') or 'application/octet-stream')) - insert(body, file.file) - end - body = concat(body, CRLF) - insert(request, fmt("Content-Length: %s\r\n", #body + 2 + #boundary_end)) - insert(request, CRLF) - insert(request, body) - insert(request, CRLF) - insert(request, boundary_end) - end - - local REQ = concat(request) - - local IO = tcp:new():timeout(TIMEOUT or __TIMEOUT__) - local ok, err = IO_CONNECT(IO, PROTOCOL, DOMAIN, PORT) - if not ok then - return ok, err - end - local ok, err = IO_SEND(IO, PROTOCOL, REQ) - if not ok then - return ok, err - end - return httpc_response(IO, PROTOCOL) +local map = { get = get, post = post, delete = delete, json = json, xml = xml, file = file, put = put } + +local function multi_request (list) + if type(list) ~= 'table' or #list < 1 then + return false, "[HTTPC ERROR]: Invalid request parameter." + end + local s = now() + local response = {} + local array = {} + for index, req in ipairs(list) do + local fn = map[req.method and lower(req.method)] + if not fn then + response[index] = {false, '[HTTPC ERROR]: Unsupported request method.', {}, now() - s} + else + tinsert(array, function () + local code, msg, headers = fn(req.domain, req.headers, req.args or req.body or req.json or req.xml or req.files, req.timeout) + response[index] = {code, msg, headers, now() - s} + end) + end + end + cf_join(tunpack(array)) + return true, response end -return httpc \ No newline at end of file + +return { + raw = raw, + get = get, + post = post, + delete = delete, + json = json, + xml = xml, + file = file, + put = put, + multi_request = multi_request, + basic_authorization = build_basic_authorization, +} diff --git a/lualib/httpc/protocol.lua b/lualib/httpc/protocol.lua new file mode 100644 index 00000000..83b94714 --- /dev/null +++ b/lualib/httpc/protocol.lua @@ -0,0 +1,484 @@ +local tcp = require "internal.TCP" + +local lz = require"lz" +local gzuncompress = lz.gzuncompress + +local new_tab = require "sys".new_tab + +local json = require "json" +local json_encode = json.encode + +local xml2lua = require "xml2lua" +local toxml = xml2lua.toxml + +local crypt = require "crypt" +local url_encode = crypt.urlencode +local base64encode = crypt.base64encode + +local HTTP_PARSER = require "protocol.http.parser" +local FILEMIME = require "protocol.http.mime" +local PARSER_HTTP_RESPONSE = HTTP_PARSER.PARSER_HTTP_RESPONSE + +local type = type +local assert = assert +local ipairs = ipairs +local tonumber = tonumber + +local random = math.random +local find = string.find +local match = string.match +local lower = string.lower +local insert = table.insert +local concat = table.concat +local toint = math.tointeger +local fmt = string.format + +local CRLF = '\x0d\x0a' +local CRLF2 = '\x0d\x0a\x0d\x0a' + +local function sock_new () + return tcp:new() +end + +local function sock_recv (sock, _, bytes) + return sock:recv(bytes) +end + +local function sock_send(sock, _, data) + return sock:send(data) +end + +local function sock_connect(sock, PROTOCOL, DOAMIN, PORT) + local ok = sock:connect(DOAMIN, PORT) + if not ok then + return nil, 'httpc连接失败.' + end + if PROTOCOL == 'https' and not sock:ssl_handshake(DOAMIN) then + return nil, 'httpc连接失败.' + end + return true +end + +local function splite_protocol(domain) + if type(domain) ~= 'string' then + return nil, '1. invalide domain' + end + + local protocol, domain_port, path = match(domain, '^(http[s]?)://([^/]+)(.*)') + if not protocol or not domain_port or not path then + return nil, '2. invalide url' + end + + if not path or path == '' then + return nil, "3. http path need '/' in end." + end + + local domain, port + if find(domain_port, ':') then + local _, Bracket_Pos = find(domain_port, '[%[%]]') + if Bracket_Pos then + domain, port = match(domain_port, '%[(.+)%][:]?(%d*)') + else + domain, port = match(domain_port, '([^:]+):(%d*)') + end + if not domain then + return nil, "4. invalide host or port: "..domain_port + end + port = toint(port) + if not port then + port = 80 + if protocol == 'https' then + port = 443 + end + end + else + domain, port = domain_port, protocol == 'https' and 443 or 80 + end + return { + protocol = protocol, + domain = domain, + port = port, + path = path, + } +end + +local function read_data(sock, bytes) + local buffers = new_tab(128, 0) + while 1 do + local buf = sock:recv(bytes) + if not buf then + return + end + buffers[#buffers+1] = buf + bytes = bytes - #buf + if bytes == 0 then + break + end + end + return concat(buffers) +end + +local function httpc_response(sock, SSL) + if not sock then + return nil, "Can't used this method before other httpc method.." + end + local body + local buf = sock:readline(CRLF2) + if not buf then + return nil, SSL .. " A peer of remote server close this connection." + end + local VERSION, CODE, STATUS, HEADER = PARSER_HTTP_RESPONSE(buf) + if not CODE or not HEADER or (VERSION ~= 1.0 and VERSION ~= 1.1) then + return nil, SSL .. " can't resolvable protocol." + end + local Content_Encoding = HEADER['Content-Encoding'] or HEADER['content-encoding'] + local Content_Length = toint(HEADER['Content-Length'] or HEADER['content-length']) + local Chunked = HEADER['Transfer-Encoding'] or HEADER['transfer-encoding'] + if Chunked and find(Chunked, "chunked") then + local resp = new_tab(128, 0) + while 1 do + local chunked_size = sock:readline(CRLF, true) + local csize = toint(tonumber(chunked_size, 16)) + if not csize or csize == 0 then + if csize then + -- 这行代码是用来去除`\r\n`的. + read_data(sock, 2) + break + end + return nil, "Invalid http trunked body." + end + local buffer = read_data(sock, csize) + if not buffer then + return nil, SSL .. " [Content_Length] A peer of remote server close this connection." + end + resp[#resp+1] = buffer + -- 这行代码是用来去除`\r\n`的. + read_data(sock, 2) + end + body = concat(resp) + elseif Content_Length then + body = "" + if Content_Length > 0 then + local buffer = read_data(sock, Content_Length) + if not buffer then + return nil, SSL .. " [Content_Length] A peer of remote server close this connection." + end + body = buffer + end + else + return CODE, STATUS, HEADER + end + local RESP = body + if Content_Encoding == "gzip" then + RESP = gzuncompress(body) + end + -- 如果有重定向, 则优先返回重定向的地址; 否则返回接收到的body内容 + if CODE == 301 or CODE == 302 or CODE == 303 or CODE == 307 or CODE == 308 then + return CODE, HEADER['Location'] or HEADER['location'] or RESP, HEADER + end + return CODE, RESP, HEADER +end + +-- 对一些特殊请求的支持 +local function build_raw_req( opt ) + local request = new_tab(16, 0) + insert(request, fmt("%s %s HTTP/1.1", opt.method, opt.path)) + insert(request, fmt("Host: %s", (opt.port == 80 or opt.port == 443) and opt.domain or opt.domain..':'..opt.port)) + insert(request, fmt("User-Agent: %s", opt.server)) + insert(request, 'Accept: */*') + insert(request, 'Accept-Encoding: gzip, identity') + insert(request, 'Connection: keep-alive') + + if opt.method == 'GET' and type(opt.args) == "table" then + local args = new_tab(8, 0) + for _, arg in ipairs(opt.args) do + assert(#arg == 2, "args need key[1]->value[2] (2 values and must be string)") + insert(args, url_encode(arg[1])..'='..url_encode(arg[2])) + end + request[1] = fmt("GET %s HTTP/1.1", opt.path .. '?' .. concat(args, "&")) + end + + if type(opt.headers) == "table" then + for _, header in ipairs(opt.headers) do + assert(#header == 2, "HEADER need key[1]->value[2] (2 values)") + insert(request, header[1]..': '..header[2]) + end + end + + if opt.method == 'POST' or opt.method == 'PUT' or opt.method == 'DELETE' then + + if type(opt.body) == "table" then + local body = new_tab(8, 0) + for _, item in ipairs(opt.body) do + assert(#item == 2, "if BODY is TABLE, BODY need key[1]->value[2] (2 values)") + insert(body, url_encode(item[1])..'='..url_encode(item[2])) + end + local Body = concat(body, "&") + insert(request, #request, fmt('Content-Length: %s', #Body)) + insert(request, #request, 'Content-Type: application/x-www-form-urlencoded\r\n') + insert(request, Body) + end + + if type(opt.body) == "string" then + insert(request, fmt('Content-Length: %s\r\n', #opt.body)) + insert(request, opt.body) + end + + end + + return concat(request, CRLF) +end + +local function build_get_req (opt) + local request = new_tab(16, 0) + insert(request, fmt("GET %s HTTP/1.1", opt.path)) + insert(request, fmt("Host: %s", (opt.port == 80 or opt.port == 443) and opt.domain or opt.domain..':'..opt.port)) + insert(request, fmt("User-Agent: %s", opt.server)) + insert(request, 'Accept: */*') + insert(request, 'Accept-Encoding: gzip, identity') + insert(request, 'Connection: keep-alive') + if type(opt.args) == "table" then + local args = new_tab(8, 0) + for _, arg in ipairs(opt.args) do + assert(#arg == 2, "args need key[1]->value[2] (2 values and must be string)") + insert(args, url_encode(arg[1])..'='..url_encode(arg[2])) + end + request[1] = fmt("GET %s HTTP/1.1", opt.path..'?'..concat(args, "&")) + end + if type(opt.headers) == "table" then + for _, header in ipairs(opt.headers) do + assert(#header == 2, "HEADER need key[1]->value[2] (2 values)") + insert(request, header[1]..': '..header[2]) + end + end + insert(request, CRLF) + return concat(request, CRLF) +end + +local function build_post_req (opt) + local request = new_tab(16, 0) + insert(request, fmt("POST %s HTTP/1.1\r\n", opt.path)) + insert(request, fmt("Host: %s\r\n", (opt.port == 80 or opt.port == 443) and opt.domain or opt.domain..':'..opt.port)) + insert(request, fmt("User-Agent: %s\r\n", opt.server)) + insert(request, 'Accept: */*\r\n') + insert(request, 'Accept-Encoding: gzip, identity\r\n') + insert(request, 'Connection: keep-alive\r\n') + + local ct = false + if type(opt.headers) == "table" then + for _, header in ipairs(opt.headers) do + if lower(header[1]) == 'content-type' then + ct = true + end + assert(lower(header[1]) ~= 'content-length', "please don't give Content-Length") + assert(#header == 2, "HEADER need key[1]->value[2] (2 values)") + insert(request, header[1] .. ': ' .. header[2] .. CRLF) + end + end + + if type(opt.body) == "table" then + local body = new_tab(8, 0) + for _, item in ipairs(opt.body) do + assert(#item == 2, "if BODY is TABLE, BODY need key[1]->value[2] (2 values)") + insert(body, url_encode(item[1])..'='..url_encode(item[2])) + end + local Body = concat(body, "&") + insert(request, fmt('Content-Length: %s\r\n', #Body)) + if not ct then + insert(request, 'Content-Type: application/x-www-form-urlencoded\r\n\r\n') + else + insert(request, '\r\n') + end + insert(request, Body) + elseif type(opt.body) == 'string' and opt.body ~= '' then + insert(request, fmt('Content-Length: %s\r\n', #opt.body)) + if not ct then + insert(request, 'Content-Type: application/x-www-form-urlencoded\r\n\r\n') + else + insert(request, '\r\n') + end + insert(request, opt.body) + else + insert(request, "Content-Length: 0\r\n\r\n") + end + + return concat(request) +end + +local function build_delete_req (opt) + local request = new_tab(16, 0) + insert(request, fmt("DELETE %s HTTP/1.1", opt.path)) + insert(request, fmt("Host: %s", (opt.port == 80 or opt.port == 443) and opt.domain or opt.domain..':'..opt.port)) + insert(request, fmt("User-Agent: %s", opt.server)) + insert(request, 'Accept: */*') + insert(request, 'Accept-Encoding: gzip, identity') + insert(request, 'Connection: keep-alive') + if type(opt.headers) == "table" then + for _, header in ipairs(opt.headers) do + assert(#header == 2, "HEADER need key[1]->value[2] (2 values)") + insert(request, header[1]..': '..header[2]) + end + end + if type(opt.body) == "string" and opt.body ~= '' then + insert(request, fmt("Content-Length: %s", #opt.body)) + end + return concat(request, CRLF) .. CRLF2 .. ( type(opt.body) == "string" and opt.body or '' ) +end + +local function build_json_req (opt) + local request = new_tab(16, 0) + insert(request, fmt("POST %s HTTP/1.1", opt.path)) + insert(request, fmt("Host: %s", (opt.port == 80 or opt.port == 443) and opt.domain or opt.domain..':'..opt.port)) + insert(request, fmt("User-Agent: %s", opt.server)) + insert(request, 'Accept: */*') + insert(request, 'Accept-Encoding: gzip, identity') + insert(request, 'Connection: keep-alive') + if type(opt.headers) == "table" then + for _, header in ipairs(opt.headers) do + assert(lower(header[1]) ~= 'content-length', "please don't give Content-Length") + assert(#header == 2, "HEADER need key[1]->value[2] (2 values)") + insert(request, header[1]..': '..header[2]) + end + end + if type(opt.json) == 'string' and opt.json ~= '' then + insert(request, 'Content-Type: application/json') + insert(request, fmt("Content-Length: %s", #opt.json)) + elseif type(opt.json) == 'table' then + opt.json = json_encode(opt['json']) + insert(request, 'Content-Type: application/json') + insert(request, "Content-Length: " .. #opt.json) + else + opt.json = "" + insert(request, 'Content-Type: application/json') + insert(request, "Content-Length: 0") + end + return concat(request, CRLF) .. CRLF2 .. opt.json +end + +local function build_xml_req(opt) + local request = new_tab(16, 0) + insert(request, fmt("POST %s HTTP/1.1", opt.path)) + insert(request, fmt("Host: %s", (opt.port == 80 or opt.port == 443) and opt.domain or opt.domain..':'..opt.port)) + insert(request, fmt("User-Agent: %s", opt.server)) + insert(request, 'Accept: */*') + insert(request, 'Accept-Encoding: gzip, identity') + insert(request, 'Connection: keep-alive') + if type(opt.headers) == "table" then + for _, header in ipairs(opt.headers) do + assert(lower(header[1]) ~= 'content-length', "please don't give Content-Length") + assert(#header == 2, "HEADER need key[1]->value[2] (2 values)") + insert(request, header[1]..': '..header[2]) + end + end + if type(opt.xml) == 'string' and opt.xml ~= '' then + insert(request, 'Content-Type: application/xml') + insert(request, fmt("Content-Length: %s", #opt.xml)) + elseif type(opt.xml) == 'table' then + opt.xml = toxml(opt.xml, "xml") + insert(request, 'Content-Type: application/xml') + insert(request, "Content-Length: " .. #opt.xml) + else + opt.xml = "" + insert(request, 'Content-Type: application/xml') + insert(request, "Content-Length: 0") + end + return concat(request, CRLF) .. CRLF2 .. opt.xml +end + +local function build_file_req (opt) + local request = new_tab(16, 0) + insert(request, fmt("POST %s HTTP/1.1\r\n", opt.path)) + insert(request, fmt("Host: %s\r\n", (opt.port == 80 or opt.port == 443) and opt.domain or opt.domain..':'..opt.port)) + insert(request, fmt("User-Agent: %s\r\n", opt.server)) + insert(request, 'Accept: */*\r\n') + insert(request, 'Accept-Encoding: gzip, identity\r\n') + insert(request, 'Connection: keep-alive\r\n') + if type(opt.headers) == "table" then + for _, header in ipairs(opt.headers) do + assert(lower(header[1]) ~= 'content-length', "please don't give Content-Length") + assert(#header == 2, "HEADER need key[1]->value[2] (2 values)") + insert(request, header[1]..': '..header[2]..'\r\n') + end + end + + if type(opt.files) == 'table' then + local bd = random(1000000000, 9999999999) + local boundary_start = fmt("------CFWebService%d", bd) + local boundary_end = fmt("------CFWebService%d--", bd) + insert(request, fmt('Content-Type: multipart/form-data; boundary=----CFWebService%s\r\n', bd)) + local body = new_tab(16, 0) + local cd = 'Content-Disposition: form-data; %s' + local ct = 'Content-Type: %s' + for index, file in ipairs(opt.files) do + assert(file.file, "files index : [" .. index .. "] unknown multipart/form-data content.") + insert(body, boundary_start) + local name = file.name + local filename = file.filename + if not file.type then + if type(name) ~= 'string' or name == '' then + name = '' + end + insert(body, fmt(cd, fmt('name="%s"', name)) .. CRLF) + else + if type(name) ~= 'string' or name == '' then + name = '' + end + if type(filename) ~= 'string' or filename == '' then + filename = '' + end + insert(body, fmt(cd, fmt('name="%s"', name) .. '; ' .. fmt('filename="%s"', filename))) + insert(body, fmt(ct, FILEMIME[file.type or ''] or 'application/octet-stream') .. CRLF) + end + insert(body, file.file) + end + insert(body, boundary_end) + body = concat(body, CRLF) + insert(request, fmt("Content-Length: %s\r\n\r\n", #body)) + insert(request, body) + end + return concat(request) +end + +local function build_put_req (opt) + local request = new_tab(16, 0) + insert(request, fmt("PUT %s HTTP/1.1", opt.path)) + insert(request, fmt("Host: %s", (opt.port == 80 or opt.port == 443) and opt.domain or opt.domain..':'..opt.port)) + insert(request, fmt("User-Agent: %s", opt.server)) + insert(request, 'Accept: */*') + insert(request, 'Accept-Encoding: gzip, identity') + insert(request, 'Connection: keep-alive') + if type(opt.headers) == "table" then + for _, header in ipairs(opt.headers) do + assert(lower(header[1]) ~= 'content-length', "please don't give Content-Length") + assert(#header == 2, "HEADER need key[1]->value[2] (2 values)") + insert(request, header[1]..': '..header[2]) + end + end + if type(opt.body) == "string" and opt.body ~= '' then + insert(request, fmt("Content-Length: %s", #opt.body)) + end + return concat(request, CRLF) .. CRLF2 .. ( type(opt.body) == "string" and opt.body or '' ) +end + +-- http base authorization +local function build_basic_authorization(username, password) + return "Authorization", "Basic " .. base64encode(username .. ":" .. password) +end + +return { + sock_new = sock_new, + sock_recv = sock_recv, + sock_send = sock_send, + sock_connect = sock_connect, + httpc_response = httpc_response, + splite_protocol = splite_protocol, + build_raw_req = build_raw_req, + build_get_req = build_get_req, + build_post_req = build_post_req, + build_json_req = build_json_req, + build_file_req = build_file_req, + build_xml_req = build_xml_req, + build_put_req = build_put_req, + build_delete_req = build_delete_req, + build_basic_authorization = build_basic_authorization, +} diff --git a/lualib/httpc/proxy.lua b/lualib/httpc/proxy.lua new file mode 100644 index 00000000..d3adc58c --- /dev/null +++ b/lualib/httpc/proxy.lua @@ -0,0 +1,307 @@ +local httpc = require "httpc.class" + +local ua = require "httpc.ua" +local protocol = require "httpc.protocol" +local sock_new = protocol.sock_new +local sock_recv = protocol.sock_recv +local sock_send = protocol.sock_send +local sock_connect = protocol.sock_connect +local build_basic_authorization = protocol.build_basic_authorization +local splite_protocol = protocol.splite_protocol +local httpc_response = protocol.httpc_response + +local sys = require "sys" +local new_tab = sys.new_tab +local ipv4 = sys.ipv4 +local ipv6 = sys.ipv6 +local str2ip = sys.str2ip + +local type = type +local ipairs = ipairs +local assert = assert +local tonumber = tonumber + +local match = string.match +local fmt = string.format +local char = string.char +local pack = string.pack +local unpack = string.unpack +local concat = table.concat + +local CRLF = '\x0d\x0a' +local CRLF2 = '\x0d\x0a\x0d\x0a' + +local Proxy = {} + +-- 构建代理认证信息 +function Proxy.auth(Auth) + if type(Auth.username) == 'string' and Auth.username ~= '' and type(Auth.password) == 'string' and Auth.password ~= '' then + local _, base_info = build_basic_authorization(Auth.username, Auth.password) + return base_info + end +end + +-- 构建请求内容 +function Proxy.build_request(source_config, headers, auth) + local domain_and_port = source_config.domain .. ":" .. source_config.port + + -- 初始化请求基本信息 + local req = new_tab(24, 0) + req[#req+1] = fmt("CONNECT %s HTTP/1.1", domain_and_port) + req[#req+1] = fmt("Host: %s", domain_and_port) + req[#req+1] = fmt("User-Agent: %s", ua.get_user_agent()) + + -- 检查是否需要额外信息 + if type(headers) == 'table' then + for index, header in ipairs(headers) do + assert(type(header) == 'table' and #header == 2, fmt("httpc Proxy headers index [%d] Parameter failed.", index)) + req[#req+1] = header[1] .. ": " .. header[2] + end + end + + -- 检查是否需要添加认证信息 + if type(auth) == 'string' then + req[#req+1] = fmt("Proxy-Authorization: " .. auth) + end + + req[#req+1] = "Proxy-Connection: keep-alive" + req[#req+1] = "Connection: keep-alive" + req[#req+1] = "Content-Length: 0" + + return concat(req, CRLF) .. CRLF2 +end + +function Proxy.http_proxy_handshake(sock, proxy_config, source_config, info) + + -- 开始与代理服务器进行握手 + local ok, err = sock_send(sock, proxy_config.protocol, info) + if not ok then + return false, err + end + + -- 检查代理服务器验证情况 + local code, response = httpc_response(sock, proxy_config.protocol) + if code ~= 200 then + return false, response + end + + -- 检查代理的通道是否需要SSL握手 + if source_config.protocol == "https" then + if not sock:ssl_handshake(proxy_config.domain) then + return false, "httpc Proxy Connect ssl handshake failed." + end + end + + -- 连接成功 + return true +end + +function Proxy.socks5_proxy_handshake(sock, proxy_config, source_config) + + -- socks5不支持IPv6. + if ipv6(source_config.domain) then + return false, "httpc socks5 Proxy Unsupported ipv6 protocol." + end + + if not sock:connect(proxy_config.domain, proxy_config.port) then + return false, "httpc Proxy socks5 Connect failed." + end + + local VER, NMETHODS, METHODS = 0x05, 1, char(0x00) + + local username, password = nil, nil + if type(proxy_config.auth) == 'table' and type(proxy_config.auth.username) == 'string' and #proxy_config.auth.username > 0 and type(proxy_config.auth.password) == 'string' and #proxy_config.auth.password > 0 then + username = proxy_config.auth.username + password = proxy_config.auth.password + NMETHODS = NMETHODS + 1 + METHODS = METHODS .. char(0x02) + end + + if not sock:send(pack(">BB", VER, NMETHODS) .. METHODS) then + return false, "httpc socks5 Proxy closed in handshake. 1" + end + + local data = sock:recv(2) + -- "\x05\x00" 表示无需认证, "\x05\x02" 表示后续需要用户名密码认证. + if data ~= '\x05\x00' and data ~= '\x05\x02' then + if not data then + return false, "httpc socks5 Proxy closed this session." + end + return false, "httpc socks5 Proxy Can't Support this protocol." + end + + -- 如果需要进一步协商用户认证(sub-negotiation) + if data == '\x05\x02' then + -- 发送用户名/密码到代理服务器进行鉴权. + local ok = sock:send(char(0x02) .. char(#username) .. username .. char(#password) .. password) + if not ok then + return false, "httpc socks5 Proxy closed this session when send auth info." + end + local data = sock:recv(2) + if not data or #data ~= 2 then + return false, "httpc socks5 Proxy closed this session when server response auth status." + end + local auth_version, auth_status = unpack(">BB", data) + if auth_version ~= 2 or auth_status ~= 0 then + return false, "httpc socks5 Proxy server Authentication failed, please check username and password." + end + end + + -- 定义版本, 命令, 预留编码 + local VER, CMD, RSV = 0x05, 0x01, 0x00 + -- 定义连接类型, 目标地址, 目标端口 + local ATYPE, DST_ADDR, DST_PORT = nil, nil, nil + if ipv4(source_config.domain) then + -- 如果http[s]对端使用IP地址直连, 协议需要改变类型与地址的编码方式. + -- 需要注意的是: 如果对端使用的https并且使用domain验证, 那么代理鉴权完成之后的ssl握手可能会失败. + ATYPE, DST_ADDR, DST_PORT = 0x01, pack(">I4", str2ip(source_config.domain)), pack(">I2", source_config.port) + else + ATYPE, DST_ADDR, DST_PORT = 0x03, char(#source_config.domain) .. source_config.domain, pack(">I2", source_config.port) + end + + -- 发送连接协议 + local ok = sock:send(pack(">BBBB", VER, CMD, RSV, ATYPE) .. DST_ADDR .. DST_PORT) + if not ok then + return false, "httpc socks5 Proxy closed in handshake. 2" + end + + local data = sock:recv(4) + if not data then + return false, "httpc socks5 Proxy closed in handshake. 3" + end + + local ver, rep, rsv, atype = unpack(">BBBB", data) + -- print("成功: ", ver, rep, rsv, atype) + + if ver ~= 5 and rep ~= 0 then + return false, "httpc socks5 Proxy Server Connect source domain failed." + end + + if atype == 1 then -- 如果atype是IPv4类型 + if not sock:recv(4) then + return false, "httpc socks5 Proxy Close this session when read IPv4 info." + end + -- print("代理服务器为本次连接分配的IPv4地址为:", table.concat({unpack(">BBBB", data)}, ".", 1, 4)) + elseif atype == 3 then -- 如果atype是domain类型 + if not sock:recv(1) then + return false, "httpc socks5 Proxy Close this session when read domain atype." + end + sock:recv(unpack(">B", data)) + -- print("代理服务器回应了一个域名:" .. domain) + elseif atype == 4 then -- 如果atype是IPv6类型 + if not sock:recv(16) then + return false, "httpc socks5 Proxy Close this session when read IPv6 atype." + end + -- print("代理服务器为本次连接分配的IPv6地址为:", table.concat({unpack(">HHHHHHHH", data)}, ":", 1, 8)) + end + + if not sock:recv(2) then + return false, "httpc socks5 Proxy Server was shutdown before the connection was completed." + end + -- print("代理服务器为本次连接分配的端口为:", unpack(">I2", data)) + + -- 到这里就可以认为代理服务器完成它的使命, 现在需要检查是否需要https握手. + if source_config.protocol == "https" then + if not sock:ssl_handshake(source_config.domain) then + return false, "httpc Proxy Connect ssl handshake failed." + end + end + + return true +end + +function Proxy.connect(sock, proxy_config, source_config, info) + local ok1, e1 = sock_connect(sock, proxy_config.protocol, proxy_config.domain, proxy_config.port) + if not ok1 then + return false, "httpc Proxy Sever failed. " .. e1 + end + + -- 尝试与代理服务器进行握手 + local ok2, e2 = Proxy.http_proxy_handshake(sock, proxy_config, source_config, info) + if not ok2 then + return false, "httpc Proxy Sever handshake failed. " .. (e2 or "") + end + + return true +end + +---comment 通过`HTTP CONNCET`来完成认证与代理. `proxy_domain`的语法为: `http://(域名或IP):端口/`; `source_domain`的语法为: `http[s]://(域名或IP):端口/`;(`/`是必须存在的); +---comment 代理通道建立成功后将会返回一个`httpc class`对象. 失败返回`false`与`string`类型的错误信息. 在不需要使用的时候请调用它的`close`方法回收资源; +---comment `Proxy`不会做任何连接有效保证, `httpc class`断开连接的时候请调用它的`close`方法回收资源, 重新使用`tunnel_connect`方法创建`httpc`对象; +---@param opt table @`proxy_domain(代理IP)`/`opt.source_domain(目标IP)`/`opt.auth(可选的basice认证)`/`opt.headers(可选的头部信息)`/`opt.timeout(可选的连接超时时间)` +---@return table | nil, string @成功返回`httpc class`, 失败返回`false`与`string`类型的错误信息 . +function Proxy.tunnel_connect(opt) + + -- 解析代理服务器域名信息 + local proxy_config, err = splite_protocol(opt.proxy_domain) + if not proxy_config then + return false, "[proxy_domain error]: " .. err + end + -- require"logging":DEBUG(proxy_config) + + -- 解析原站服务器域名信息 + local source_config, err = splite_protocol(opt.source_domain) + if not source_config then + return false, "[source_domain error]: " .. err + end + -- require"logging":DEBUG(source_config) + + -- 检查是否需要构建认证 + local auth = nil + if type(opt.auth) == 'table' then + auth = Proxy.auth(opt.auth) + end + + local headers = nil + if type(opt.headers) == 'table' and #opt.headers > 0 then + headers = opt.headers + end + + -- 构建代理通道所需的握手信息 + local info = Proxy.build_request(source_config, headers, auth) + -- print(info) + + -- 设定socket超时时间 + local sock = sock_new():timeout(tonumber(opt.timeout) and tonumber(opt.timeout) > 0 and tonumber(opt.timeout) or 15) + + -- 尝试连接到代理服务器 + local ok, err = Proxy.connect(sock, proxy_config, source_config, info) + if not ok then + sock:close() + return false, err + end + + return httpc:new({ domain = opt.source_domain }):set_socket(sock):disable_reconnect() +end + +---comment 通过`SOCK5 CONNCET`来完成认证与代理. `proxy_domain`的语法为: `socks5://(域名或IP):端口`; `source_domain`的语法为: `http[s]://(域名或IP):端口/`, `/`是必须存在的; +---comment 代理通道建立成功后将会返回一个`httpc class`对象. 失败返回`false`与`string`类型的错误信息. 在不需要使用的时候请调用它的`close`方法回收资源; +---comment `Proxy`不会做任何连接有效保证, `httpc class`断开连接的时候请调用它的`close`方法回收资源, 重新使用`socks5_connect`方法创建`httpc`对象; +---@param opt table @`proxy_domain(代理IP)`/`opt.source_domain(目标IP)`/`opt.auth(可选的basice认证)`/`opt.headers(可选的头部信息)`/`opt.timeout(可选的连接超时时间)` +---@return table | nil, string @成功返回`httpc class`, 失败返回`false`与`string`类型的错误信息 . +function Proxy.socks5_connect(opt) + -- 解析代理服务器域名信息 + local domain, port = match(type(opt.proxy_domain) == 'string' and opt.proxy_domain or "" , "socks5://[%[]?([^%]%[]+)[%]]?[:](%d+)") + if not domain or not port then + return false, "[proxy_domain error]: Invalid Proxy ip/domain and port." + end + + -- 解析原站服务器域名信息 + local source_config, err = splite_protocol(opt.source_domain) + if not source_config then + return false, "[source_domain error]: " .. err + end + + -- 设定socket超时时间 + local sock = sock_new():timeout(tonumber(opt.timeout) and tonumber(opt.timeout) > 0 and tonumber(opt.timeout) or 15) + + local ok, err = Proxy.socks5_proxy_handshake(sock, { domain = domain, port = port , auth = opt.auth }, source_config) + if not ok then + sock:close() + return false, err + end + + return httpc:new({ domain = opt.source_domain }):set_socket(sock):disable_reconnect() +end + +return Proxy \ No newline at end of file diff --git a/lualib/httpc/ua.lua b/lualib/httpc/ua.lua new file mode 100644 index 00000000..45f8c6c3 --- /dev/null +++ b/lualib/httpc/ua.lua @@ -0,0 +1 @@ +return require "protocol.http.ua" \ No newline at end of file diff --git a/lualib/httpd/Cookie.lua b/lualib/httpd/Cookie.lua new file mode 100644 index 00000000..65c6c516 --- /dev/null +++ b/lualib/httpd/Cookie.lua @@ -0,0 +1,143 @@ +local Co = require "internal.Co" +local co_self = Co.self + +local crypt = require "crypt" +local hashkey = crypt.hashkey +local desencode = crypt.desencode +local desdecode = crypt.desdecode + +local type = type +local pcall = pcall +local assert = assert +local ipairs = ipairs +local os_date = os.date +local os_time = os.time +local toint = math.tointeger +local concat = table.concat +local splite = string.gmatch + +-- 默认密匙 +local secure = 'http://github.com/candymi/core_framework' + +-- 加密Cookie Value +local function encode_value (value) + return desencode(hashkey(secure), value, true):upper() +end + +-- 解密Cookie Value +local function decode_value (value) + local ok, info = pcall(desdecode, hashkey(secure), value:lower(), true) + if not ok then + return + end + return info +end + +-- 当前协程注册的cookie +local Cookie = { + client = {}, + server = {}, +} + +function Cookie.setSecure (sec) + if type(sec) == 'string' and sec ~= '' then + secure = sec + end +end + +-- 设置Cookie +function Cookie.setCookie (name, value, expires, notall, https) + assert(type(name) == 'string' and name ~= '', 'invalide Cookie Key') + assert(type(value) == 'string' or type(value) == 'number', 'invalide Cookie Value') + assert(not expires or expires > os_time(), 'invalide Cookie Expires') + local co = co_self() + local cs = Cookie.server[co] + if not cs then + cs = {} + Cookie.server[co] = cs + end + -- Cookie格式表 + local tab = { + ['name'] = name, + ['value'] = value, + ['expires'] = expires, + ['path'] = '/', + ['httponly'] = notall and 'HttpOnly' or nil, + ['secure'] = https and 'Secure' + } + -- 特殊兼容操作 + if expires then + tab['max-age'] = toint(expires - os_time()) + end + cs[#cs+1] = tab +end + +-- 获取指定Cookie字段 +function Cookie.getCookie (name) + local co = co_self() + local cs = Cookie.client[co] + if not cs then + return + end + local value = cs[name] + if not value then + return + end + return decode_value(value) +end + +function Cookie.delCookie (name) + return Cookie.setCookie(name, ' ', os_time() + 1) +end + +-- 对cookie进行反序列化 +function Cookie.deserialization (cs) + local co = co_self() + local Cookies = {} + if type(cs) ~= 'string' or cs == '' then + Cookie.client[co] = Cookies + return + end + for name, value in splite(cs, '([^ =]+)=([^ ;]+)') do + Cookies[name] = value + end + Cookie.client[co] = Cookies + -- local log = require 'logging' + -- local Log = log:new() + -- Log:DEBUG('反序列化后的cookie', Cookies) +end + +-- 对cookie进行序列化 +function Cookie.serialization () + local co = co_self() + local cs = Cookie.server[co] + if not cs then + return {} + end + local tab = {} + for _, cookie in ipairs(cs) do + local t = {} + t[#t+1] = concat({cookie['name'], '=', encode_value(cookie['value'])}) + t[#t+1] = cookie['max-age'] and concat({'max-age', '=', cookie['max-age']}) or nil + t[#t+1] = cookie['expires'] and concat({'expires', '=', os_date("%a, %d %b %Y %X GMT", cookie['expires'])}) or nil + t[#t+1] = concat({'path', '=', cookie['path']}) + t[#t+1] = cookie['httponly'] + if cookie['httponly'] then + t[#t+1] = cookie['secure'] + end + tab[#tab+1] = 'Set-Cookie: '..concat(t, "; ") + end + return tab +end + +-- 清理Cookie +function Cookie.clean () + local co = co_self() + Cookie.server[co] = nil + Cookie.client[co] = nil + -- local log = require 'logging' + -- local Log = log:new() + -- Log:DEBUG('反序列化后的cookie', Cookie) +end + +return Cookie diff --git a/lualib/httpd/Form.lua b/lualib/httpd/Form.lua index 0c42f980..1dd2742a 100644 --- a/lualib/httpd/Form.lua +++ b/lualib/httpd/Form.lua @@ -1,7 +1,20 @@ +local url = require "url" +local urlencode = url.encode +local urldecode = url.decode + +local sys = require "sys" +local new_tab = sys.new_tab + local type = type +local tonumber = tonumber + local string = string -local table = table +local sub = string.sub +local find = string.find +local match = string.match local splite = string.gmatch + +local table = table local insert = table.insert -- require "utils" @@ -10,14 +23,35 @@ local form = { ARGS = 1, } +function form.get_args (path) + if type(path) ~= 'string' or path == '' then + return + end + local s, e = find(path, '?'), #path + if not s or e - s < 3 then + return + end + return form.urlencode(sub(path, s + 1, e)) +end + -- 将body解析为x-www-form-urlencoded function form.urlencode(body) if type(body) ~= 'string' then return end local ARGS = {} - for key, value in splite(body, "([^%?&]-)=([^%?&]+)") do - ARGS[key] = value + for key, value in splite(body, "([^&]-)=([^&]+)") do + local tname, keyname = match(urldecode(key), "(.+)%[(.+)%]$") + if tname and keyname then + local t = ARGS[tname] + if not t then + t = new_tab(8, 8) + ARGS[tname] = t + end + t[tonumber(keyname) or keyname] = urldecode(value) + else + ARGS[urldecode(key)] = urldecode(value) + end end return ARGS end @@ -36,22 +70,14 @@ function form.multipart(body, BOUNDARY) insert(FILES, {filename = name, file = file}) end end - if #FILES > 0 then - -- return form.FILE, FILE - return 0, FILES - end local ARGS = {} for key, value in splite(body, 'name="([^"]*)"\r\n\r\n([^%-]-)\r\n[%-]-'..BOUNDARY) do if (key and key ~= '' ) and (value and value ~= '') then insert(ARGS, {key, value}) end end - if #ARGS > 0 then - -- return form.ARGS, ARGS - return 1, ARGS - end - return + return FILES, ARGS end -return form \ No newline at end of file +return form diff --git a/lualib/httpd/Redirect.lua b/lualib/httpd/Redirect.lua new file mode 100644 index 00000000..9ea5ce0e --- /dev/null +++ b/lualib/httpd/Redirect.lua @@ -0,0 +1,44 @@ +local type = type +local assert = assert +local find = string.find + +local urldecode = require "url".decode + +local codes = { + [301] = "301 Moved Permanently", -- (永久移动) + [302] = "302 Found", -- (发现) + [303] = "303 See Other", -- (查看其他) + [307] = "307 Temporary Redirect", -- (临时重定向) + [308] = "308 Permanent Redirect", -- (永久重定向) +} + +---comment 检查状态码是否在指定范围内 +---@param code number @HTTP状态码 +---@return boolean | integer +local function check_code(code) + return codes[code] and code or nil +end + +---comment 检查重定向的url是否合法. +---@param url string @合法的路由或http[s]地址 +---@return boolean | string +local function check_url(url) + if type(url) ~= "string" or url == "" then + return false + end + url = urldecode(url) + if find(url, "^/.*") then + return url + end + if find(url, "^http[s]?://.*") then + return url + end + return false +end + +---comment 让注册的`USE`/`API`路由可以合法的重定向. +---@param code integer @HTTP状态码. +---@param url string @需要重定向的`URL`. +return function (code, url) + return { __OPCODE__ = -65536, __CODE__ = assert(check_code(code), "Invalid http code.") , __MSG__ = assert(check_url(url), "Invalid url") } +end \ No newline at end of file diff --git a/lualib/httpd/Response.lua b/lualib/httpd/Response.lua new file mode 100644 index 00000000..a20fa5ab --- /dev/null +++ b/lualib/httpd/Response.lua @@ -0,0 +1,33 @@ +local type = type +local assert = assert + +local aio = require "aio" +local aio_stat = aio.stat + +---@comment Httpd响应构造器 +local Response = { __VERSION__ = 0.1 } + +---@comment 文件类型 +---@param filename string @合法且完整的`文件路径`(如: `static/index.html`) +---@param filetype string @合法且完整的`文件类型`(如: `text/css`) +---@param fileinline boolean @响应的文件是否需要内嵌到浏览器 +---@return table @`文件`响应构造器 +function Response.file_response(filename, filetype, fileinline) + assert(type(filename) == 'string' and filename ~= '', "[http `make_file` response] : file name does not exist.") + local info = aio_stat(filename) + if type(info) ~= 'table' or info.mode ~= 'file' then + return assert(nil, "[http `make_file` response] : can't find file or invalid file type.") + end + return { __OPCODE__ = -128, __CODE__ = 200, __FILEINLINE__ = fileinline, __FILENAME__ = filename, __FILETYPE__ = type(filetype) == 'string' and filetype or nil, __FILESIZE__ = aio_stat(filename).size } +end + +---@comment 文本类型 +---@param ctext string @合法且完整的`响应内容`(如: `Hello World.`) +---@param ctype string @合法且完整的`响应类型`(如: `text/html`) +---@return table @`文本`响应构造器 +function Response.text_response(ctext, ctype) + assert(type(ctype) == 'string' and type(ctext) == 'string', "[http `make_text` response] : Invalid content type or response content.") + return { __OPCODE__ = -128, __CODE__ = 200, __TYPE__ = ctype, __MSG__ = ctext } +end + +return Response \ No newline at end of file diff --git a/lualib/httpd/Router.lua b/lualib/httpd/Router.lua index 39998aa5..58894994 100644 --- a/lualib/httpd/Router.lua +++ b/lualib/httpd/Router.lua @@ -1,109 +1,229 @@ -local log = require "log" -local math = math -local string = string -local find = string.find -local match = string.match -local splite = string.gmatch - -local type = type -local assert = assert -local ipairs = ipairs -local tonumber = tonumber -local tostring = tostring -local toint = math.tointeger - -local Router = { - API = 1, - USE = 2, - STATIC = 3, - WS = 4, -} - -local routes = {} -- 存储路由 - -local typ = { - int = toint, - float = tonumber, - string = tostring, -} - --- 路由分割: /a/b/c/d = {'a', 'b', 'c', 'd'} -local function splite_route(route) - local tab = {} - for r in splite(route, '/([^/?]+)') do - tab[#tab + 1] = r - end - return tab -end - -local function registery_router(route, class, route_type) - local tab = splite_route(route) - if route == '/' then -- 如果注册路由为'/', 则转义为:'' - tab = {''} - end - for index, r in ipairs(tab) do - if not routes[index] then - routes[index] = {} - end - if not routes[index][r] then - routes[index][r] = true - end - if #tab == index then - routes[index][r] = {class = class, type = route_type} - end - end -end - -local function find_route(path) - local tab = splite_route(path) - if #tab == 0 then -- 如果路由为/[/]{0, n}, 则转义为: '' - tab[1] = '' - end - -- for index, r in ipairs(tab) do - -- local route = routes[index][r] - -- if type(route) == 'table' then - -- if #tab == index then - -- return route.class, route.type - -- end - -- if route.type == Router.STATIC then - -- return route.class, route.type - -- end - -- end - -- end - for index, route in ipairs(routes) do - local r = tab[index] - if not r then - return false - end - local t = route[r] - if type(t) == 'table' then - if #tab == index then - return t.class, t.type - end - if t.type == Router.STATIC then - return t.class, t.type - end - end - end -end - --- 查找路由 -function Router.find(path) - return find_route(path) -end - --- 注册路由 -function Router.registery(route, class, route_type) - if type(route) ~= 'string' or route == '' then -- 过滤错误的路由输入 - return log.warn('Please Do not add empty string in route registery method :)') - end - if find(route, '//') then -- 不允许出现路由出现[//] - return log.warn('Please Do not add [//] in route registery method :)') - end - if find(route, '^/%[%w+:.+%]$') then -- 不允许顶层路由注册rest模式. - return log.warn('Please Do not add [/[type:key] in root route :)]') - end - return registery_router(route, class, route_type) -end - -return Router \ No newline at end of file +local LOG = require "logging":new({dump = true, path = 'httpd-Router'}) + +local aio = require "aio" +local aio_stat = aio.stat + +local url = require "url" +local url_decode = url.decode +-- local url_encode = url.encode + +local new_tab = require("sys").new_tab + +local string = string +local byte = string.byte +local split = string.sub +local find = string.find +local match = string.match +local splite = string.gmatch +local spliter = string.gsub + +local type = type +local next = next +local pairs = pairs +local error = error +local ipairs = ipairs +local tonumber = tonumber + +local slash = '\x2f' -- '/' +local slash2 = '\x2f\x2f' -- '//' +local point = '\x2e' -- '.' +local point2 = '\x2e\x2e' -- '..' + +local class = require "class" + +local Router = class("httpd-route") + +Router.API, Router.USE, Router.STATIC, Router.WS = 1, 2, 3, 4 + +function Router:ctor (opt) + self.rests = {} -- rest路由 + self.routes = {} -- 普通路由 + self.statics = {} -- 静态文件路由 + self.enable_rest = false -- 默认关闭rest路由支持 +end + +function Router:enable_rest_route () + self.enable_rest = true +end + +function Router:tonumber (v) + return tonumber(v) +end + +function Router:toarray (v, t) + if type(v) ~= 'string' or #v < 3 then + return v + end + local array = new_tab(32, 0) + for str in splite(v, "[^,%[%]%{%}]+") do + if not t or t == 'string[]' then + array[#array+1] = str + else + array[#array+1] = tonumber(str) or tonumber(str:gsub("^0x", ""), 16) + end + end + return array +end + +-- 将rest路由转换匹配模式语法 +function Router:to_regex (r) + if type(r) ~= 'string' or not find(r, "[{}]") then + return + end + local regex = r + :gsub("%+", "%%+") + :gsub("%-", "%%-") + :gsub("%.", "%%.") + :gsub("%*", "%%*") + :gsub("[/]+", "/") + :gsub("/", "[/]-") + local args = {} + for p in splite(r, "%{([^/]-)%}") do + local t, v = match(p, "([^:{}]+):([^:{}]+)") + if t ~= 'string' and t ~= 'number' and t ~= 'string[]' and t ~= 'number[]' then + local name = match(p, "([^}{]+)") + if not name then + error("Invalid rest router syntex in [" .. r .. "], type is not support.") + end + t, v = "string", name + end + local reg = "([^/]+)" + if t == 'number' then + reg = "([%%d]+[%%.]?[%%d]-)" + end + regex = spliter(regex, "{" .. spliter(t, "%[%]", "%%[%%]") .. ":" .. v .. "}", reg) + regex = spliter(regex, "{" .. v .. "}", reg) + args[#args+1] = { k = v, t = t } + end + if byte(r, #r) == byte("/") then + regex = regex .. "$" + else + regex = regex .. "[/]-$" + end + args["route"] = r + args["regex"] = "^" .. regex + -- LOG:DEBUG(args) + return regex, args +end + +function Router:match_regex (route, regex) + return match(route, regex) +end + +function Router:to_route (route) + local r = spliter(route, "([/]+)", '/') + if byte(r, #r) ~= byte(slash) then + return r + end + return split(r, 1, -2) +end + +function Router:hex_route (route) + local tab = new_tab(32, 0) + for r in splite(route, '/([^ /%?]+)') do + if r ~= '' then + tab[#tab + 1] = r + end + end + return tab +end + +-- 检查是路径回退是否超出静态文件根目录(是否合法路径.) +function Router:is_out_of_directory (path) + local deep = 1 + for r in splite(path, "/([^/#%?]+)") do + if r == point2 then + deep = deep - 1 + elseif r ~= point then + deep = deep + 1 + end + if deep == 0 then + return true + end + end + return false +end + +-- 路由查找 +function Router:find (method, path) + -- 检查是否能O(1)定位普通路由 + path = url_decode(split(path, 1, (find(path, '?') or 0) - 1)) + local r = self.routes[self:to_route(path)] + if r then + return r.class, r.type + end + -- 检查是否需要查找rest路由 + if self.enable_rest then + for regex, cls in pairs(self.rests) do + local args_list = table.pack(self:match_regex(path, regex)) + if args_list and #args_list == #cls then + local args = new_tab(8, 0) + for index, arg in ipairs(args_list) do + local item = cls[index] + local t = item.t + local k = item.k + if t == 'number' then + args[k] = self:tonumber(arg) + elseif t == 'number[]' or t == 'string[]' then + args[k] = self:toarray(arg, t) + else + args[k] = arg + end + end + return cls.class, cls.type, args + end + end + end + -- 查找静态文件路由(文件) + local prefix, typ = self.statics.prefix, self.statics.type + -- LOG:DEBUG(prefix, typ) + if not prefix and not typ then + return + end + -- 非GET/HEAD方法不查找静态文件 + if method ~= 'GET' and method ~= 'HEAD' then + return + end + -- 凡是超出静态文件根目录返回404. + if self:is_out_of_directory(path) then + return + end + -- 构建静态静态文件检查器 + if not self.load_file then + self.load_file = function ( path ) + local filepath = prefix .. url_decode(path) + local stat = aio_stat(filepath) + if type(stat) ~= 'table' or stat.mode ~= 'file' then + return + end + return stat.size, filepath, match(filepath, '[%.]?([^%./]+)$') + end + end + return self.load_file, typ +end + +-- 注册rest语法路由与普通路由 +function Router:registery (route, class, route_type) + -- 过滤错误的路由输入 + if type(route) ~= 'string' or route == '' then + return LOG:WARN('Please Do not add empty string in route registery method :)') + end + if find(route, slash2) then -- 不允许出现路由出现[//] + return LOG:WARN('Please Do not add [//] in route registery method :)') + end + local regex, args = self:to_regex(route) + if regex and args then + self.rests[regex], args["class"], args["type"] = args, class, route_type + end + self.routes[self:to_route(route)] = {class = class, type = route_type} +end + +-- 注册静态文件路由 +function Router:static (route_prefix, route_type) + if next(self.statics) then + return + end + self.statics.prefix, self.statics.type = route_prefix, route_type +end + +return Router diff --git a/lualib/httpd/Throw.lua b/lualib/httpd/Throw.lua new file mode 100644 index 00000000..caa8e255 --- /dev/null +++ b/lualib/httpd/Throw.lua @@ -0,0 +1,25 @@ +local type = type +local assert = assert +local toint = math.tointeger + +---comment 检查http code是否合法. +---@param code integer @响应内容. +---@return boolean | integer +local function check_code(code) + code = toint(code) + return code and code >= 400 and code < 600 and code or nil +end + +---comment 检查http response是否合法. +---@param response string @响应内容. +---@return boolean | string +local function check_response (response) + return type(response) == 'string' and response ~= '' and response or nil +end + +---comment 让注册的`USE`/`API`路由可以合法的抛出异常. +---@param code integer @HTTP状态码. +---@param response string @异常附带的响应体. +return function (code, response) + return { __OPCODE__ = -256, __CODE__ = assert(check_code(code), "Invalid http code."), __MSG__ = assert(check_response(response), "Invalid http response.") } +end \ No newline at end of file diff --git a/lualib/httpd/http.lua b/lualib/httpd/http.lua index 5649b7dd..91a1c8d5 100644 --- a/lualib/httpd/http.lua +++ b/lualib/httpd/http.lua @@ -2,34 +2,38 @@ local type = type local assert = assert local find = string.find -local http = {} ---[[ - httpd 的 before函数本身仅根据状态码来确定具体Action - 如果使用者在函数内直接写入状态码, 请确保您非常清楚before函数的实现. ---]] +---comment 可以用来控制`before`方法内的行为. +local HTTP = {} - --- 成功 -function http.ok() +---comment 允许`httpd.before`通过(passed) +function HTTP.ok() return 200 end --- 重定向 -function http.redirect(domain, code) - assert(type(domain) == 'string' and domain ~= '' and find(domain, '^http[s]?://.+'), '重定向必须给出一个字符串类型的域名(http[s]://domain)') - if type(code) == 'number' and (code == 301 or code == 302) then - return code, domain +---comment 此方法可以让开发者在`httpd.before`函数内重定向. +---@param code integer @重定向的HTTP状态码(301,302,303,307,308) +---@param url string @重定向的url(`https://cfadmin.cn` or `/api`) +---@return integer @此方法只可用在`httpd:bedore`方法内. +---@return string @调用方法必须是`return http.redirect(code, url)` +function HTTP.redirect(code, url) + assert(type(url) == 'string' and url ~= '' and (find(url, '^http[s]?://.+') or find(url, '^/.*')), 'Redirection must give a string type domain name ("http[s]://domain" or "/")') + if type(code) == 'number' and (code == 301 or code == 302 or code == 303 or code == 307 or code == 308) then + return code, url end - return 302, domain + return 302, url end --- 指定错误码 -function http.throw(code, body) - assert(type(code) == 'number' and code >= 400 and code < 500, '指定错误码必须在范围之内(400 - 499)') +---comment 此方法可以让开发者在`httpd.before`函数内抛出异常. +---@param code integer @`HTTP`异常状态码(`400`-`600`之间) +---@param body string @异常的具体内容(optional) +---@return integer @此方法只可用在`httpd:bedore`方法内. +---@return string? @调用方法必须是`return http.throw(code, body)` +function HTTP.throw(code, body) + assert(type(code) == 'number' and code >= 400 and code < 600, 'The specified error code must be within the range (400-600).') if type(body) == 'string' and body ~= '' then return code, body end return code end -return http \ No newline at end of file +return HTTP \ No newline at end of file diff --git a/lualib/httpd/init.lua b/lualib/httpd/init.lua index a69abdf6..b98760ec 100644 --- a/lualib/httpd/init.lua +++ b/lualib/httpd/init.lua @@ -1,51 +1,74 @@ -local HTTP = require "protocol.http" +local http = require "protocol.http" +local router = require "httpd.Router" local tcp = require "internal.TCP" local class = require "class" -local sys = require "system" -local log = require "log" +local log = require "logging" local cf = require "cf" +local sys = require("sys") +local os_date = sys.date +local ipv4 = sys.ipv4 +local ipv6 = sys.ipv6 + local type = type local ipairs = ipairs local fmt = string.format local match = string.match -local io_open = io.open -local os_date = os.date +local io_write = io.write +local io_type = io.type +local io_output = io.output +local toint = math.tointeger -- 请求解析 -local EVENT_DISPATCH = HTTP.EVENT_DISPATCH - --- 注册HTTP路由 -local HTTP_ROUTE_REGISTERY = HTTP.ROUTE_REGISTERY +local RAW_DISPATCH = http.RAW_DISPATCH local httpd = class("httpd") function httpd:ctor(opt) - self.API = HTTP.API - self.USE = HTTP.USE - self.IO = tcp:new() + self.sock = tcp:new() + self.router = router:new() + self.WS = self.router.WS + self.API = self.router.API + self.USE = self.router.USE + self.STATIC = self.router.STATIC + self.__timeout = nil + self.__server = nil + self.__before_func = nil + self.__max_path_size = 1024 + self.__max_header_size = 65535 + self.__max_body_size = 1 * 1024 * 1024 + self.__compress_bytes = 128 + self.__enable_gzip = false + self.__enable_cookie = false + self.__enable_cros_timeout = nil end --- 用来注册WebSocket对象 +---comment 注册WebSocket路由 +---@param route string @路由地址 +---@param class table @路由class function httpd:ws(route, class) if route and type(class) == "table" then - -- HTTP_ROUTE_REGISTERY(self.routes, route, class, HTTP.WS) - HTTP_ROUTE_REGISTERY(route, class, HTTP.WS) + self.router:registery(route, class, self.WS) end end --- 用来注册接口 +---comment 用来注册api路由 +---@param route string @路由地址 +---@param class function @回调函数 function httpd:api(route, class) if route and (type(class) == "table" or type(class) == "function")then - HTTP_ROUTE_REGISTERY(route, class, HTTP.API) + self.router:registery(route, class, self.API) end end --- 用来注册普通路由 + +---comment 用来注册use路由 +---@param route string @路由地址 +---@param class function @回调函数 function httpd:use(route, class) if route and (type(class) == "table" or type(class) == "function") then - HTTP_ROUTE_REGISTERY(route, class, HTTP.USE) + self.router:registery(route, class, self.USE) end end @@ -55,110 +78,222 @@ function httpd:group(target, prefix, array) for _, route in ipairs(array) do local r, c = route['route'], route['class'] if target == self.USE then - self:use(prefix..r, c) + self:use(prefix .. r, c) else - self:api(prefix..r, c) + self:api(prefix .. r, c) end end end --- 最大URI长度 +---comment 最大URI长度 +---@param path_size integer @最大请求路由长度 function httpd:max_path_size(path_size) if type(path_size) == 'number' then self.__max_path_size = path_size end end --- 最大Header长度 +---comment 最大Header长度 +---@param header_size integer @最大headers长度 function httpd:max_header_size(header_size) if type(header_size) == 'number' then self.__max_header_size = header_size end end --- 最大Body长度 +---comment 最大Body长度 +---@param body_size integer @最大body长度 function httpd:max_body_size(body_size) if type(body_size) == 'number' then self.__max_body_size = body_size end end --- 在路由函数被执行之后执行此方法 +---comment 在`API`与`USE`路由处理之前调用, 通常为中间件注册使用; +---@param func function @回调函数 function httpd:before(func) if type(func) == 'function' then - self._before_func = func + self.__before_func = func end end --- 可自定义Server Name +---comment 指定响应头部的Server字段内容 +---@param server string @指定header的server_name function httpd:server_name(server) - if type(server) == "string" then + if type(server) == "string" and server ~= '' then self.__server = server end end +---comment 连接保持时间 +---@param timeout number @超时时间必须大于0 function httpd:timeout(timeout) - if type(timeout) == "number" then + if type(timeout) == "number" and timeout > 0 then self.__timeout = timeout end end --- 注册静态文件读取路径, foldor是一个目录, ttl是静态文件缓存周期 +---comment 开启跨域支持 +---@param timeout integer @跨域支持 +function httpd:enable_cros(timeout) + if toint(timeout) and toint(timeout) > 0 then + self.__enable_cros_timeout = toint(timeout) + else + self.__enable_cros_timeout = '86400' + end +end + +--- 开启`gzip`/`deflate`压缩算法支持, 如果手动安装`lua-br`则可以自动识别并使用`brotli`压缩算法. +---@param compress_bytes integer @指定压缩的最小字节数(不可低于128). +function httpd:enable_gzip(compress_bytes) + self.__enable_gzip = true + if toint(compress_bytes) and toint(compress_bytes) > self.__compress_bytes then + self.__compress_bytes = compress_bytes + end +end + +---comment 开启rest路由注册支持, 具体使用方式参考官网文档 +function httpd:enable_rest () + self.router:enable_rest_route() +end + +---comment 是否记录解析cookie +function httpd:enable_cookie () + self.__enable_cookie = true +end + +---comment 开启错误页面显示 +function httpd:enable_error_pages () + self.__enable_error_pages = true +end + +---comment 设置Cookie加密Key +---@param secure string @开启并设置`Cookie`密钥 +function httpd:cookie_secure (secure) + if type(secure) == 'string' and secure ~= '' then + self.__cookie_secure = secure + end +end + +---comment 注册静态文件读取路径, foldor是一个目录, ttl是静态文件缓存周期 +---@param foldor string @目录名称 +---@param ttl number @设置缓存时间 function httpd:static(foldor, ttl) - if foldor and type(foldor) == 'string' and #foldor > 0 then - ttl = math.tointeger(ttl) - if ttl and ttl > 0 then - self.ttl = ttl - end - HTTP_ROUTE_REGISTERY('./'..foldor, function (path) - if path then - local FILE = io_open(path, "rb") - if not FILE then - return - end - local file = FILE:read('*a') - FILE:close() - return file, match(path, '.+%.([%a]+)') - end - end, HTTP.STATIC) + if not self.foldor then + self.foldor = foldor or 'static' + if ttl and ttl > 0 then + self.ttl = ttl end + return self.router:static(self.foldor, self.STATIC) + end end --- 记录日志到文件 +---comment 记录日志到文件 function httpd:log(path) - self.logpath = path or "cf-httpd.log" - log.outfile = self.logpath + if type(path) == 'string' and path ~= '' then + self.logging = log:new({ dump = true, path = path }) + end end +---comment 关闭所有日志 +function httpd:nolog( disable ) + -- disable指定为true后本机将不会生成任何请求日志, 这样能有利于框架提升更高的性能. + self.CLOSE_LOG = disable +end + +---comment LOG_FMT用于构建日志格式 +local LOG_FMT = "[%s] - %s - %s - %s - %s - %d - req_time: %0.6f/Sec\n" + function httpd:tolog(code, path, ip, ip_list, method, speed) - if self.logpath then - if not self.logfile then - local err - self.logfile, err = io_open(self.logpath, "a") - if not self.logfile then - return log.error(self.logpath..":"..err) - end - end - local ok, err = self.logfile:write(fmt("[%s] - %s - %s - %s - %s - %d - req_time: %0.6f/Sec\n", os_date("%Y/%m/%d %H:%M:%S"), ip, ip_list, path, method, code, speed)) - if not ok then - return log.error(self.logpath..":"..err) - end - self.logfile:flush() - end - print(fmt("[%s] - %s - %s - %s - %s - %d - req_time: %0.6f/Sec", os_date("%Y/%m/%d %H:%M:%S"), ip, ip_list, path, method, code, speed)) + if self.CLOSE_LOG then + return + end + local now = os_date("%Y/%m/%d %H:%M:%S") + if self.logging then + self.logging:dump(fmt(LOG_FMT, now, ip, ip_list, path, method, code, speed)) + end + if io_type(io_output()) == 'file' then + io_write(fmt(LOG_FMT, now, ip, ip_list, path, method, code, speed)) + end +end + +---comment 监听普通套接字与端口 +---@param ip string @需要监听的合法`IP`地址. +---@param port integer @指定一个在有效范围内并未被占用的端口. +---@param backlog integer @默认为`128` +---@return boolean +function httpd:listen(ip, port, backlog) + assert(type(ip) == 'string' and toint(port), "httpd error: invalid ip or port") + self.ip, self.port = (ipv4(ip) or ipv6(ip)) and ip or "0.0.0.0", port + self.sock:set_backlog(toint(backlog) and toint(backlog) > 0 and toint(backlog) or 128) + return assert(self.sock:listen(self.ip, self.port, + function (fd, ipaddr, port) + return RAW_DISPATCH(fd, { ipaddr = match(ipaddr, '^::[f]+:(.+)') or ipaddr, port = port }, self) + end) + ) end --- 监听请求 -function httpd:listen(ip, port) - return self.IO:listen(ip, port, function (fd, ipaddr) - ipaddr = match(ipaddr, '^::[f]+:(.+)') or ipaddr - return EVENT_DISPATCH(fd, ipaddr, self) +---comment 监听加密套接字与端口 +---@param ip string @需要监听的合法`IP`地址. +---@param port integer @指定一个在有效范围内并未被占用的端口. +---@param backlog integer @默认为`128`, 这一般已经够用了. +---@param key string @指定TLS套接字所需的私钥所在路径; +---@param cert string @指定TLS套接字所需的证书所在路径; +---@param pw string @如果证书和私钥设置的密码请填写此字段; +---@return boolean +function httpd:listen_ssl(ip, port, backlog, key, cert, pw) + assert(type(ip) == 'string' and toint(port), "httpd error: invalid ip or port") + self.ssl_ip, self.ssl_port = (ipv4(ip) or ipv6(ip)) and ip or "0.0.0.0", port + self.ssl_key, self.ssl_cert, self.ssl_pw = key, cert, pw + self.sock:set_backlog(toint(backlog) and toint(backlog) > 0 and toint(backlog) or 128) + return assert(self.sock:listen_ssl(self.ssl_ip, self.ssl_port, { cert = self.ssl_cert, key = self.ssl_key, pw = self.ssl_pw }, + function (sock, ipaddr, port) + return RAW_DISPATCH(sock, { ipaddr = match(ipaddr, '^::[f]+:(.+)') or ipaddr, port = port }, self) end) + ) end --- 正确的运行方式 +---comment 监听`unixsock`套接字 +---@param unix_domain_path string @`unixdomain`所在路径 +---@param backlog integer @默认为128 +---@return boolean +function httpd:listenx(unix_domain_path, backlog) + assert(type(unix_domain_path) == 'string' and unix_domain_path ~= '', "httpd error: invalid unix domain path") + self.unix_domain_path = unix_domain_path + self.sock:set_backlog(toint(backlog) and toint(backlog) > 0 and toint(backlog) or 128) + return assert(self.sock:listen_ex(unix_domain_path, true, function (fd, ipaddr) + return RAW_DISPATCH(fd, { ipaddr = match(ipaddr, '^::[f]+:(.+)') or ipaddr }, self) + end)) +end + +---comment 此方法应该在配置完成所有`httpd`配置后调用, 此方法之后的代码或将永远不会被执行. function httpd:run() - return cf.wait() + if self.ip and self.port then + if self.logging then + self.logging:dump(fmt('[%s] [INFO] httpd listen: %s:%d \n', os_date("%Y/%m/%d %H:%M:%S"), self.ip, self.port)) + end + io_write(fmt('\27[32m[%s] [INFO]\27[0m httpd listen: %s:%d \n', os_date("%Y/%m/%d %H:%M:%S"), self.ip, self.port)) + end + + if self.unix_domain_path then + if self.logging then + self.logging:dump(fmt('[%s] [INFO] httpd listen: %s\n', os_date("%Y/%m/%d %H:%M:%S"), self.unix_domain_path)) + end + io_write(fmt('\27[32m[%s] [INFO]\27[0m httpd listen: %s\n', os_date("%Y/%m/%d %H:%M:%S"), self.unix_domain_path)) + end + + if self.ssl_ip and self.ssl_port and self.ssl_key and self.ssl_cert then + if self.logging then + self.logging:dump(fmt('[%s] [INFO] httpd ssl listen: %s:%d\n', os_date("%Y/%m/%d %H:%M:%S"), self.ssl_ip, self.ssl_port)) + end + io_write(fmt('\27[32m[%s] [INFO]\27[0m httpd ssl listen: %s:%d\n', os_date("%Y/%m/%d %H:%M:%S"), self.ssl_ip, self.ssl_port)) + end + + if self.logging then + self.logging:dump(fmt('[%s] [INFO] httpd Web Server Running...\n', os_date("%Y/%m/%d %H:%M:%S"))) + end + io_write(fmt('\27[32m[%s] [INFO]\27[0m httpd Web Server Running...\n', os_date("%Y/%m/%d %H:%M:%S"))) + return cf.wait() end return httpd diff --git a/lualib/internal/Co.lua b/lualib/internal/Co.lua index e6164b34..7ae82be5 100644 --- a/lualib/internal/Co.lua +++ b/lualib/internal/Co.lua @@ -1,117 +1,187 @@ -local log = require "log" +local require = require local task = require "task" +local task_new = task.new +local task_start = task.start + +local sys = require "sys" +local new_tab = sys.new_tab local type = type -local assert = assert local error = error +local print = print +local assert = assert +local select = select -local task_new = task.new -local task_stop = task.stop -local task_start = task.start +local os_date = os.date +local fmt = string.format +local dbg_traceback = debug.traceback + +local tunpack = table.unpack +local coroutine = coroutine local co_new = coroutine.create local co_start = coroutine.resume local co_wait = coroutine.yield local co_status = coroutine.status local co_self = coroutine.running - -local insert = table.insert -local remove = table.remove - -local cos = {} - -local main_co = co_self() -local main_task = task_new() - -local TASK_POOL = {} - -local function task_pop() - if #TASK_POOL > 0 then - return remove(TASK_POOL) - end - return task_new() -end - -local function task_push(task) - return insert(TASK_POOL, task) -end - -local CO_POOL = {} - -local function co_pop(func) - if #CO_POOL > 0 then - return remove(CO_POOL) - end - local co = co_new(func) - co_start(co) - return co +local co_close = coroutine.close + +local main_co = nil +local main_task = nil +local main_waited = true + +local co_num = 0 + +local co_map = new_tab(0, 128) +co_map[co_self()] = {co_self(), nil, false} + +local tab = debug.getregistry() +tab['__G_CO__'] = co_map + +local co_wlist = new_tab(128, 0) + +local function co_wrapper() + return co_new(function () + -- 使用`数字索引`比`Hash索引`更快. + local CO_INDEX, ARGS_INDEX, WAKEUP_INDEX = 1, 2, 3 + -- 使用数字下标迭代比`ipairs`更快. + local start, total = 1, #co_wlist + -- 使用两级`FIFO`队列交替管理协程的运行与切换, 并且每次预分配的`FIFO`队列的大小与上次执行的协程的数量相关. + local co_rlist = co_wlist + co_wlist = new_tab(32, 0) + while true do + for index = start, total do + local obj = co_rlist[index] + local co, args = obj[CO_INDEX], obj[ARGS_INDEX] + local ok, errinfo + tab['co'] = co + if args then + ok, errinfo = co_start(co, tunpack(args)) -- 带参数的协程 + else + ok, errinfo = co_start(co) -- `fork`的协程不需要参数 + end + tab['co'] = nil + -- 如果协程`执行出错`或`执行完毕`, 则去掉引用销毁 + if not ok or co_status(co) ~= 'suspended' then + -- 如果发生异常,则应该把异常打印出来. + if not ok then + print(fmt("[%s] [coroutine error] %s", os_date("%Y/%m/%d %H:%M:%S"), dbg_traceback(co, errinfo, 1))) + end + -- 如果支持销毁协程, 则可以尝试回收资源. + if co_close then + co_close(co) + end + co_map[co] = nil + co_num = co_num - 1 + end + obj[ARGS_INDEX], obj[WAKEUP_INDEX] = nil, false + end + -- 如果没有执行对象则应该放弃执行权. + -- 等待有任务之后再次唤醒后再执行 + total = #co_wlist + if total == 0 then + main_waited = true + co_wait() + total = #co_wlist + end + co_rlist = co_wlist + co_wlist = new_tab(total >= 128 and 128 or total, 0) + end + end) end -local function co_push(co) - return insert(CO_POOL, co) +local function co_check_init() + -- 如果尚未初始化资源, 则优先初始化. + if not main_task and not main_co then + main_task = task_new() + main_co = co_wrapper() + end + -- 如果协程未启动, 则启动协程开始运行. + if main_waited then + main_waited = false + task_start(main_task, main_co) + end end -local function f() - while 1 do - local ok, msg = pcall(co_wait()) - if not ok then - log.error(msg) - end - local co, main = co_self() - if not main then - task_push(cos[co]) - co_push(co) - cos[co] = nil - end - end +local function co_add_queue(co, ...) + local args = nil + if select("#", ...) > 0 then + args = {...} + end + local ctx = co_map[co] + if not ctx then + ctx = {co, args, true} + co_map[co] = ctx + else + ctx[2], ctx[3] = args, true + end + co_wlist[#co_wlist+1] = ctx end local Co = {} -- 创建协程 function Co.new(f) - return co_new(f) + return co_new(f) end --- 查找 +-- 获取协程 function Co.self() - return co_self() + return co_self() end --- 让出 +-- 让出协程 function Co.wait() - local co = co_self() - assert(cos[co] or co == main_co, "非cf创建的协程不能让出执行权") - return co_wait() + assert(co_map[co_self()], "[coroutine error]: This coroutine is not associated internally, so it cannot yield.") + return co_wait() end --- 启动 -function Co.spwan(func, ...) - if func and type(func) == "function" then - local co = co_pop(f) - cos[co] = task_pop() - return task_start(cos[co], co, func, ...) - end - error("Co Just Can Spwan a Coroutine to run in sometimes.") +-- 唤醒协程 +function Co.wakeup(co, ...) + if type(co) ~= 'thread' then + error("[coroutine error]: Invalid coroutine.") + end + if co == co_self() then + error("[coroutine error]: Cannot wake up a running coroutine.") + end + if co_status(co) ~= 'suspended' then + error("[coroutine error]: Invalid status coroutine. [" .. co_status(co) .. "]") + end + local ctx = co_map[co] + if not ctx then + error("[coroutine error]: This coroutine is not associated internally, so it cannot wakeup.") + end + if ctx[3] then + error("[coroutine error]: Try to wake up a coroutine several times.") + end + co_check_init() + co_add_queue(co, ...) end --- 唤醒 -function Co.wakeup(co, ...) - assert(type(co) == 'thread', "试图传递一个非协程的类型的参数到wakeup内部.") - local status = co_status(co) - if co == co_self() then - return log.error("不能唤醒当前正在执行的协程") - end - if main_co == co and status == "suspended" then - return task_start(main_task, main_co, ...) - end - local t = cos[co] - assert(t, "非cf创建的协程不能由cf来唤醒") - return task_start(t, co, ...) +-- 创建协程 +function Co.spawn(func, ...) + assert(type(func) == 'function', "[coroutine error]: Invalid callback.") + -- 创建协程与打包参数 + co_num = co_num + 1 + local co = co_new(func) + co_check_init() + co_add_queue(co, ...) + return co end +-- 计算数量 function Co.count() - return #CO_POOL, #TASK_POOL + return co_num +end + +-- 刷新缓存 +function Co.flush() + local map = {} + for key, value in pairs(co_map) do + map[key] = value + end + co_map = map + tab['__G_CO__'] = map end -return Co +return Co \ No newline at end of file diff --git a/lualib/internal/TCP.lua b/lualib/internal/TCP.lua index 4a2de9f5..7e74197c 100644 --- a/lualib/internal/TCP.lua +++ b/lualib/internal/TCP.lua @@ -1,413 +1,862 @@ -local ti = require "internal.Timer" -local dns = require "protocol.dns" -local co = require "internal.Co" local class = require "class" -local tcp = require "tcp" -local log = require "log" -local split = string.sub +local sys = require "sys" +local new_tab = sys.new_tab + +local type = type +local assert = assert +local io_open = io.open +local ssub = string.sub +local spack = string.pack +local sfind = string.find +local concat = table.concat local insert = table.insert local remove = table.remove + +local dns = require "protocol.dns" local dns_resolve = dns.resolve +local co = require "internal.Co" local co_new = co.new local co_wakeup = co.wakeup -local co_spwan = co.spwan +local co_spawn = co.spawn local co_self = co.self local co_wait = coroutine.yield +local ti = require "internal.Timer" +local ti_timeout = ti.timeout + +local tcp = require "tcp" +local tcp_new = tcp.new +local tcp_ssl_new = tcp.new_ssl +local tcp_ssl_new_fd = tcp.new_ssl_fd local tcp_start = tcp.start local tcp_stop = tcp.stop local tcp_free_ssl = tcp.free_ssl local tcp_close = tcp.close local tcp_connect = tcp.connect -local tcp_ssl_connect = tcp.ssl_connect +local tcp_ssl_do_handshake = tcp.ssl_connect local tcp_read = tcp.read local tcp_sslread = tcp.ssl_read local tcp_write = tcp.write local tcp_ssl_write = tcp.ssl_write local tcp_listen = tcp.listen +local tcp_listen_ex = tcp.listen_ex +local tcp_sendfile = tcp.sendfile + +local tcp_peek = tcp.peek +local tcp_sslpeek = tcp.sslpeek local tcp_new_client_fd = tcp.new_client_fd local tcp_new_server_fd = tcp.new_server_fd - +local tcp_new_sever_unixsock_fd = tcp.new_server_unixsock_fd +local tcp_new_client_unixsock_fd = tcp.new_client_unixsock_fd + +local tcp_ssl_verify = tcp.ssl_verify +local tcp_ssl_set_fd = tcp.ssl_set_fd +local tcp_ssl_set_alpn = tcp.ssl_set_alpn +local tcp_ssl_get_alpn = tcp.ssl_get_alpn +local tcp_set_read_buf = tcp.tcp_set_read_buf +local tcp_set_write_buf = tcp.tcp_set_write_buf +local ssl_set_connect_server = tcp.ssl_set_connect_server +local tcp_ssl_set_accept_mode = tcp.ssl_set_accept_mode +local tcp_ssl_set_connect_mode = tcp.ssl_set_connect_mode +local tcp_ssl_set_privatekey = tcp.ssl_set_privatekey +local tcp_ssl_set_certificate = tcp.ssl_set_certificate +local tcp_ssl_set_userdata_key = tcp.ssl_set_userdata_key + +local G_Reference = {} +local tab = debug.getregistry() +tab['__G_TCP__'] = G_Reference local EVENT_READ = 0x01 local EVENT_WRITE = 0x02 -local POOL = {} +local POOL = new_tab(128, 0) local function tcp_pop() - if #POOL > 0 then - return remove(POOL) - end - return tcp.new() + return remove(POOL) or tcp_new() +end + +local function tcp_push(tio) + return insert(POOL, tio) end -local function tcp_push(tcp) - insert(POOL, tcp) +local tlist = new_tab(3, 0) +local function buffer_concat(buf_1, buf_2) + tlist[1], tlist[2] = buf_1, buf_2 + return concat(tlist) end local TCP = class("TCP") function TCP:ctor(...) - +--[[ + -- 当前socke运行模式 + self.mode = nil + -- 默认关闭定时器 + self._timeout = nil + -- 默认backlog + self._backlog = 128 + -- connect 或 accept 得到的文件描述符 + self.fd = nil + -- listen unix domain socket 文件描述符 + self.ufd = nil + -- ssl 对象 + self.ssl = nil + self.ssl_ctx = nil + -- 密钥与证书路径 + self.privatekey_path = nil + self.certificate_path = nil + -- 配套密码 + self.ssl_password = nil +--]] + G_Reference[self] = true end -- 超时时间 function TCP:timeout(Interval) - if Interval and Interval > 0 then - self._timeout = Interval - end - return self + if type(Interval) == 'number' and Interval >= 0 then + self._timeout = Interval + end + return self end -- 设置fd function TCP:set_fd(fd) - if not self.fd then - self.fd = fd - end - return self + if not self.fd then + self.fd = fd + end + return self end -- 设置backlog function TCP:set_backlog(backlog) - if type(backlog) == 'number' and backlog > 0 then - self._backlog = backlog + if type(backlog) == 'number' and backlog > 0 then + self._backlog = backlog + end + return self +end + +-- 设置Read buffer大小 +function TCP:set_read_buffer_size(size) + tcp_set_read_buf(self.fd, size) +end + +-- 设置Write buffer大小 +function TCP:set_write_buffer_size(size) + tcp_set_write_buf(self.fd, size) +end + +-- 开启验证 +function TCP:ssl_set_verify() + if not self.ssl or not self.ssl_ctx then + self.ssl, self.ssl_ctx = tcp_ssl_new() + end + return tcp_ssl_verify(self.ssl, self.ssl_ctx) +end + +-- 设置NPN/ALPN +function TCP:ssl_set_alpn(protocol) + if type(protocol) == 'string' and protocol ~= '' then + if not self.ssl or not self.ssl_ctx then + self.ssl, self.ssl_ctx = tcp_ssl_new() end - return self + self.alpn = protocol + end end -function TCP:send(buf) - if self.ssl then - return log.error("Please use ssl_send method :)") +-- 获取NPN/ALPN +function TCP:ssl_get_alpn() + if not self.ssl or not self.ssl_ctx then + return + end + return tcp_ssl_get_alpn(self.ssl, self.ssl_ctx) +end + +-- 设置私钥 +function TCP:ssl_set_privatekey(privatekey_path) + if not self.ssl or not self.ssl_ctx then + self.ssl, self.ssl_ctx = tcp_ssl_new() + end + assert(type(privatekey_path) == 'string' and privatekey_path ~= '', "Invalid privatekey_path") + self.privatekey_path = privatekey_path + return tcp_ssl_set_privatekey(self.ssl, self.ssl_ctx, self.privatekey_path) +end + +-- 设置证书 +function TCP:ssl_set_certificate(certificate_path) + if not self.ssl or not self.ssl_ctx then + self.ssl, self.ssl_ctx = tcp_ssl_new() + end + assert(type(certificate_path) == 'string' and certificate_path ~= '', "Invalid certificate_path") + self.certificate_path = certificate_path + return tcp_ssl_set_certificate(self.ssl, self.ssl_ctx, self.certificate_path) +end + +-- 设置证书与私钥的密码 +function TCP:ssl_set_password(password) + if not self.ssl or not self.ssl_ctx then + self.ssl, self.ssl_ctx = tcp_ssl_new() + end + assert(type(password) == 'string', "not have ssl or ssl_ctx.") + self.ssl_password = password + return tcp_ssl_set_userdata_key(self.ssl, self.ssl_ctx, self.ssl_password) +end + +-- sendfile实现. +function TCP:sendfile (filename, offset) + if self.ssl or self.ssl_ctx then + return self:ssl_sendfile(filename, offset) + end + if type(filename) == 'string' and filename ~= '' then + local co = co_self() + self.SEND_IO = tcp_pop() + self.sendfile_current_co = co_self() + self.sendfile_co = co_new(function (ok) + tcp_stop(self.SEND_IO) + tcp_push(self.SEND_IO) + self.SEND_IO = nil + self.sendfile_co = nil + self.sendfile_current_co = nil + return co_wakeup(co, ok) + end) + tcp_sendfile(self.SEND_IO, self.sendfile_co, filename, self.fd, offset or 65535) + return co_wait() + end +end + +-- ssl_sendfile实现 +function TCP:ssl_sendfile(filename, offset) + if type(filename) ~= 'string' or filename == '' then + return nil, "Invalid filename." + end + local f, err = io_open(filename, "r") + if not f then + return nil, err + end + for buf in f:lines(offset or 65535) do + if not self:ssl_send(buf) then + return false, f:close() end + end + return true, f:close() +end + +function TCP:send(buf) + if self.ssl then + return self:ssl_send(buf) + end + if not self.fd or type(buf) ~= 'string' or buf == '' then + return + end + local wlen = tcp_write(self.fd, buf, 0) + if not wlen or wlen == #buf then + return wlen == #buf + end + assert(not self.send_co, "[TCP ERROR]: Try to call the 'send' method multiple times.") + local co = co_self() + self.SEND_IO = tcp_pop() + self.send_current_co = co_self() + self.send_co = co_new(function ( ) while 1 do - local len = tcp_write(self.fd, buf, #buf) - if not len or len == #buf then - return len == #buf - end - if len == 0 then - self.SEND_IO = tcp_pop() - local co = co_self() - self.send_current_co = co_self() - self.send_co = co_new(function ( ... ) - while 1 do - local len = tcp_write(self.fd, buf, #buf) - if not len or len == #buf then - tcp_push(self.SEND_IO) - tcp_stop(self.SEND_IO) - self.SEND_IO = nil - self.send_co = nil - self.send_current_co = nil - return co_wakeup(co, len == #buf) - end - buf = split(buf, len + 1, -1) - co_wait() - end - end) - tcp_start(self.SEND_IO, self.fd, EVENT_WRITE, self.send_co) - return co_wait() - end - buf = split(buf, len + 1, -1) + local len = tcp_write(self.fd, buf, wlen) + if not len or len + wlen == #buf then + tcp_stop(self.SEND_IO) + tcp_push(self.SEND_IO) + self.SEND_IO = nil + self.send_co = nil + self.send_current_co = nil + return co_wakeup(co, (len or 0) + wlen == #buf) + end + wlen = wlen + len + co_wait() end + end) + tcp_start(self.SEND_IO, self.fd, EVENT_WRITE, self.send_co) + return co_wait() end function TCP:ssl_send(buf) - if not self.ssl then - return log.error("Please use send method :)") - end + if not self.fd or not self.ssl or type(buf) ~= 'string' or buf == '' then + return nil, "SSL Write Buffer error." + end + local ssl = self.ssl + local wlen = tcp_ssl_write(ssl, buf, #buf) + if not wlen or wlen == #buf then + return wlen == #buf + end + assert(not self.send_co, "[TCP ERROR]: Try to call the 'send' method multiple times.") + local co = co_self() + self.SEND_IO = tcp_pop() + self.send_current_co = co_self() + self.send_co = co_new(function ( ) while 1 do - local len = tcp_ssl_write(self.ssl, buf, #buf) - if not len or len == #buf then - return len == #buf - end - if len == 0 then - self.SEND_IO = tcp_pop() - local co = co_self() - self.send_current_co = co_self() - self.send_co = co_new(function ( ... ) - while 1 do - local len = tcp_ssl_write(self.ssl, buf, #buf) - if not len or len == #buf then - tcp_push(self.SEND_IO) - tcp_stop(self.SEND_IO) - self.SEND_IO = nil - self.send_co = nil - self.send_current_co = nil - -- 这里在发送数据的时候, 客户端可能已经关闭了链接 - -- if not len then log.error("write error.") - return co_wakeup(co, len == #buf) - end - buf = split(buf, len + 1, -1) - co_wait() - end - end) - tcp_start(self.SEND_IO, self.fd, EVENT_WRITE, self.send_co) - return co_wait() - end - buf = split(buf, len + 1, -1) + local len = tcp_ssl_write(ssl, buf, #buf) + if not len or len == #buf then + tcp_stop(self.SEND_IO) + tcp_push(self.SEND_IO) + self.SEND_IO = nil + self.send_co = nil + self.send_current_co = nil + return co_wakeup(co, len == #buf) + end + co_wait() end + end) + tcp_start(self.SEND_IO, self.fd, EVENT_WRITE, self.send_co) + return co_wait() end -function TCP:recv(bytes) - if self.ssl then - return log.error("Please use ssl_recv method :)") - end - self.READ_IO = tcp_pop() - local co = co_self() - self.read_current_co = co_self() - self.read_co = co_new(function ( ... ) - local buf, len = tcp_read(self.fd, bytes) +-- READLINE +function TCP:readline(sp, nosp) + if self.ssl then + return self:ssl_readline(sp, nosp) + end + if type(sp) ~= 'string' or #sp < 1 then + return nil, "Invalid separator." + end + assert(not self.read_co, "[TCP ERROR]: Try to call the 'readline' method multiple times.") + local _timeout = self._timeout + local fd, buffer = self.fd, nil + local asize, msize = 0, 1024 + while 1 do + ::CONTONIE:: + local buf, bsize = tcp_peek(fd, msize, true) + if not buf then + if bsize ~= 0 then + return false, bsize + end + local co = co_self() + self.READ_IO = tcp_pop() + self.read_co = co_new(function ( ) if self.timer then - self.timer:stop() - self.timer = nil + self.timer:stop() + self.timer = nil end tcp_push(self.READ_IO) tcp_stop(self.READ_IO) self.READ_IO = nil self.read_co = nil - self.read_current_co =nil - if not buf then - return co_wakeup(co) - end - return co_wakeup(co, buf, len) - end) - self.timer = ti.timeout(self._timeout, function ( ... ) + return co_wakeup(co, true) + end) + self.timer = ti_timeout(_timeout, function ( ) tcp_push(self.READ_IO) tcp_stop(self.READ_IO) self.timer = nil - self.read_co = nil self.READ_IO = nil + self.read_co = nil self.read_current_co = nil return co_wakeup(co, nil, "read timeout") - end) - tcp_start(self.READ_IO, self.fd, EVENT_READ, self.read_co) - return co_wait() + end) + tcp_start(self.READ_IO, fd, EVENT_READ, self.read_co) + local ok, errinfo = co_wait() + if not ok then + return false, errinfo + end + goto CONTONIE + end + asize = asize + bsize + buffer = buffer and buffer_concat(buffer, buf) or buf + local s, e = sfind(buffer, sp) + if s and e then + tcp_peek(fd, bsize - (asize - e), false) + if nosp then + e = s - 1 + end + return ssub(buffer, 1, e), e + end + tcp_peek(fd, bsize, false) + end end -function TCP:ssl_recv(bytes) - if not self.ssl then - return log.error("Please use recv method :)") - end - local buf, len = tcp_sslread(self.ssl, bytes) +-- SSL READLINE +function TCP:ssl_readline(sp, nosp) + if not self.ssl then + return self:readline(sp, nosp) + end + if type(sp) ~= 'string' or #sp < 1 then + return nil, "Invalid separator." + end + assert(not self.read_co, "[TCP ERROR]: Try to call the 'ssl_readline' method multiple times.") + local _timeout = self._timeout + local ssl, buffer = self.ssl, nil + local asize, msize = 0, 1024 + -- 开始读取数据 + while 1 do + ::CONTONIE:: + local buf, bsize = tcp_sslpeek(ssl, msize, true) if not buf then - local co = co_self() - self.read_current_co = co_self() - self.READ_IO = tcp_pop() - self.read_co = co_new(function ( ... ) - while 1 do - local buf, len = tcp_sslread(self.ssl, bytes) - if not buf and not len then - if self.timer then - self.timer:stop() - self.timer = nil - end - tcp_push(self.READ_IO) - tcp_stop(self.READ_IO) - self.READ_IO = nil - self.read_co = nil - self.read_current_co = nil - return co_wakeup(co) - end - if buf and len then - if self.timer then - self.timer:stop() - self.timer = nil - end - tcp_push(self.READ_IO) - tcp_stop(self.READ_IO) - self.READ_IO = nil - self.read_co = nil - self.read_current_co = nil - return co_wakeup(co, buf, len) - end - co_wait() - end - end) - self.timer = ti.timeout(self._timeout, function ( ... ) - tcp_push(self.READ_IO) - tcp_stop(self.READ_IO) - self.timer = nil - self.READ_IO = nil - self.read_co = nil - self.read_current_co = nil - return co_wakeup(co, nil, "read timeout") - end) - tcp_start(self.READ_IO, self.fd, EVENT_READ, self.read_co) - return co_wait() + if bsize ~= 0 then + return false, bsize + end + local co = co_self() + self.READ_IO = tcp_pop() + self.read_co = co_new(function ( ) + if self.timer then + self.timer:stop() + self.timer = nil + end + tcp_push(self.READ_IO) + tcp_stop(self.READ_IO) + self.READ_IO = nil + self.read_co = nil + return co_wakeup(co, true) + end) + self.timer = ti_timeout(_timeout, function ( ) + tcp_push(self.READ_IO) + tcp_stop(self.READ_IO) + self.timer = nil + self.READ_IO = nil + self.read_co = nil + self.read_current_co = nil + return co_wakeup(co, nil, "read timeout") + end) + tcp_start(self.READ_IO, self.fd, EVENT_READ, self.read_co) + local ok, errinfo = co_wait() + if not ok then + return false, errinfo + end + goto CONTONIE end - return buf, len + asize = asize + bsize + buffer = buffer and buffer_concat(buffer, buf) or buf + local s, e = sfind(buffer, sp) + if s and e then + tcp_sslpeek(ssl, bsize - (asize - e), false) + if nosp then + e = s - 1 + end + return ssub(buffer, 1, e), e + end + tcp_sslpeek(ssl, bsize, false) + end end -function TCP:listen(ip, port, cb) - self.LISTEN_IO = tcp_pop() - self.fd = tcp_new_server_fd(ip, port) - if not self.fd then - return log.error("this IP and port Create A bind or listen method Faild! :) ") +function TCP:recv(bytes) + if self.ssl then + return self:ssl_recv(bytes) + end + local fd = self.fd + local data, len = tcp_read(fd, bytes) + if type(len) ~= 'number' or len > 0 then + return data, len + end + assert(not self.read_co, "[TCP ERROR]: Try to call the 'recv' method multiple times.") + local coctx = co_self() + self.READ_IO = tcp_pop() + self.read_current_co = co_self() + self.read_co = co_new(function ( ) + local buf, bsize = tcp_read(fd, bytes) + if self.timer then + self.timer:stop() + self.timer = nil end - self.co = co_new(function (fd, ipaddr) - while 1 do - if fd and ipaddr then - co_spwan(cb, fd, ipaddr) - fd, ipaddr = co_wait() - end - end - end) - return tcp.listen(self.LISTEN_IO, self.fd, self.co, self._backlog) + tcp_push(self.READ_IO) + tcp_stop(self.READ_IO) + self.READ_IO = nil + self.read_co = nil + self.read_current_co = nil + return co_wakeup(coctx, buf, bsize) + end) + self.timer = ti_timeout(self._timeout, function ( ) + tcp_push(self.READ_IO) + tcp_stop(self.READ_IO) + self.timer = nil + self.read_co = nil + self.READ_IO = nil + self.read_current_co = nil + return co_wakeup(coctx, nil, "read timeout") + end) + tcp_start(self.READ_IO, fd, EVENT_READ, self.read_co) + return co_wait() end -function TCP:connect(domain, port) - local ok, IP = dns_resolve(domain) - if not ok then - return nil, "Can't resolve this domain or ip:"..domain - end - self.fd = tcp_new_client_fd(IP, port) - if not self.fd then - log.error("Connect This IP or Port Faild!"..domain, IP) - return nil, "Connect This host fault! :" - end - local co = co_self() - self.CONNECT_IO = tcp_pop() - self.connect_current_co = co_self() - self.connect_co = co_new(function (connected) +function TCP:ssl_recv(bytes) + local ssl = self.ssl + if not ssl then + return nil, "Please use recv method :)" + end + local buf, len = tcp_sslread(ssl, bytes) + if buf then + return buf, len + end + assert(not self.read_co, "[TCP ERROR]: Try to call the 'recv' method multiple times.") + local coctx = co_self() + self.READ_IO = tcp_pop() + self.read_current_co = co_self() + self.read_co = co_new(function ( ) + while 1 do + local buffer, bsize = tcp_sslread(ssl, bytes) + if (buffer and bsize) or (not buffer and not bsize) then if self.timer then - self.timer:stop() - self.timer = nil + self.timer:stop() + self.timer = nil end - tcp_push(self.CONNECT_IO) - tcp_stop(self.CONNECT_IO) - self.connect_current_co = nil - self.CONNECT_IO = nil - self.connect_co = nil - if connected then - return co_wakeup(co, true) - end - return co_wakeup(co, false, '连接失败') - end) - self.timer = ti.timeout(self._timeout, function ( ... ) - tcp_push(self.CONNECT_IO) - tcp_stop(self.CONNECT_IO) - self.timer = nil - self.CONNECT_IO = nil - self.connect_co = nil - self.connect_current_co = nil - return co_wakeup(co, nil, 'connect timeot.') - end) - tcp_connect(self.CONNECT_IO, self.fd, self.connect_co) - return co_wait() + tcp_push(self.READ_IO) + tcp_stop(self.READ_IO) + self.READ_IO = nil + self.read_co = nil + self.read_current_co = nil + return co_wakeup(coctx, buffer, bsize) + end + co_wait() + end + end) + self.timer = ti_timeout(self._timeout, function ( ) + tcp_push(self.READ_IO) + tcp_stop(self.READ_IO) + self.timer = nil + self.READ_IO = nil + self.read_co = nil + self.read_current_co = nil + return co_wakeup(coctx, nil, "read timeout") + end) + tcp_start(self.READ_IO, self.fd, EVENT_READ, self.read_co) + return co_wait() end -function TCP:ssl_connect(domain, port) - local ok, err = self:connect(domain, port) - if not ok then - return nil, "SSL connect error." +function TCP:listen(ip, port, cb) + self.mode = "server" + self.LISTEN_IO = tcp_pop() + self.fd = tcp_new_server_fd(ip, port, self._backlog or 128) + if not self.fd then + return nil, "Listen port failed. Please check if the port is already occupied." + end + if type(cb) ~= 'function' then + return nil, "Listen function was invalid." + end + self.listen_co = co_new(function (fd, ipaddr, port) + while 1 do + if fd and ipaddr then + co_spawn(cb, fd, ipaddr, port) + fd, ipaddr, port = co_wait() + end end - self.ssl_ctx, self.ssl = tcp.new_ssl(self.fd) - if not self.ssl_ctx or not self.ssl then - return log.error("Create a SSL Error! :) ") + end) + return true, tcp_listen(self.LISTEN_IO, self.fd, self.listen_co) +end + +local function ssl_accept(callback, fd, ipaddr, port, opt) + local sock = TCP:new() + sock:set_fd(fd):timeout(5) -- 如果ssl握手长期未完成则选择断开连接 + sock.ssl, sock.ssl_ctx = tcp_ssl_new_fd(fd) + if type(opt.pw) == 'string' and opt.pw ~= '' then + sock:ssl_set_password(opt.pw) + end + sock.mode = "server" + sock:ssl_set_alpn(opt.alpn) + sock:ssl_set_certificate(opt.cert) + sock:ssl_set_privatekey(opt.key) + tcp_ssl_set_accept_mode(sock.ssl, sock.ssl_ctx) + if not sock:ssl_handshake() then + return sock:close() + end + return callback(sock, ipaddr, port) +end + +function TCP:listen_ssl(ip, port, opt, cb) + self.mode = "server" + self.LISTEN_SSL_IO = tcp_pop() + self.sfd = tcp_new_server_fd(ip, port, self._backlog or 128) + if not self.sfd then + return nil, "Listen port failed. Please check if the port is already occupied." + end + if type(opt) ~= 'table' then + return nil, "ssl listen must have key/cert/pw(optional)." + end + if type(opt.pw) == 'string' and opt.pw ~= '' then + self:ssl_set_password(opt.pw) + end + self:ssl_set_certificate(opt.cert) + self:ssl_set_privatekey(opt.key) + -- 验证证书与私钥有效性 + if not self:ssl_set_verify() then + return nil, "The certificate does not match the private key." + end + if type(cb) ~= 'function' then + return nil, "Listen function was invalid." + end + local sslopt = { timeout = self.timeout, alpn = self.alpn, cert = opt.cert, key = opt.key, pw = opt.pw } + self.listen_ssl_co = co_new(function (fd, ipaddr, port) + while 1 do + if fd and ipaddr then + co_spawn(ssl_accept, cb, fd, ipaddr, port, sslopt) + fd, ipaddr, port = co_wait() + end end - local co = co_self() - self.CONNECT_IO = tcp_pop() - self.connect_current_co = co - self.connect_co = co_new(function () - local EVENTS = EVENT_WRITE - while 1 do - local ok, EVENT = tcp_ssl_connect(self.ssl) - if ok then - if self.timer then - self.timer:stop() - self.timer = nil - end - tcp_push(self.CONNECT_IO) - tcp_stop(self.CONNECT_IO) - self.CONNECT_IO = nil - self.connect_co = nil - self.connect_current_co = nil - return co_wakeup(co, ok) - end - if EVENTS ~= EVENT then - EVENTS = EVENT - tcp_stop(self.CONNECT_IO) - tcp_start(self.CONNECT_IO, self.fd, EVENTS, self.connect_co) - end - co_wait() - end - end) - self.timer = ti.timeout(self._timeout, function ( ... ) - tcp_push(self.CONNECT_IO) - tcp_stop(self.CONNECT_IO) - self.timer = nil - self.CONNECT_IO = nil - self.connect_co = nil - self.connect_current_co = nil - return co_wakeup(co, nil, 'ssl_connect timeot.') - end) - tcp_start(self.CONNECT_IO, self.fd, EVENT_WRITE, self.connect_co) - return co_wait() + end) + return true, tcp_listen(self.LISTEN_SSL_IO, self.sfd, self.listen_ssl_co) end -function TCP:count() - return #POOL +function TCP:listen_ex(unix_domain_path, removed, cb) + self.mode = "server" + self.LISTEN_EX_IO = tcp_pop() + self.ufd = tcp_new_sever_unixsock_fd(unix_domain_path, removed or true, self._backlog or 128) + if not self.ufd then + return nil, "Listen_ex unix domain socket failed. Please check the domain_path was exists and access." + end + if type(cb) ~= 'function' then + return nil, "Listen_ex function was invalid." + end + self.listen_ex_co = co_new(function (fd) + while 1 do + if fd then + co_spawn(cb, fd, "127.0.0.1") + fd = co_wait() + end + end + end) + return true, tcp_listen_ex(self.LISTEN_EX_IO, self.ufd, self.listen_ex_co) end -function TCP:close() +function TCP:connect_ex(path) + self.mode = "client" + self.fd = tcp_new_client_unixsock_fd(assert(type(path) == 'string' and path, "Invalid unix domain path.")) + if not self.fd then + return nil, "Connect to unix domain socket failed." + end + return true +end +function TCP:connect(domain, port) + self.mode = "client" + local ok, IP = dns_resolve(domain) + if not ok then + return nil, "Can't resolve this domain or ip:" .. (domain or IP or "") + end + self.fd = tcp_new_client_fd(IP, port) + if not self.fd then + return nil, "Connect This host fault! "..(domain or "no domain")..":"..(port or "no port") + end + local co = co_self() + self.CONNECT_IO = tcp_pop() + self.connect_current_co = co_self() + self.connect_co = co_new(function (connected, errinfo) if self.timer then - self.timer:stop() - self.timer = nil + self.timer:stop() + self.timer = nil end + tcp_push(self.CONNECT_IO) + tcp_stop(self.CONNECT_IO) + self.connect_current_co = nil + self.CONNECT_IO = nil + self.connect_co = nil + return co_wakeup(co, connected, errinfo) + end) + self.timer = ti_timeout(self._timeout, function () + tcp_push(self.CONNECT_IO) + tcp_stop(self.CONNECT_IO) + self.timer = nil + self.CONNECT_IO = nil + self.connect_co = nil + self.connect_current_co = nil + return co_wakeup(co, nil, 'connect timeout.') + end) + tcp_connect(self.CONNECT_IO, self.fd, self.connect_co) + return co_wait() +end - if self.READ_IO then - tcp_stop(self.READ_IO) - tcp_pop(self.READ_IO) - self.READ_IO = nil - self.read_co = nil - end +function TCP:ssl_connect(domain, port) + local ok, errinfo = self:connect(domain, port) + if not ok then + return false, errinfo + end + return self:ssl_handshake(domain) +end - if self.SEND_IO then - tcp_stop(self.SEND_IO) - tcp_pop(self.SEND_IO) - self.SEND_IO = nil - self.send_co = nil +local function event_wait(self, event) + -- 当前协程对象 + local co = co_self() + self.connect_current_co = co + -- 从对象池之中取出一个观察者对象 + self.CONNECT_IO = tcp_pop() + -- 读/写回调 + self.connect_co = co_new(function ( ) + -- 如果事件在超时之前到来需要停止定时器 + if self.timer then + self.timer:stop() + self.timer = nil end + -- 停止当前IO事件观察者并且将其放入对象池之中 + tcp_stop(self.CONNECT_IO) + tcp_push(self.CONNECT_IO) + self.CONNECT_IO = nil + self.connect_co = nil + self.connect_current_co = nil + -- 唤醒协程 + return co_wakeup(co, true) + end) + -- 定时器回调 + self.timer = ti_timeout(self._timeout, function ( ) + -- 停止当前IO事件观察者并且将其放入对象池之中 + tcp_push(self.CONNECT_IO) + tcp_stop(self.CONNECT_IO) + self.timer = nil + self.CONNECT_IO = nil + self.connect_co = nil + self.connect_current_co = nil + -- 唤醒协程 + return co_wakeup(co, nil, 'connect timeout.') + end) + -- 注册I/O事件 + tcp_start(self.CONNECT_IO, self.fd, event, self.connect_co) + -- 让出执行权 + return co_wait() +end - if self.CONNECT_IO then - tcp_stop(self.CONNECT_IO) - tcp_pop(self.CONNECT_IO) - self.CONNECT_IO = nil - self.connect_co = nil +function TCP:ssl_handshake(domain) + -- 如果设置了NPN/ALPN, 则需要在握手协商中指定. + if self.alpn then + tcp_ssl_set_alpn(self.ssl, self.ssl_ctx, spack(">B", #self.alpn) .. self.alpn) + end + -- 如果是服务端模式, 需要等待客户端先返送hello信息. + -- 如果是客户端模式, 需要先发送hello信息. + if self.mode == "server" then + local ok, err = event_wait(self, EVENT_READ) + if not ok then + return nil, err end - - if self.connect_current_co then - co_wakeup(self.connect_current_co) - self.connect_current_co = nil + else + if not self.ssl_ctx and not self.ssl then + self.ssl, self.ssl_ctx = tcp_ssl_new_fd(self.fd) + else + tcp_ssl_set_fd(self.ssl, self.fd) end - - if self.send_current_co then - co_wakeup(self.send_current_co) - self.send_current_co = nil + -- 如果有必要的话, 增加TLS的SNI特性支持. + ssl_set_connect_server(self.ssl, domain or "localhost") + end + -- 开始握手 + :: CONTINUE :: + local successe, event = tcp_ssl_do_handshake(self.ssl) + if not successe then + -- 握手失败无需继续尝试 + if not event then + return nil, "ssl handshake failed." end - - if self.read_current_co then - co_wakeup(self.read_current_co) - self.read_current_co = nil + -- 获取下次握手的等待事件: `READ` 或 `WRITE` + local ok, errinfo = event_wait(self, event) + if ok then + -- 如果本次尝试成功则继续握手流程 + goto CONTINUE end + -- 握手超时、连接超时或连接中断 + return nil, errinfo + end + -- 握手成功 + return true +end - if self._timeout then - self._timeout = nil - end +function TCP:count() + return #POOL +end - if self.ssl and self.ssl_ctx then - tcp_free_ssl(self.ssl_ctx, self.ssl) - self.ssl_ctx = nil - self.ssl = nil - end +function TCP:close() - if self.fd then - tcp_close(self.fd) - self.fd = nil - end + if self.timer then + self.timer:stop() + self.timer = nil + end + + if self.READ_IO then + tcp_stop(self.READ_IO) + tcp_push(self.READ_IO) + self.READ_IO = nil + self.read_co = nil + end + + if self.SEND_IO then + tcp_stop(self.SEND_IO) + tcp_push(self.SEND_IO) + self.SEND_IO = nil + self.send_co = nil + self.sendfile_co = nil + end + + if self.CONNECT_IO then + tcp_stop(self.CONNECT_IO) + tcp_push(self.CONNECT_IO) + self.CONNECT_IO = nil + self.connect_co = nil + end + + if self.LISTEN_IO then + tcp_stop(self.LISTEN_IO) + tcp_push(self.LISTEN_IO) + self.LISTEN_IO = nil + self.listen_co = nil + end + + if self.LISTEN_EX_IO then + tcp_stop(self.LISTEN_EX_IO) + tcp_push(self.LISTEN_EX_IO) + self.LISTEN_EX_IO = nil + self.listen_ex_co = nil + end + + if self.LISTEN_SSL_IO then + tcp_stop(self.LISTEN_SSL_IO) + tcp_push(self.LISTEN_SSL_IO) + self.LISTEN_SSL_IO = nil + self.listen_ssl_co = nil + end + + if self.connect_current_co then + co_wakeup(self.connect_current_co) + self.connect_current_co = nil + end + + if self.send_current_co then + co_wakeup(self.send_current_co) + self.send_current_co = nil + end + + if self.read_current_co then + co_wakeup(self.read_current_co) + self.read_current_co = nil + end + + if self.sendfile_current_co then + co_wakeup(self.sendfile_current_co) + self.sendfile_current_co = nil + end + + if self._timeout then + self._timeout = nil + end + + if self.ssl and self.ssl_ctx then + tcp_free_ssl(self.ssl, self.ssl_ctx) + self.ssl_ctx = nil + self.ssl = nil + end + + if self.fd then + tcp_close(self.fd) + self.fd = nil + end + + if self.ufd then + tcp_close(self.ufd) + self.ufd = nil + end + + if self.sfd then + tcp_close(self.sfd) + self.sfd = nil + end + + G_Reference[self] = nil +end +---comment 刷新 +function TCP.flush() + local G_REF = {} + for key, value in pairs(G_Reference) do + G_REF[key] = value + end + POOL = {} + G_Reference = G_REF + tab['__G_TCP__'] = G_REF end -return TCP +return TCP \ No newline at end of file diff --git a/lualib/internal/Timer.lua b/lualib/internal/Timer.lua index 63f02529..197adf53 100644 --- a/lualib/internal/Timer.lua +++ b/lualib/internal/Timer.lua @@ -1,144 +1,158 @@ +local sys = require "sys" +local time = sys.time + local co = require "internal.Co" local ti = require "timer" -local log = require "log" local type = type -local pcall = pcall +local pairs = pairs +local setmetatable = setmetatable + local ti_new = ti.new local ti_start = ti.start -local ti_stop = ti.stop local co_new = co.new local co_wait = co.wait -local co_spwan = co.spwan +local co_spawn = co.spawn local co_wakeup = co.wakeup local co_self = co.self -local insert = table.insert -local remove = table.remove +local co_start = coroutine.resume +local co_wait_ex = coroutine.yield local Timer = {} -local TIMER_LIST = {} +local TMap = {} --- 内部函数防止被误用 -local function Timer_new() - if #TIMER_LIST > 0 then - return remove(TIMER_LIST) - end - return ti_new() -end +local tab = debug.getregistry() +tab['__G_TIMER__'] = TMap -local function Timer_release(t) - ti_stop(t) - insert(TIMER_LIST, t) +local function get_tid(offset) + local ts = time() + if offset > 0 then + ts = ts + offset * 1e3 + end + return ts * 0.1 // 1 end -function Timer.count( ... ) - return #TIMER_LIST +local function get_ctx(tid) + return TMap[tid] end --- 超时器 -- -function Timer.timeout(timeout, cb) - if type(timeout) ~= 'number' or timeout <= 0 then - return - end - if type(cb) ~= 'function' then - return - end - local t = Timer_new() - if not t then - return log.error("timeout error: Create timer class error! memory maybe not enough...") - end - local timer = {STOP = false} - timer.stop = function (...) - if timer.STOP then - return - end - Timer_release(t) - timer.STOP = true - timer.co = nil - Timer[timer] = nil - end - timer.co = co_new(function (...) - Timer_release(t) - local ok, err = pcall(cb) - if not ok then - log.error('timeout error:', err) - end - if timer.STOP then - return - end - Timer[timer] = nil - timer.STOP = true - timer.co = nil - end) - Timer[timer] = timer - ti_start(t, timeout, timer.co) - return timer +local function set_ctx(tid, ctx) + if not ctx then + TMap[tid] = nil + return + end + local list = TMap[tid] + if not list then + TMap[tid] = {ctx} + else + list[#list+1] = ctx + end + return ctx end --- 循环定时器 -- -function Timer.at(repeats, cb) - if type(repeats) ~= 'number' or repeats <= 0 then - return - end - if type(cb) ~= 'function' then - return - end - local t = Timer_new() - if not t then - return log.error("timeout error: Create timer class error! memory maybe not enough...") - end - local timer = { STOP = false } - timer.stop = function (...) - if timer.STOP then - return - end - Timer_release(t) - timer.STOP = true - timer.co = nil - Timer[timer] = nil - end - timer.co = co_new(function () - local co_wait = coroutine.yield - while 1 do - if timer.STOP then - return +local TTimer = ti_new() +Timer.TTimer = TTimer + +Timer.TCo = co_new(function () + local run_idx, time_idx, func_idx, again_idx, async_idx = 1, 2, 3, 4, 5 + local TNow = get_tid(0) + co_wait_ex(co_self()) + while true do + local now = get_tid(0) + -- print("距离", now - TNow ) + for tid = TNow, now, 1 do + local list = get_ctx(tid) + if list then + for idx = 1, #list do + local ctx = list[idx] + if ctx[run_idx] then + if ctx[async_idx] then + ctx[func_idx]() + else + co_spawn(ctx[func_idx]) end - co_spwan(cb) - if timer.STOP then - return + if ctx[again_idx] then -- 如果需要重复 + set_ctx(get_tid(ctx[time_idx]), ctx) end - co_wait() + end end - end) - Timer[timer] = timer - ti_start(t, repeats, timer.co) - return timer + set_ctx(tid, nil) + end + end + TNow = now + 1 + co_wait_ex() + end +end) + +-- 初始化 +co_start(Timer.TCo) +-- 选更小的时间来定期检查. +ti_start(TTimer, 0.01, Timer.TCo) + +---@class Timer @定时器对象 +local class = require "class" + +local TIMER = class("Timer") + +-- 初始化 +function TIMER:ctor() end +-- 停止定时器 +function TIMER:stop() if self then self[1] = false end end + +---comment 初始化定时器对象 +---@param timeout number @超时时间 +---@param again boolean @是否重复 +---@param async boolean @直接调用 +---@param func function @回调函数 +---@return Timer @定时器对象 +local function Timer_Init(timeout, again, async, func) + return set_ctx(get_tid(timeout), setmetatable({true, timeout, func, again, async}, TIMER)) end --- 休眠 -- -function Timer.sleep(repeats) - if type(repeats) ~= 'number' or repeats <= 0 then - return - end - local t = Timer_new() - if not t then - return log.error("timeout error: Create timer class error! memory maybe not enough...") - end - local timer = {} - timer.current_co = co_self() - timer.co = co_new(function (...) - local current_co = timer.current_co - Timer[timer] = nil - timer.current_co, timer.co = nil - Timer_release(t) - return co_wakeup(current_co) - end) - Timer[timer] = timer - ti_start(t, repeats, timer.co) - return co_wait() +---comment 一次性定时器 +---@param timeout number @超时时间 +---@param callback function @回调函数 +function Timer.timeout(timeout, callback) + if type(timeout) ~= 'number' or timeout <= 0 or type(callback) ~= 'function' then + return + end + return Timer_Init(timeout, false, false, callback) +end + +---comment 重复定时器 +---@param repeats number @间隔时间 +---@param callback function @回调函数 +function Timer.at(repeats, callback) + if type(repeats) ~= 'number' or repeats <= 0 or type(callback) ~= 'function' then + return + end + return Timer_Init(repeats, true, false, callback) +end + +---comment 休眠当前协程 +---@param nsleep number @休眠时间(毫秒) +function Timer.sleep(nsleep) + if type(nsleep) ~= 'number' or nsleep <= 0 then + return + end + local coctx = co_self() + Timer_Init(nsleep, false, true, function () + return co_wakeup(coctx) + end) + co_wait() +end + +---comment 刷新 +function Timer.flush() + local Map = {} + for key, value in pairs(TMap) do + Map[key] = value + end + TMap = Map + tab['__G_TIMER__'] = Map end -return Timer +return Timer \ No newline at end of file diff --git a/lualib/internal/UDP.lua b/lualib/internal/UDP.lua index 28765084..1d30969d 100644 --- a/lualib/internal/UDP.lua +++ b/lualib/internal/UDP.lua @@ -1,6 +1,5 @@ local ti = require "internal.Timer" local co = require "internal.Co" -local log = require "log" local udp = require "udp" local class = require "class" @@ -9,80 +8,98 @@ local co_self = co.self local co_wakeup = co.wakeup local co_wait = co.wait +local G_Reference = {} +local tab = debug.getregistry() +tab['__G_UDP__'] = G_Reference + local UDP = class("UDP") function UDP:ctor(opt) - self.udp = udp.new() + self.udp = udp.new() + G_Reference[self] = true end -- 超时时间 function UDP:timeout(Interval) - if Interval and Interval > 0 then - self._timeout = Interval - end - return self + if type(Interval) == 'number' and Interval >= 0 then + self._timeout = Interval + end + return self end - function UDP:connect(ip, port) - if not self.udp then - return nil, "Can't Create a UDP socket." - end - self.fd = udp.connect(ip, port) - if self.fd < 0 then - return nil, "a peer of connect from udp port maybe closed." - end - return true + self.fd = udp.connect(ip, port) + if not self.fd or self.fd <= 0 then + return nil, "Can't Creat UDP Socket" + end + return true end function UDP:recv(...) - if self.udp then - local co = co_self() - self.read_co = co_new(function ( ... ) - local data, len = udp.recv(self.fd) - if self.timer then - self.timer:stop() - self.timer = nil - end - udp.stop(self.udp) - self.read_co = nil - if data then - return co_wakeup(co, data, len) - end - return co_wakeup(co, nil, '未知的udp错误') - end) - self.timer = ti.timeout(self._timeout, function ( ... ) - udp.stop(self.udp) - self.read_co = nil - self.timer = nil - return co_wakeup(co, nil, 'udp_recv timout(超时)..') - end) - udp.start(self.udp, self.fd, self.read_co) - return co_wait() - end + if self.udp then + local co = co_self() + self.read_co = co_new(function ( ... ) + local data, len = udp.recv(self.fd) + if self.timer then + self.timer:stop() + self.timer = nil + end + udp.stop(self.udp) + self.read_co = nil + if data then + return co_wakeup(co, data, len) + end + return co_wakeup(co, nil, '未知的udp错误') + end) + self.timer = ti.timeout(self._timeout, function ( ... ) + udp.stop(self.udp) + self.read_co = nil + self.timer = nil + return co_wakeup(co, nil, 'udp_recv timout(超时)..') + end) + udp.start(self.udp, self.fd, self.read_co) + return co_wait() + end end function UDP:send(data) - assert(not self.udp or self.fd or self.fd < 0, "UDP ERROR 参数不完整.") - return udp.send(self.fd, data, #data) + if type(data) ~= 'string' or not self.fd or self.fd <= 0 then + return + end + return udp.send(self.fd, data, #data) end function UDP:close() + if self.udp then + udp.stop(udp) + self.udp = nil + end - if self.udp then - self.udp = nil - end + if self.timer then + ti.stop(self.timer) + self.timer = nil + end - if self.fd then - udp.close(self.fd) - self.fd = nil - end + if self.fd then + udp.close(self.fd) + self.fd = nil + end - if self._timeout then - self._timeout = nil - end + if self._timeout then + self._timeout = nil + end + + G_Reference[self] = nil +end - -- var_dump(self) +---comment 刷新 +function UDP.flush() + local G_REF = {} + for key, value in pairs(G_Reference) do + G_REF[key] = value + end + G_Reference = G_REF + tab['__G_UDP__'] = G_REF end -return UDP \ No newline at end of file +return UDP diff --git a/lualib/json.lua b/lualib/json.lua deleted file mode 100644 index a81e105a..00000000 --- a/lualib/json.lua +++ /dev/null @@ -1,26 +0,0 @@ -local cjson = require "cjson" - --- this lib fork from resty cjson, only modified some compatible codes --- more details please check it. -cjson.decode_array_with_array_mt(true) - -local CJSON = { - null = null, - _VERSION = cjson._VERSION, - encode = cjson.encode, - decode = cjson.decode, - array_mt = cjson.array_mt, - empty_array = cjson.empty_array, - empty_array_mt = cjson.empty_array_mt, - encode_max_depth = cjson.encode_max_depth, - decode_max_depth = cjson.decode_max_depth, - decode_array_with_array_mt = cjson.decode_array_with_array_mt, - encode_empty_table_as_object = cjson.encode_empty_table_as_object, -} - --- 设置稀疏数组用null填充 -function CJSON.sparse_array_to_null(array) - return setmetatable(array, cjson.array_mt) -end - -return CJSON \ No newline at end of file diff --git a/lualib/json/init.lua b/lualib/json/init.lua new file mode 100644 index 00000000..51a77264 --- /dev/null +++ b/lualib/json/init.lua @@ -0,0 +1,51 @@ +local cjson = require "cjson" + +local setmetatable = setmetatable + +local cjson_array_mt = cjson.array_mt + +-- this lib fork from resty cjson, only modified some compatible codes +-- more details please check it. + +cjson.decode_array_with_array_mt(true) + +-- 默认允许稀疏数组 +cjson.encode_sparse_array(true) + +local json = { + null = null, + _VERSION = cjson._VERSION, + array_mt = cjson_array_mt, + empty_array = cjson.empty_array, + empty_array_mt = cjson.empty_array_mt, + encode_max_depth = cjson.encode_max_depth, + encode_sparse_array = cjson.encode_sparse_array, + decode_max_depth = cjson.decode_max_depth, + decode_array_with_array_mt = cjson.decode_array_with_array_mt, + encode_empty_table_as_object = cjson.encode_empty_table_as_object, +} + +cjson = require "cjson.safe" +local cjson_encode = cjson.encode +local cjson_decode = cjson.decode + +-- 设置稀疏数组用null填充 +function json.sparse_array_to_null(array) + return setmetatable(array, cjson_array_mt) +end + +---comment json序列化 +---@param tab table @可序列化的`lua table` +---@return string @合法的`json`字符串 +function json.encode (tab) + return cjson_encode(tab) +end + +---comment json反序列化 +---@param json string @合法的json字符串 +---@return table @`lua table` +function json.decode (json) + return cjson_decode(json) +end + +return json diff --git a/lualib/json/jsonp.lua b/lualib/json/jsonp.lua new file mode 100644 index 00000000..2131dff7 --- /dev/null +++ b/lualib/json/jsonp.lua @@ -0,0 +1,23 @@ +local json = require "json" +local json_encode = json.encode +local json_decode = json.decode + +local sub = string.sub +local find = string.find +local concat = table.concat + +local jsonp = {} + +function jsonp.encode(callback_name, tab) + return concat {callback_name, "(", json_encode(tab), ")"} +end + +function jsonp.decode(str) + local s, e = find(str, "%("), find(str, "%)", -1) + if not s or not e or e <= s then + return + end + return json_decode(sub(str, s + 1, e - 1)) +end + +return jsonp \ No newline at end of file diff --git a/lualib/json/jwt.lua b/lualib/json/jwt.lua new file mode 100644 index 00000000..e89ab226 --- /dev/null +++ b/lualib/json/jwt.lua @@ -0,0 +1,82 @@ +local json = require "json" +local json_encode = json.encode + +local crypt = require "crypt" +local hashkey = crypt.hashkey +local desencode = crypt.desencode +local desdecode = crypt.desdecode +local hmac_sha256 = crypt.hmac_sha256 +local hmac_sha384 = crypt.hmac_sha384 +local hmac_sha512 = crypt.hmac_sha512 +local base64urlencode = crypt.base64urlencode +local base64urldecode = crypt.base64urldecode + +local type = type +local pcall = pcall +local assert = assert + +local match = string.match + +local concat = table.concat + +-- 支持的签名算法 +local algorithms = { HS256 = hmac_sha256, HS384 = hmac_sha384, HS512 = hmac_sha512 } + +-- 签名算法 +local function sign(secret, text, algorithm) + return (algorithms[algorithm] or hmac_sha256)(secret, text) +end + +---comment 使用`encode`编码, 使用`decode`解码 +local jwt = { version = 0.1, algorithms = { HS256 = "HS256", HS384 = "HS384", HS512 = "HS512" } } + +---comment jwt 序列化 +---@param text string @字符串类型的数据载荷 +---@param secret string @HASH摘要签名与对称加密使用的秘钥 +---@param algorithm string @指定签名算法: [`HS256`, `HS384`, `HS512`] +---@return string | nil @符合规范的json web token字符串或者nil +---@return string? @出错后的错误信息 +function jwt.encode(text, secret, algorithm) + assert(type(text) == 'string' and text ~= '' and type(secret) == 'string' and secret ~= '', "Invalid json web token encode parameter.") + -- HEADER + local ok, header = pcall(json_encode, { alg = jwt.algorithms[algorithm] or "HS256", typ = "JWT" }) + if not ok then + return nil, header + end + header = base64urlencode(header) + -- PAYLOAD + local payload = base64urlencode(desencode(hashkey(secret), text)) + -- SIGNATURE + local signature = base64urlencode(sign(hashkey(secret), concat({header, payload}, "."), algorithm)) + return concat({header, payload, signature}, ".") +end + +---comment jwt 反序列化 +---@param token string @待序列化的合法json web token字符串 +---@param secret string @摘要签名与对称解密使用的秘钥 +---@param algorithm string @指定签名算法: [`HS256`, `HS384`, `HS512`] +---@return string | nil @编码前的数据载荷或nil +---@return string? @出错后的错误信息 +function jwt.decode(token, secret, algorithm) + assert(type(token) == 'string' and token ~= '' and type(secret) == 'string' and secret ~= '', "Invalid json web token decode parameter.") + local header, payload, signature = match(token or "", "([^.]+)%.([^.]+)%.([^.]+)") + if not header or not payload or not signature then + return nil, "Invalid json web token format." + end + if base64urlencode(sign(hashkey(secret), concat({header, payload}, "."), algorithm)) ~= signature then + return nil, "Invalid json web token signature." + end + -- base64解码 + local debase_ok, info = pcall(base64urldecode, payload) + if not debase_ok then + return nil, info + end + -- des对称解密 + local decode_ok, rawpayload = pcall(desdecode, hashkey(secret), info) + if not decode_ok then + return nil, rawpayload + end + return rawpayload +end + +return jwt \ No newline at end of file diff --git a/lualib/log/init.lua b/lualib/log/init.lua deleted file mode 100644 index 3affa571..00000000 --- a/lualib/log/init.lua +++ /dev/null @@ -1,142 +0,0 @@ ---[[ --- log.lua --- --- Copyright (c) 2016 rxi --- --- This library is free software; you can redistribute it and/or modify it --- under the terms of the MIT license. See LICENSE for details. --- Modefy by CandyMi In 2018.12.18 - -log的内部方法包括: -log.trace(...) 紫色 -log.debug(...) 天蓝色 -log.info(...) 绿色 -log.warn(...) 黄色 -log.error(...) 红色 -log.fatal(...) 粉色 - -log.usecolor -默认情况下: 这个为true! 如果你的终端不支持ANSI颜色转义码, 请将它设置为false或者nil. - -log.outfile -将log输出到outfile字符串指定的文件(路径). - -log.level -请参考使用方法相关method - ---]] - -local debug_getinfo = debug.getinfo - -local tostring = tostring - -local select = select - -local ipairs = ipairs - -local type = type - -local concat = table.concat - -local date = os.date - -local open = io.open - -local ceil = math.ceil - -local floor = math.floor - -local toint = math.tointeger - -local fmt = string.format - - -local log = { _version = "0.1.0" } - -log.usecolor = true -log.outfile = nil -log.level = "trace" - - -local modes = { - { name = "trace", color = "\27[34m", }, - { name = "debug", color = "\27[36m", }, - { name = "info", color = "\27[32m", }, - { name = "warn", color = "\27[33m", }, - { name = "error", color = "\27[31m", }, - { name = "fatal", color = "\27[35m", }, -} - - -local levels = {} -for i, v in ipairs(modes) do - levels[v.name] = i -end - - -local round = function(x, increment) - if not toint(x) then - increment = increment or 1 - x = x / increment - return (x > 0 and floor(x + .5) or ceil(x - .5)) * increment - end - return x -end - - -local _tostring = tostring - -local tostring = function(...) - local t = {} - for i = 1, select('#', ...) do - local x = select(i, ...) - if type(x) == "number" then - x = round(x, .01) - end - t[#t + 1] = _tostring(x) - end - return concat(t, " ") -end - - -for i, x in ipairs(modes) do - local nameupper = x.name:upper() - log[x.name] = function(...) - - -- Return early if we're below the log level - if i < levels[log.level] then - return - end - - local msg = tostring(...) - local info = debug_getinfo(2, "Sl") - local lineinfo = "[C]:[-1]" - if info then - lineinfo = info.short_src .. ":" .. info.currentline - end - - -- Output to console - print(fmt("%s[%s][%s]%s %s: %s", - log.usecolor and x.color or "", - nameupper, - date("%Y/%m/%d %H:%M:%S"), - log.usecolor and "\27[0m" or "", - lineinfo, - msg)) - - -- Output to log file - if log.outfile then - local fp = open(log.outfile, "a") - if not fp then - return log.warn("Cant't write info to "..(log.outfile or "")) - end - fp:write(fmt("[%s][%s] %s: %s\n", nameupper, date("%Y/%m/%d %H:%M:%S"), lineinfo, msg)) - fp:close() - end - - end - -end - - -return log diff --git a/lualib/logging/init.lua b/lualib/logging/init.lua new file mode 100644 index 00000000..8acf8bd0 --- /dev/null +++ b/lualib/logging/init.lua @@ -0,0 +1,226 @@ +-- logging 核心配置 +local cf = require "cf" +local cf_at = cf.at + +local class = require "class" + +local sys = require "sys" +local now = sys.now +local new_tab = sys.new_tab + +local os_date = os.date + +local type = type +local select = select +local assert = assert +local pairs = pairs +local tostring = tostring + +local modf = math.modf +local toint = math.tointeger +local debug_getinfo = debug.getinfo +local io_open = io.open +local io_write = io.write +local io_type = io.type +local fmt = string.format +local concat = table.concat + +-- 可以在这里手动设置是否使用异步日志 +local ASYNC = true +-- 这里可以设置异步所使用的buffer. +local ASYNC_BUFFER_SIZE = 1 << 20 + +if ASYNC and io_type(io.output()) == 'file' then + local output = io.output() + output:setvbuf("full", ASYNC_BUFFER_SIZE) + local at = cf_at(0.5, function () + output:flush() + end) +end + +-- 格式化时间: [年-月-日 时:分:秒,毫秒] +local function fmt_Y_m_d_H_M_S() + local ts, f = modf(now()) + return concat({'[', os_date('%Y-%m-%d %H:%M:%S', ts), ',', fmt("%003d", modf(f * 1e3)), ']'}) +end + +-- 格式化时间: [年-月-日 时:分:秒] +local function Y_m_d() + return os_date('%Y-%m-%d') +end + +-- LOG函数的调用信息 +local function debuginfo () + local info = debug_getinfo(3, 'Sln') or debug_getinfo(2, 'Sln') or debug_getinfo(1, 'Sln') + return concat({'[', info.source, ':', info.currentline, ']'}) +end + +-- 格式化 +local function table_format(tab) + local list = {} + for key, value in pairs(tab) do + local k, v + if type(key) == 'number' then + k = concat({'[', key, ']'}) + elseif type(key) == 'string' then + k = concat({'["', key, '"]'}) + else + k = concat({'[', tostring(key), ']'}) + end + if type(value) == 'table' then + if key ~= '__index' then + v = table_format(value) + end + elseif type(value) == 'string' then + v = concat({'"', value, '"'}) + elseif value then + v = tostring(value) + end + if k and v then + list[#list+1] = concat({k, '=', v}) + end + end + return concat({tab.__name or "", '{', concat(list, ', '), '}'}) +end + +local function info_fmt(...) + local args = {...} + local index, len = 1, select('#', ...) + local tab = new_tab(16, 0) + while 1 do + local arg = args[index] + if type(arg) == 'table' then + tab[#tab+1] = table_format(arg) + else + if type(arg) == 'string' then + tab[#tab+1]= '"' .. tostring(arg) .. '"' + else + tab[#tab+1]= tostring(arg) + end + end + if index >= len then + break + end + index = index + 1 + end + return concat(tab, ', ') +end + +-- 格式化日志 +local function FMT (where, level, ...) + return concat({ fmt_Y_m_d_H_M_S(), where, level, ':', info_fmt(...), '\n'}, ' ') +end + +local Log = class("Log") + +function Log:ctor (opt) + if type(opt) == 'table' then + self.counter = 0 + self.sync = opt.sync + self.dumped = opt.dump + self.path = opt.path + self.today = Y_m_d() + self.buffer_size = toint(opt.buffer_size) + end +end + +-- 常规日志 +function Log:INFO (...) + local info = debuginfo() + io_write(FMT("\27[32m"..info, "[INFO]".."\27[0m", ...)) + if not self.dumped or type(self.path) ~= 'string' then + return + end + self:dump(FMT(info, "[INFO]", ...)) +end + +-- 错误日志 +function Log:ERROR (...) + local info = debuginfo() + io_write(FMT("\27[31m"..info, "[ERROR]".."\27[0m", ...)) + if not self.dumped or type(self.path) ~= 'string' then + return + end + self:dump(FMT(info, "[ERROR]", ...)) +end + +-- 调试日志 +function Log:DEBUG (...) + local info = debuginfo() + io_write(FMT("\27[36m"..info, "[DEBUG]".."\27[0m", ...)) + if not self.dumped or type(self.path) ~= 'string' then + return + end + self:dump(FMT(info, "[DEBUG]", ...)) +end + +-- 警告日志 +function Log:WARN (...) + local info = debuginfo() + io_write(FMT("\27[33m"..info, "[WARN]".."\27[0m", ...)) + if not self.dumped or type(self.path) ~= 'string' then + return + end + self:dump(FMT(info, "[WARN]", ...)) +end + +-- 可以在这里手动设置日志路径 +local LOG_FOLDER = 'logs/' + +-- 异步写入(主线程负责写缓存, 刷写磁盘工作交由工作线程) +local function async_write(self, log) + if not self.timer then + self.timer = cf_at(0.5, function ( ) + if self.oldfile then + self.oldfile:close() + self.oldfile = nil + end + --[[ + 开始根据counter来决定是否刷写磁盘, 如果counter数量大于0的时候说明需要; + 这可以减少空闲时间的无效操作, 也可以减少一些特殊情况下的性能损耗问题; + ]] + if self.counter % (self.buffer_size or ASYNC_BUFFER_SIZE) == 0 then + self.counter = 0 + return + end + if self.file then + self.counter = 0 + self.file:flush() + end + end) + end + self.counter = self.counter + #log + return self.file:write(log) +end + +-- 同步写入(直接刷写到磁盘) +local function sync_write(self, log) + return self.file:write(log) +end + +-- dump日志到磁盘 +function Log:dump(log) + local today = Y_m_d() + if today ~= self.today then + self.today = today + if self.file then + self.oldfile = self.file + self.file = nil + end + end + if not self.file then + self.file = assert(io_open(LOG_FOLDER..self.path..'_'..self.today..'.log', 'a+')) + if not self.sync and ASYNC then + self.file:setvbuf("full", self.buffer_size or ASYNC_BUFFER_SIZE) + end + end + --[[ + `全局配置`和`指定配置`都将使用`同步`的方式刷写日志 + ]] + if self.sync or not ASYNC then + return sync_write(self, log) + end + return async_write(self, log) +end + +return Log \ No newline at end of file diff --git a/lualib/mail/init.lua b/lualib/mail/init.lua index 1f1d9807..6b167dea 100644 --- a/lualib/mail/init.lua +++ b/lualib/mail/init.lua @@ -1,30 +1,7 @@ -local crypt = require "crypt" -local base64encode = crypt.base64encode +local smtp = require "protocol.smtp" -local socket = require "mail.socket" -local connect = socket.connect -local close = socket.close -local send = socket.send -local recv = socket.recv - -local tonumber = tonumber -local tostring = tostring +local toint = math.tointeger local match = string.match -local fmt = string.format -local os_date = os.date - -local MAX_PACKET_SIZE = 1024 - -local mail = {} - -local function read_packet(str) - local str_code, err = match(str, "(%d+) (.+)\r\n") - local code = tonumber(str_code) - if not code then - return - end - return code, err -end local function check_mail(mail) if match(mail, '.+@.+') then @@ -33,180 +10,52 @@ local function check_mail(mail) return false end -local function time() - return os_date("[%Y/%m/%d %H:%M:%S]") -end - --- HELO 命令 -local function HELO_PACKET(session, SSL) - local code, data, err - data, err = recv(session, MAX_PACKET_SIZE, SSL) - if not data then - return nil, err - end - code, err = read_packet(data) - if not code then - return nil, time().."[HELO ERROR]: 不支持的协议." - end - -- 发送HELO命令 - send(session, "HELO CoreFramework(lua)/0.1\r\n", SSL) - -- 发送HELO命令 - data, err = recv(session, MAX_PACKET_SIZE, SSL) - if not data then - return nil, err - end - code, err = read_packet(data) - if code ~= 250 and code ~= 220 then - return nil, time()..'[HELO ERROR]: ' .. tostring(err) or '服务器关闭了连接.' - end - return true -end - --- 验证登录 -local function AUTH_PACKET(session, username, password, SSL) - local code, data, err - send(session, "AUTH LOGIN\r\n", SSL) - data, err = recv(session, MAX_PACKET_SIZE, SSL) - if not data then - return nil, time()..'[AUTH LOGIN ERROR]: 1.' .. tostring(err) or '服务器关闭了连接. ' - end - code, err = read_packet(data) - if not code or code ~= 334 then - return nil, time()..'[AUTH LOGIN ERROR]: 1. 验证失败('.. tostring(code) .. (err or '未知错误') ..')' - end - -- 发送base64用户名 - send(session, base64encode(username)..'\r\n', SSL) - data, err = recv(session, MAX_PACKET_SIZE, SSL) - if not data then - return nil, time()..'[AUTH LOGIN ERROR]: 2.' .. tostring(err) or '服务器关闭了连接.' - end - code, err = read_packet(data) - if not code or code ~= 334 then - return nil, time()..'[AUTH LOGIN ERROR]: 2. 验证失败('.. tostring(code) .. (err or '未知错误') ..')' - end - -- 发送base64密码 - send(session, base64encode(password)..'\r\n', SSL) - data, err = recv(session, MAX_PACKET_SIZE, SSL) - if not data then - return nil, time()..'[AUTH LOGIN ERROR]: 3.' .. tostring(err) or '服务器关闭了连接.' - end - code, err = read_packet(data) - if not code or code ~= 235 then - return nil, time()..'[AUTH LOGIN ERROR]: 3. 验证失败('.. tostring(code) .. (err or '未知错误') ..')' - end - return code, err -end - --- 发送邮件头部 -local function MAIL_HEADER(session, from, to, SSL) - local code, data, err - -- 邮件发送者 - send(session, fmt("MAIL FROM:<%s>\r\n", from), SSL) - data, err = recv(session, MAX_PACKET_SIZE, SSL) - if not data then - return nil, time()..'[MAIL FROM ERROR]: ' .. tostring(err) or '服务器关闭了连接. ' - end - code, err = read_packet(data) - if not code or code ~= 250 then - return nil, time()..'[MAIL FROM ERROR]: ('.. tostring(code) .. (err or '未知错误') ..')' - end - -- 邮件接收者 - send(session, fmt("RCPT TO:<%s>\r\n", to), SSL) - data, err = recv(session, MAX_PACKET_SIZE, SSL) - if not data then - return nil, time()..'[RCPT TO ERROR]: ' .. tostring(err) or '服务器关闭了连接. ' - end - code, err = read_packet(data) - if not code or code ~= 250 then - return nil, time()..'[RCPT TO ERROR]: ('.. tostring(code) .. (err or '未知错误') ..')' - end - return true -end - --- 发送邮件内容 -local function MAIL_CONTENT(session, from, to, subject, mime, content, SSL) - local code, data, err - -- DATA命令, 开始发送邮件实体 - send(session, "DATA\r\n", SSL) - data, err = recv(session, MAX_PACKET_SIZE, SSL) - if not data then - return nil, time()..'[MAIL CONTENT ERROR]: ' .. tostring(err) or '服务器关闭了连接. ' - end - code, err = read_packet(data) - if not code or code ~= 354 then - return nil, time()..'[MAIL CONTENT ERROR]: ('.. tostring(code) .. (err or '未知错误') ..')' - end - local FROM = fmt("from:<%s>\r\n", from) - local TO = fmt("to:<%s>\r\n", to) - local SUBJECT = fmt("subject:%s\r\n", subject) - if mime and mime == 'html' then - mime = 'Content-Type: text/html\r\n' - else - mime = '' - end - send(session, FROM..TO..SUBJECT..mime..'\r\n'..content..'\r\n\r\n.\r\n', SSL) - data, err = recv(session, MAX_PACKET_SIZE, SSL) - if not data then - return nil, time()..'[MAIL CONTENT ERROR]: ' .. tostring(err) or '服务器关闭了连接. ' - end - code, err = read_packet(data) - if not code or code ~= 250 then - return nil, time()..'[MAIL CONTENT ERROR]: ('.. tostring(code) .. (err or '未知错误') ..')' - end - return true -end +local mail = {} function mail.send(opt) - local ok, session, err if not opt.username or not opt.password or opt.username == '' or opt.password == '' then - return nil, "用户名或密码不能为空" - end - if not opt.from or not opt.to or opt.from == '' or opt.to == '' then - return nil, "邮箱发送者与接受者不能空" + return nil, "Username or password cannot be empty." end - if not check_mail(opt.from) or not check_mail(opt.to) then - return nil, "发送者与接受者邮箱格式不正确" + if not opt.from or not opt.to or opt.from == '' or opt.to == '' or not check_mail(opt.from) or not check_mail(opt.to) then + return nil, "The sender or receiver of the email cannot be empty." end - if not opt.host or not opt.port or opt.host == '' or (tonumber(opt.port) <= 0 or tonumber(opt.port) > 65535) then - return nil, "邮件server配置错误, 请检查配置参数." + if not opt.host or not opt.port or opt.host == '' or (not toint(opt.port)) or (toint(opt.port) <= 0 or toint(opt.port) > 65535) then + return nil, "Invalid target mail server configuration." end if not opt.subject or opt.subject == '' then - return nil, "邮件主题为空, 请检查配置参数." + return nil, "The email subject is empty, please check the configuration parameters." end if not opt.content or opt.content == '' then - return nil, "邮件内容为空, 请检查配置参数" + return nil, "The email content is empty, please check the configuration parameters." end - -- 连接邮件服务器并且返回tcp session - session, err = connect(opt.host, opt.port, opt.SSL) - if not session then - return nil, err - end - -- HELO 命令 - ok, err = HELO_PACKET(session, opt.SSL) + local s = smtp:new(opt):set_timeout(15) + -- 开始发送邮件 + local ok, err + -- 尝试连接服务器 + ok, err = s:connect() if not ok then - close(session) - return nil, err + return nil, err, s:close() end - -- AUTH LOGIN 命令 - ok, err = AUTH_PACKET(session, opt.username, opt.password, opt.SSL) + -- print("连接成功") + -- 尝试握手包 + ok, err = s:hello_packet() if not ok then - close(session) - return nil, err + return nil, err, s:close() end - -- HEADER 头部 命令 1 - ok, err = MAIL_HEADER(session, opt.from, opt.to, opt.SSL) + -- print("握手成功") + -- 身份认证 + ok, err = s:auth_packet() if not ok then - close(session) - return nil, err + return nil, err, s:close() end - -- HEADER 头部 命令 1 + Content-Type + Content Body内容 - ok, err = MAIL_CONTENT(session, opt.from, opt.to, opt.subject, opt.mime, opt.content, opt.SSL) + -- print("认证成功") + -- 发送数据 + ok, err = s:send_mail() + s:close() if not ok then - close(session) return nil, err end - close(session) - return ok, time()..": 邮件发送成功!" + return ok end diff --git a/lualib/mail/socket.lua b/lualib/mail/socket.lua deleted file mode 100644 index 32b766ff..00000000 --- a/lualib/mail/socket.lua +++ /dev/null @@ -1,44 +0,0 @@ -local TCP = require "internal.TCP" - -local socket = {} --- hook connect 与 ssl connect -function socket.connect(host, port, SSL) - local session = TCP:new() - if not SSL then - local ok, err = session:connect(host, port) - if not ok then - session:close() - return ok, err - end - else - local ok, err = session:ssl_connect(host, port) - if not ok then - session:close() - return ok, err - end - end - return session -end - --- hook read 与 ssl read -function socket.recv(session, bytes, SSL) - if not SSL then - return session:recv(bytes) - end - return session:ssl_recv(bytes) -end - --- hook send 与 ssl send -function socket.send(session, buf, SSL) - if not SSL then - return session:send(buf) - end - return session:ssl_send(buf) -end - --- hook close session -function socket.close(session) - return session:close() -end - -return socket diff --git a/lualib/msgpack.lua b/lualib/msgpack.lua new file mode 100644 index 00000000..1cccfeb4 --- /dev/null +++ b/lualib/msgpack.lua @@ -0,0 +1,28 @@ +local lmsgpack = require "lmsgpack.safe" +local lmsgpack_encode = lmsgpack.pack +local lmsgpack_decode = lmsgpack.unpack + +local msgpack = {} + +-- 序列化 +function msgpack.encode (...) + return lmsgpack_encode(...) +end + +-- 反序列化 +function msgpack.decode (...) + return lmsgpack_decode(...) +end + +-- 序列化 +function msgpack.pack (...) + return lmsgpack_encode(...) +end + +-- 反序列化 +function msgpack.unpack (...) + return lmsgpack_decode(...) +end + + +return msgpack diff --git a/lualib/process/channel.lua b/lualib/process/channel.lua new file mode 100644 index 00000000..6a7a76cc --- /dev/null +++ b/lualib/process/channel.lua @@ -0,0 +1,142 @@ +local TCP = require "internal.TCP" +local sock_recv = TCP.recv +local sock_send = TCP.send +local sock_close = TCP.close + +local dataset = require "process.dataset" + +local session = require "process.session" +local session_wakeup = session.wakeup + +local lpack = require "pack" +local lpack_encode = lpack.encode +local lpack_decode = lpack.decode + +local cf = require "cf" +local cf_fork = cf.fork +local cf_wait = cf.wait +local cf_wakeup = cf.wakeup + +local assert = assert +local strunpack = string.unpack + +local MAX_BUFFER_SIZE = 4194304 + +local class = require "class" + +local Channel = class("Channel") + +function Channel:ctor() + self.id = dataset.get('pid') +end + +function Channel:send(data) + if not self.writer then + self.queue = {} + self.writer = cf_fork(function () + local sock = self.sock + local qlen, queue = #self.queue, self.queue + while true do + self.queue = {} + for idx = 1, qlen do + sock_send(sock, queue[idx]) + end + queue = self.queue + qlen = #queue + if qlen == 0 then + self.waited = true + cf_wait() + qlen = #queue + end + end + end) + end + if self.waited then + self.waited = nil + cf_wakeup(self.writer) + end + self.queue[#self.queue + 1] = data +end + +function Channel:recv() + local sock = self.sock + local data = sock_recv(sock, 4) + if not data then + return sock_close(sock) + end + local len = strunpack("> 32) & 0xFFFFFFFF +end + +---comment 等待`sessionid` +---@param sessionid integer @会话`ID` +function session.wait(sessionid) + assert(mtype(sessionid) == 'integer', "Invalide `sessionid`") + session_map[sessionid] = cf_self() + return cf_wait() +end + +---comment 唤醒`sessionid` +---@param sessionid integer @会话`ID` +function session.wakeup(sessionid, ...) + local co = session_map[sessionid] + if co then + session_map[sessionid] = nil + cf_wakeup(co, ...) + return true + end + return false +end + +return session \ No newline at end of file diff --git a/lualib/process/utils.lua b/lualib/process/utils.lua new file mode 100644 index 00000000..11d5f9c7 --- /dev/null +++ b/lualib/process/utils.lua @@ -0,0 +1,11 @@ +local utils = {} + +function utils.copy(tab) + local t = {} + for k, v in pairs(tab) do + t[k] = v + end + return t +end + +return utils \ No newline at end of file diff --git a/lualib/process/worker.lua b/lualib/process/worker.lua new file mode 100644 index 00000000..c2e1be45 --- /dev/null +++ b/lualib/process/worker.lua @@ -0,0 +1,47 @@ +local dataset = require "process.dataset" + +local channel = require "process.channel" +local channel_send = channel.send + +local lpack = require "pack" +local lpack_encode = lpack.encode + +local session = require "process.session" +local session_getsid = session.getsid +local sessionid_wait = session.wait + +local process = { isWorker = true, pid = dataset.get('pid') } + +local mchan = nil + +---comment 由框架完成进程初始化. +function process.init() + -- 构建进程通信通道 + mchan = channel:new() + mchan:connect('worker') +end + +---comment 向`Master`进程发送消息(非阻塞) +function process.send(...) + channel_send(mchan, lpack_encode(nil, ...)) +end + +---comment 向`Master`进程发送消息(会阻塞) +function process.call(...) + local sessionid = session_getsid() + channel_send(mchan, lpack_encode(sessionid, ...)) + return sessionid_wait(sessionid) +end + +---comment 注册进程事件 +---@param msg_type string @事件类型: `message` +---@param func function @回调函数: function(sessionid, ...) +function process.on(msg_type, func) + -- 注册消息事件 + if msg_type == 'message' and type(func) == 'function' then + mchan:setcb(func) + return + end +end + +return process \ No newline at end of file diff --git a/lualib/protobuf/init.lua b/lualib/protobuf/init.lua new file mode 100644 index 00000000..772a77f8 --- /dev/null +++ b/lualib/protobuf/init.lua @@ -0,0 +1,50 @@ +local lprotobuf = require "lprotobuf" + + +local lprotobuf_tohex = lprotobuf.tohex + +local lprotobuf_clear = lprotobuf.clear + +local lprotobuf_load = lprotobuf.load +local lprotobuf_loadfile = lprotobuf.loadfile + +local lprotobuf_encode = lprotobuf.encode +local lprotobuf_decode = lprotobuf.decode + + +local pb = {} + +-- 转化为16进制可读字符串 +function pb.tohex (pb_string) + return lprotobuf_tohex(pb_string) +end + +-- 从字符串中读取 +function pb.load (pb_cp_string) + return lprotobuf_load(pb_cp_string) +end + +-- 从文件中读取 +function pb.loadfile (filename) + return lprotobuf_loadfile(filename) +end + +-- 序列化 +function pb.encode (pb_registey, table) + return lprotobuf_encode(pb_registey, table) +end + +-- 反序列化 +function pb.decode (pb_registey, pb_string) + return lprotobuf_decode(pb_registey, pb_string) +end + +-- 清理 +-- When you passed A not exists message struct will get Segmentation fault. +function pb.clear (...) + return lprotobuf_clear(...) +end + +-- require ("logging"):new():DEBUG(lprotobuf) + +return pb diff --git a/lualib/protobuf/protoc.lua b/lualib/protobuf/protoc.lua new file mode 100644 index 00000000..c3e0b547 --- /dev/null +++ b/lualib/protobuf/protoc.lua @@ -0,0 +1,1117 @@ +local string = string +local tonumber = tonumber +local setmetatable = setmetatable +local error = error +local ipairs = ipairs +local io = io +local table = table +local math = math +local assert = assert +local tostring = tostring +local type = type +local insert_tab = table.insert + +local function meta(name, t) + t = t or {} + t.__name = name + t.__index = t + return t +end + +local function default(t, k, def) + local v = t[k] + if not v then + v = def or {} + t[k] = v + end + return v +end + +local Lexer = meta "Lexer" do + +local escape = { + a = "\a", b = "\b", f = "\f", n = "\n", + r = "\r", t = "\t", v = "\v" +} + +local function tohex(x) return string.byte(tonumber(x, 16)) end +local function todec(x) return string.byte(tonumber(x, 10)) end +local function toesc(x) return escape[x] or x end + +function Lexer.new(name, src) + local self = { + name = name, + src = src, + pos = 1 + } + return setmetatable(self, Lexer) +end + +function Lexer:__call(patt, pos) + return self.src:match(patt, pos or self.pos) +end + +function Lexer:test(patt) + self:whitespace() + local pos = self('^'..patt..'%s*()') + if not pos then return false end + self.pos = pos + return true +end + +function Lexer:expected(patt, name) + if not self:test(patt) then + return self:error((name or ("'"..patt.."'")).." expected") + end + return self +end + +function Lexer:pos2loc(pos) + local linenr = 1 + pos = pos or self.pos + for start, stop in self.src:gmatch "()[^\n]*()\n?" do + if start <= pos and pos <= stop then + return linenr, pos - start + 1 + end + linenr = linenr + 1 + end +end + +function Lexer:error(fmt, ...) + local ln, co = self:pos2loc() + return error(("%s:%d:%d: "..fmt):format(self.name, ln, co, ...)) +end + +function Lexer:opterror(opt, msg) + if not opt then return self:error(msg) end + return nil +end + +function Lexer:whitespace() + local pos, c = self "^%s*()(%/?)" + self.pos = pos + if c == '' then return self end + return self:comment() +end + +function Lexer:comment() + local pos = self "^%/%/[^\n]*\n?()" + if not pos then + if self "^%/%*" then + pos = self "^%/%*.-%*%/()" + if not pos then + self:error "unfinished comment" + end + end + end + if not pos then return self end + self.pos = pos + return self:whitespace() +end + +function Lexer:line_end(opt) + self:whitespace() + local pos = self '^[%s;]*%s*()' + if not pos then + return self:opterror(opt, "';' expected") + end + self.pos = pos + return pos +end + +function Lexer:eof() + self:whitespace() + return self.pos > #self.src +end + +function Lexer:keyword(kw, opt) + self:whitespace() + local ident, pos = self "^([%a_][%w_]*)%s*()" + if not ident or ident ~= kw then + return self:opterror(opt, "''"..kw..'" expected') + end + self.pos = pos + return kw +end + +function Lexer:ident(name, opt) + self:whitespace() + local b, ident, pos = self "^()([%a_][%w_]*)%s*()" + if not ident then + return self:opterror(opt, (name or 'name')..' expected') + end + self.pos = pos + return ident, b +end + +function Lexer:full_ident(name, opt) + self:whitespace() + local b, ident, pos = self "^()([%a_][%w_.]*)%s*()" + if not ident or ident:match "%.%.+" then + return self:opterror(opt, (name or 'name')..' expected') + end + self.pos = pos + return ident, b +end + +function Lexer:integer(opt) + self:whitespace() + local ns, oct, hex, s, pos = + self "^([+-]?)(0?)([xX]?)([0-9a-fA-F]+)%s*()" + local n + if oct == '0' and hex == '' then + n = tonumber(s, 8) + elseif oct == '' and hex == '' then + n = tonumber(s, 10) + elseif oct == '0' and hex ~= '' then + n = tonumber(s, 16) + end + if not n then + return self:opterror(opt, 'integer expected') + end + self.pos = pos + return ns == '-' and -n or n +end + +function Lexer:number(opt) + self:whitespace() + if self:test "nan%f[%A]" then + return 0.0/0.0 + elseif self:test "inf%f[%A]" then + return 1.0/0.0 + end + local ns, d1, s, d2, s2, pos = self "^([+-]?)(%.?)([0-9]+)(%.?)([0-9]*)()" + if not ns then + return self:opterror(opt, 'floating-point number expected') + end + local es, pos2 = self("(^[eE][+-]?[0-9]+)%s*()", pos) + if d1 == "." and d2 == "." then + return self:error "malformed floating-point number" + end + self.pos = pos2 or pos + local n = tonumber(d1..s..d2..s2..(es or "")) + return ns == '-' and -n or n +end + +function Lexer:quote(opt) + self:whitespace() + local q, start = self '^(["\'])()' + if not start then + return self:opterror(opt, 'string expected') + end + self.pos = start + local patt = '()(\\?'..q..')%s*()' + while true do + local stop, s, pos = self(patt) + if not stop then + self.pos = start-1 + return self:error "unfinished string" + end + self.pos = pos + if s == q then + return self.src:sub(start, stop-1) + :gsub("\\x(%x+)", tohex) + :gsub("\\(%d+)", todec) + :gsub("\\(.)", toesc) + end + end +end + +function Lexer:structure(opt) + self:whitespace() + if not self:test "{" then + return self:opterror(opt, 'opening curly brace expected') + end + local t = {} + while not self:test "}" do + local ident = self:full_ident "field name" -- TODO: full_ident? + self:test ":" + local value = self:constant() + self:test "," + self:line_end "opt" + ---@cast ident string + t[ident] = value + end + return t +end + +function Lexer:array(opt) + self:whitespace() + if not self:test "%[" then + return self:opterror(opt, 'opening square bracket expected') + end + local t = {} + while not self:test "]" do + local value = self:constant() + self:test "," + t[#t + 1] = value + end + return t +end + +function Lexer:constant(opt) + local c = self:full_ident('constant', 'opt') or + self:number('opt') or + self:quote('opt') or + self:structure('opt') or + self:array('opt') + if not c and not opt then + return self:error "constant expected" + end + return c +end + +function Lexer:option_name() + local ident + if self:test "%(" then + ident = self:full_ident "option name" + self:expected "%)" + else + ident = self:ident "option name" + end + while self:test "%." do + ident = ident .. "." .. self:ident() + end + return ident +end + +function Lexer:type_name() + if self:test "%." then + local id, pos = self:full_ident "type name" + return "."..id, pos + else + return self:full_ident "type name" + end +end + +end + +local Parser = meta "Parser" do +Parser.typemap = {} +Parser.loaded = {} +Parser.paths = { "", "." } + +function Parser.new() + local self = {} + self.typemap = {} + self.loaded = {} + self.paths = { "", "." } + return setmetatable(self, Parser) +end + +function Parser:error(msg) + return self.lex:error(msg) +end + +function Parser:addpath(path) + insert_tab(self.paths, path) +end + +function Parser:parsefile(name) + local info = self.loaded[name] + if info then return info end + local errors = {} + for _, path in ipairs(self.paths) do + local fn = path ~= "" and path.."/"..name or name + local fh, err = io.open(fn) + if fh then + local content = fh:read "*a" + info = self:parse(content, name) + fh:close() + return info + end + insert_tab(errors, err or fn..": ".."unknown error") + end + if self.import_fallback then + info = self.import_fallback(name) + end + if not info then + error("module load error: "..name.."\n\t"..table.concat(errors, "\n\t")) + end + return info +end + +-- parser + +local labels = { optional = 1; required = 2; repeated = 3 } + +local key_types = { + int32 = 5; int64 = 3; uint32 = 13; + uint64 = 4; sint32 = 17; sint64 = 18; + fixed32 = 7; fixed64 = 6; sfixed32 = 15; + sfixed64 = 16; bool = 8; string = 9; +} + +local com_types = { + group = 10; message = 11; enum = 14; +} + +local types = { + double = 1; float = 2; int32 = 5; + int64 = 3; uint32 = 13; uint64 = 4; + sint32 = 17; sint64 = 18; fixed32 = 7; + fixed64 = 6; sfixed32 = 15; sfixed64 = 16; + bool = 8; string = 9; bytes = 12; + group = 10; message = 11; enum = 14; +} + +local function register_type(self, lex, tname, typ) + if not tname:match "%."then + tname = self.prefix..tname + end + if self.typemap[tname] then + return lex:error("type %s already defined", tname) + end + self.typemap[tname] = typ +end + +local function type_info(lex, tname) + local tenum = types[tname] + if com_types[tname] then + return lex:error("invalid type name: "..tname) + elseif tenum then + tname = nil + end + return tenum, tname +end + +local function map_info(lex) + local keyt = lex:ident "key type" + if not key_types[keyt] then + return lex:error("invalid key type: "..keyt) + end + local valt = lex:expected "," :type_name() + local name = lex:expected ">" :ident() + local ident = name:gsub("^%a", string.upper) + :gsub("_(%a)", string.upper).."Entry" + local kt, ktn = type_info(lex, keyt) + local vt, vtn = type_info(lex, valt) + return name, types.message, ident, { + name = ident, + field = { + { + name = "key", + number = 1; + label = labels.optional, + type = kt, + type_name = ktn + }, + { + name = "value", + number = 2; + label = labels.optional, + type = vt, + type_name = vtn + }, + }, + options = { map_entry = true } + } +end + +local function inline_option(lex, info) + if lex:test "%[" then + info = info or {} + while true do + local name = lex:option_name() + local value = lex:expected '=' :constant() + info[name] = value + if lex:test "%]" then + return info + end + lex:expected ',' + end + end +end + +local function field(self, lex, ident) + local name, typ, type_name, map_entry + if ident == "map" and lex:test "%<" then + name, typ, type_name, map_entry = map_info(lex) + self.locmap[map_entry.field[1]] = lex.pos + self.locmap[map_entry.field[2]] = lex.pos + register_type(self, lex, type_name, types.message) + else + typ, type_name = type_info(lex, ident) + name = lex:ident() + end + local info = { + name = name, + number = lex:expected "=":integer(), + label = ident == "map" and labels.repeated or labels.optional, + type = typ, + type_name = type_name + } + local options = inline_option(lex) + if options then + info.default_value, options.default = tostring(options.default), nil + info.json_name, options.json_name = options.json_name, nil + if options.packed and options.packed == "false" then + options.packed = false + end + end + info.options = options + if info.number <= 0 then + lex:error("invalid tag number: "..info.number) + end + return info, map_entry +end + +local function label_field(self, lex, ident) + local label = labels[ident] + local info, map_entry + if not label then + if self.syntax == "proto2" and ident ~= "map" then + return lex:error("proto2 disallow missing label") + end + return field(self, lex, ident) + end + if label == labels.optional and self.syntax == "proto3" then + return lex:error("proto3 disallow 'optional' label") + end + info, map_entry = field(self, lex, lex:type_name()) + info.label = label + return info, map_entry +end + +local toplevel = {} do + +function toplevel:package(lex, info) + local package = lex:full_ident 'package name' + lex:line_end() + info.package = package + self.prefix = "."..package.."." + return self +end + +function toplevel:import(lex, info) + local mode = lex:ident('"weak" or "public"', 'opt') or "public" + if mode ~= 'weak' and mode ~= 'public' then + return lex:error '"weak or "public" expected' + end + local name = lex:quote() + lex:line_end() + local result = self:parsefile(name) + if self.on_import then + self.on_import(result) + end + local dep = default(info, 'dependency') + local index = #dep + dep[index+1] = name + if mode == "public" then + local it = default(info, 'public_dependency') + insert_tab(it, index) + else + local it = default(info, 'weak_dependency') + insert_tab(it, index) + end +end + +local msg_body = {} do + +function msg_body:message(lex, info) + local nested_type = default(info, 'nested_type') + insert_tab(nested_type, toplevel.message(self, lex)) + return self +end + +function msg_body:enum(lex, info) + local nested_type = default(info, 'enum_type') + insert_tab(nested_type, toplevel.enum(self, lex)) + return self +end + +function msg_body:extend(lex, info) + local extension = default(info, 'extension') + local nested_type = default(info, 'nested_type') + local ft, mt = toplevel.extend(self, lex, {}) + for _, v in ipairs(ft) do + insert_tab(extension, v) + end + for _, v in ipairs(mt) do + insert_tab(nested_type, v) + end + return self +end + +function msg_body:extensions(lex, info) + local rt = default(info, 'extension_range') + repeat + local start = lex:integer "field number range" + local stop = math.floor(2^29) + lex:keyword 'to' + if not lex:keyword('max', 'opt') then + stop = lex:integer "field number range end or 'max'" + end + insert_tab(rt, { start = start, ['end'] = stop }) + until not lex:test ',' + lex:line_end() + return self +end + +function msg_body:reserved(lex, info) + lex:whitespace() + if not lex '^%d' then + local rt = default(info, 'reserved_name') + repeat + insert_tab(rt, (lex:quote())) + until not lex:test ',' + else + local rt = default(info, 'reserved_range') + local first = true + repeat + local start = lex:integer(first and 'field name or number range' + or 'field number range') + if lex:keyword('to', 'opt') then + local stop = lex:integer 'field number range end' + insert_tab(rt, { start = start, ['end'] = stop }) + else + insert_tab(rt, { start = start, ['end'] = start }) + end + first = false + until not lex:test ',' + end + lex:line_end() + return self +end + +function msg_body:oneof(lex, info) + local fs = default(info, "field") + local ts = default(info, "nested_type") + local ot = default(info, "oneof_decl") + local index = #ot + 1 + local oneof = { name = lex:ident() } + lex:expected "{" + while not lex:test "}" do + local ident = lex:type_name() + if ident == "option" then + toplevel.option(self, lex, oneof) + else + local f, t = field(self, lex, ident) + self.locmap[f] = lex.pos + if t then insert_tab(ts, t) end + f.oneof_index = index - 1 + insert_tab(fs, f) + end + lex:line_end 'opt' + end + ot[index] = oneof +end + +function msg_body:option(lex, info) + toplevel.option(self, lex, default(info, 'options')) +end + +end + +function toplevel:message(lex, info) + local name = lex:ident 'message name' + local typ = { name = name } + register_type(self, lex, name, types.message) + local prefix = self.prefix + self.prefix = prefix..name.."." + lex:expected "{" + while not lex:test "}" do + local ident, pos = lex:type_name() + local body_parser = msg_body[ident] + if body_parser then + body_parser(self, lex, typ) + else + local fs = default(typ, 'field') + local f, t = label_field(self, lex, ident) + self.locmap[f] = pos + insert_tab(fs, f) + if t then + local ts = default(typ, 'nested_type') + insert_tab(ts, t) + end + end + lex:line_end 'opt' + end + lex:line_end 'opt' + if info then + info = default(info, 'message_type') + insert_tab(info, typ) + end + self.prefix = prefix + return typ +end + +function toplevel:enum(lex, info) + local name = lex:ident 'enum name' + local enum = { name = name } + register_type(self, lex, name, types.enum) + lex:expected "{" + while not lex:test "}" do + local ident = lex:ident 'enum constant name' + if ident == 'option' then + toplevel.option(self, lex, default(enum, 'options')) + else + local values = default(enum, 'value') + local number = lex:expected '=' :integer() + lex:line_end() + insert_tab(values, { + name = ident, + number = number, + options = inline_option(lex) + }) + end + lex:line_end 'opt' + end + lex:line_end 'opt' + if info then + info = default(info, 'enum_type') + insert_tab(info, enum) + end + return enum +end + +function toplevel:option(lex, info) + local ident = lex:option_name() + lex:expected "=" + local value = lex:constant() + lex:line_end() + local options = info and default(info, 'options') or {} + options[ident] = value + return options, self +end + +function toplevel:extend(lex, info) + local name = lex:type_name() + local ft = info and default(info, 'extension') or {} + local mt = info and default(info, 'message_type') or {} + lex:expected "{" + while not lex:test "}" do + local ident, pos = lex:type_name() + local f, t = label_field(self, lex, ident) + self.locmap[f] = pos + f.extendee = name + insert_tab(ft, f) + insert_tab(mt, t) + lex:line_end 'opt' + end + return ft, mt +end + +local svr_body = {} do + +function svr_body:rpc(lex, info) + local name, pos = lex:ident "rpc name" + local rpc = { name = name } + self.locmap[rpc] = pos + local _, tn + lex:expected "%(" + rpc.client_streaming = lex:keyword("stream", "opt") + _, tn = type_info(lex, lex:type_name()) + if not tn then return lex:error "rpc input type must by message" end + rpc.input_type = tn + lex:expected "%)" :expected "returns" :expected "%(" + rpc.server_streaming = lex:keyword("stream", "opt") + _, tn = type_info(lex, lex:type_name()) + if not tn then return lex:error "rpc output type must by message" end + rpc.output_type = tn + lex:expected "%)" + if lex:test "{" then + while not lex:test "}" do + lex:line_end "opt" + lex:keyword "option" + toplevel.option(self, lex, default(rpc, 'options')) + end + end + lex:line_end "opt" + local t = default(info, "method") + insert_tab(t, rpc) +end + +function svr_body:option(lex, info) + toplevel.option(self, lex, default(info, 'options')) -- TODO: should be deeper in the info? +end + +function svr_body.stream(_, lex) + lex:error "stream not implement yet" +end + +end + +function toplevel:service(lex, info) + local name = lex:ident 'service name' + local svr = { name = name } + lex:expected "{" + while not lex:test "}" do + local ident = lex:type_name() + local body_parser = svr_body[ident] + if body_parser then + body_parser(self, lex, svr) + else + return lex:error "expected 'rpc' or 'option' in service body" + end + lex:line_end 'opt' + end + lex:line_end 'opt' + if info then + info = default(info, 'service') + insert_tab(info, svr) + end + return svr +end + +end + +local function make_context(self, lex) + local ctx = { + syntax = "proto2"; + locmap = {}; + prefix = "."; + lex = lex; + parser = self; + } + ctx.loaded = self.loaded + ctx.typemap = self.typemap + ctx.paths = self.paths + + function ctx.import_fallback(import_name) + if self.unknown_import == true then + return true + elseif type(self.unknown_import) == 'string' then + return import_name:match(self.unknown_import) and true or nil + elseif self.unknown_import then + return self:unknown_import(import_name) + end + end + + function ctx.type_fallback(type_name) + if self.unknown_type == true then + return true + elseif type(self.unknown_type) == 'string' then + return type_name:match(self.unknown_type) and true + elseif self.unknown_type then + return self:unknown_type(type_name) + end + end + + function ctx.on_import(info) + if self.on_import then + return self.on_import(info) + end + end + + return setmetatable(ctx, Parser) +end + +function Parser:parse(src, name) + local loaded = self.loaded[name] + if loaded then + if loaded == true then + error("loop loaded: "..name) + end + return loaded + end + + name = name or "" + self.loaded[name] = true + local lex = Lexer.new(name, src) + local ctx = make_context(self, lex) + local info = { name = lex.name, syntax = ctx.syntax } + + local syntax = lex:keyword('syntax', 'opt') + if syntax then + info.syntax = lex:expected '=' :quote() + ctx.syntax = info.syntax + lex:line_end() + end + + while not lex:eof() do + local ident = lex:ident() + local top_parser = toplevel[ident] + if top_parser then + top_parser(ctx, lex, info) + else + lex:error("unknown keyword '"..ident.."'") + end + lex:line_end "opt" + end + self.loaded[name] = name ~= "" and info or nil + return ctx:resolve(lex, info) +end + +-- resolver + +local function empty() end + +local function iter(t, k) + local v = t[k] + if v then return ipairs(v) end + return empty +end + +local function check_dup(self, lex, typ, map, k, v) + local old = map[v[k]] + if old then + local ln, co = lex:pos2loc(self.locmap[old]) + lex:error("%s '%s' exists, previous at %d:%d", + typ, v[k], ln, co) + end + map[v[k]] = v +end + +local function check_type(self, lex, tname) + if tname:match "^%." then + local t = self.typemap[tname] + if not t then + return lex:error("unknown type '%s'", tname) + end + return t, tname + end + local prefix = self.prefix + for i = #prefix+1, 1, -1 do + local op = prefix[i] + prefix[i] = tname + local tn = table.concat(prefix, ".", 1, i) + prefix[i] = op + local t = self.typemap[tn] + if t then return t, tn end + end + local tn, t + if self.type_fallback then + tn, t = self.type_fallback(tname) + end + if tn then + t = types[t or "message"] + if tn == true then tn = "."..tname end + return t, tn + end + return lex:error("unknown type '%s'", tname) +end + +local function check_field(self, lex, info) + if info.extendee then + local t, tn = check_type(self, lex, info.extendee) + if t ~= types.message then + lex:error("message type expected in extension") + end + info.extendee = tn + end + if info.type_name then + local t, tn = check_type(self, lex, info.type_name) + info.type = t + info.type_name = tn + end +end + +local function check_enum(self, lex, info) + local names, numbers = {}, {} + for _, v in iter(info, 'value') do + lex.pos = self.locmap[v] + check_dup(self, lex, 'enum name', names, 'name', v) + if not (info.options + and info.options.options + and info.options.options.allow_alias) then + check_dup(self, lex, 'enum number', numbers, 'number', v) + end + end +end + +local function check_message(self, lex, info) + insert_tab(self.prefix, info.name) + local names, numbers = {}, {} + for _, v in iter(info, 'field') do + lex.pos = assert(self.locmap[v]) + check_dup(self, lex, 'field name', names, 'name', v) + check_dup(self, lex, 'field number', numbers, 'number', v) + check_field(self, lex, v) + end + for _, v in iter(info, 'nested_type') do + check_message(self, lex, v) + end + for _, v in iter(info, 'extension') do + lex.pos = assert(self.locmap[v]) + check_field(self, lex, v) + end + self.prefix[#self.prefix] = nil +end + +local function check_service(self, lex, info) + local names = {} + for _, v in iter(info, 'method') do + lex.pos = self.locmap[v] + check_dup(self, lex, 'rpc name', names, 'name', v) + local t, tn = check_type(self, lex, v.input_type) + v.input_type = tn + if t ~= types.message then + lex:error "message type expected in parameter" + end + t, tn = check_type(self, lex, v.output_type) + v.output_type = tn + if t ~= types.message then + lex:error "message type expected in return" + end + end +end + +function Parser:resolve(lex, info) + self.prefix = { "", info.package } + for _, v in iter(info, 'message_type') do + check_message(self, lex, v) + end + for _, v in iter(info, 'enum_type') do + check_enum(self, lex, v) + end + for _, v in iter(info, 'service') do + check_service(self, lex, v) + end + for _, v in iter(info, 'extension') do + lex.pos = assert(self.locmap[v]) + check_field(self, lex, v) + end + self.prefix = nil + return info +end + +end + +local has_pb, pb = pcall(require, "protobuf") do +if has_pb then + local descriptor_pb = + "\10\249#\10\16descriptor.proto\18\15google.protobuf\"G\10\17FileDescript".. + "orSet\0182\10\4file\24\1 \3(\0112$.google.protobuf.FileDescriptorProto\"".. + "\219\3\10\19FileDescriptorProto\18\12\10\4name\24\1 \1(\9\18\15\10\7pack".. + "age\24\2 \1(\9\18\18\10\10dependency\24\3 \3(\9\18\25\10\17public_depend".. + "ency\24\10 \3(\5\18\23\10\15weak_dependency\24\11 \3(\5\0186\10\12messag".. + "e_type\24\4 \3(\0112 .google.protobuf.DescriptorProto\0187\10\9enum_type".. + "\24\5 \3(\0112$.google.protobuf.EnumDescriptorProto\0188\10\7service\24".. + "\6 \3(\0112'.google.protobuf.ServiceDescriptorProto\0188\10\9extension".. + "\24\7 \3(\0112%.google.protobuf.FieldDescriptorProto\18-\10\7options\24".. + "\8 \1(\0112\28.google.protobuf.FileOptions\0189\10\16source_code_info\24".. + "\9 \1(\0112\31.google.protobuf.SourceCodeInfo\18\14\10\6syntax\24\12 \1(".. + "\9\"\228\3\10\15DescriptorProto\18\12\10\4name\24\1 \1(\9\0184\10\5field".. + "\24\2 \3(\0112%.google.protobuf.FieldDescriptorProto\0188\10\9extension".. + "\24\6 \3(\0112%.google.protobuf.FieldDescriptorProto\0185\10\11nested_ty".. + "pe\24\3 \3(\0112 .google.protobuf.DescriptorProto\0187\10\9enum_type\24".. + "\4 \3(\0112$.google.protobuf.EnumDescriptorProto\18H\10\15extension_rang".. + "e\24\5 \3(\0112/.google.protobuf.DescriptorProto.ExtensionRange\0189\10".. + "\10oneof_decl\24\8 \3(\0112%.google.protobuf.OneofDescriptorProto\0180".. + "\10\7options\24\7 \1(\0112\31.google.protobuf.MessageOptions\26,\10\14Ex".. + "tensionRange\18\13\10\5start\24\1 \1(\5\18\11\10\3end\24\2 \1(\5\"\169\5".. + "\10\20FieldDescriptorProto\18\12\10\4name\24\1 \1(\9\18\14\10\6number\24".. + "\3 \1(\5\18:\10\5label\24\4 \1(\0142+.google.protobuf.FieldDescriptorPro".. + "to.Label\0188\10\4type\24\5 \1(\0142*.google.protobuf.FieldDescriptorPro".. + "to.Type\18\17\10\9type_name\24\6 \1(\9\18\16\10\8extendee\24\2 \1(\9\18".. + "\21\10\13default_value\24\7 \1(\9\18\19\10\11oneof_index\24\9 \1(\5\18.".. + "\10\7options\24\8 \1(\0112\29.google.protobuf.FieldOptions\"\182\2\10\4T".. + "ype\18\15\10\11TYPE_DOUBLE\16\1\18\14\10\10TYPE_FLOAT\16\2\18\14\10\10TY".. + "PE_INT64\16\3\18\15\10\11TYPE_UINT64\16\4\18\14\10\10TYPE_INT32\16\5\18".. + "\16\10\12TYPE_FIXED64\16\6\18\16\10\12TYPE_FIXED32\16\7\18\13\10\9TYPE_B".. + "OOL\16\8\18\15\10\11TYPE_STRING\16\9\18\14\10\10TYPE_GROUP\16\10\18\16".. + "\10\12TYPE_MESSAGE\16\11\18\14\10\10TYPE_BYTES\16\12\18\15\10\11TYPE_UIN".. + "T32\16\13\18\13\10\9TYPE_ENUM\16\14\18\17\10\13TYPE_SFIXED32\16\15\18\17".. + "\10\13TYPE_SFIXED64\16\16\18\15\10\11TYPE_SINT32\16\17\18\15\10\11TYPE_S".. + "INT64\16\18\"C\10\5Label\18\18\10\14LABEL_OPTIONAL\16\1\18\18\10\14LABEL".. + "_REQUIRED\16\2\18\18\10\14LABEL_REPEATED\16\3\"$\10\20OneofDescriptorPro".. + "to\18\12\10\4name\24\1 \1(\9\"\140\1\10\19EnumDescriptorProto\18\12\10\4".. + "name\24\1 \1(\9\0188\10\5value\24\2 \3(\0112).google.protobuf.EnumValueD".. + "escriptorProto\18-\10\7options\24\3 \1(\0112\28.google.protobuf.EnumOpti".. + "ons\"l\10\24EnumValueDescriptorProto\18\12\10\4name\24\1 \1(\9\18\14\10".. + "\6number\24\2 \1(\5\0182\10\7options\24\3 \1(\0112!.google.protobuf.Enum".. + "ValueOptions\"\144\1\10\22ServiceDescriptorProto\18\12\10\4name\24\1 \1(".. + "\9\0186\10\6method\24\2 \3(\0112&.google.protobuf.MethodDescriptorProto".. + "\0180\10\7options\24\3 \1(\0112\31.google.protobuf.ServiceOptions\"\193".. + "\1\10\21MethodDescriptorProto\18\12\10\4name\24\1 \1(\9\18\18\10\10input".. + "_type\24\2 \1(\9\18\19\10\11output_type\24\3 \1(\9\18/\10\7options\24\4 ".. + "\1(\0112\30.google.protobuf.MethodOptions\18\31\10\16client_streaming\24".. + "\5 \1(\8:\5false\18\31\10\16server_streaming\24\6 \1(\8:\5false\"\231\4".. + "\10\11FileOptions\18\20\10\12java_package\24\1 \1(\9\18\28\10\20java_out".. + "er_classname\24\8 \1(\9\18\"\10\19java_multiple_files\24\10 \1(\8:\5fals".. + "e\18,\10\29java_generate_equals_and_hash\24\20 \1(\8:\5false\18%\10\22ja".. + "va_string_check_utf8\24\27 \1(\8:\5false\18F\10\12optimize_for\24\9 \1(".. + "\0142).google.protobuf.FileOptions.OptimizeMode:\5SPEED\18\18\10\10go_pa".. + "ckage\24\11 \1(\9\18\"\10\19cc_generic_services\24\16 \1(\8:\5false\18$".. + "\10\21java_generic_services\24\17 \1(\8:\5false\18\"\10\19py_generic_ser".. + "vices\24\18 \1(\8:\5false\18\25\10\10deprecated\24\23 \1(\8:\5false\18".. + "\31\10\16cc_enable_arenas\24\31 \1(\8:\5false\18\25\10\17objc_class_pref".. + "ix\24$ \1(\9\18C\10\20uninterpreted_option\24\231\7 \3(\0112$.google.pro".. + "tobuf.UninterpretedOption\":\10\12OptimizeMode\18\9\10\5SPEED\16\1\18\13".. + "\10\9CODE_SIZE\16\2\18\16\10\12LITE_RUNTIME\16\3*\9\8\232\7\16\128\128".. + "\128\128\2\"\230\1\10\14MessageOptions\18&\10\23message_set_wire_format".. + "\24\1 \1(\8:\5false\18.\10\31no_standard_descriptor_accessor\24\2 \1(\8:".. + "\5false\18\25\10\10deprecated\24\3 \1(\8:\5false\18\17\10\9map_entry\24".. + "\7 \1(\8\18C\10\20uninterpreted_option\24\231\7 \3(\0112$.google.protobu".. + "f.UninterpretedOption*\9\8\232\7\16\128\128\128\128\2\"\160\2\10\12Field".. + "Options\18:\10\5ctype\24\1 \1(\0142#.google.protobuf.FieldOptions.CType:".. + "\6STRING\18\14\10\6packed\24\2 \1(\8\18\19\10\4lazy\24\5 \1(\8:\5false".. + "\18\25\10\10deprecated\24\3 \1(\8:\5false\18\19\10\4weak\24\10 \1(\8:\5f".. + "alse\18C\10\20uninterpreted_option\24\231\7 \3(\0112$.google.protobuf.Un".. + "interpretedOption\"/\10\5CType\18\10\10\6STRING\16\0\18\8\10\4CORD\16\1".. + "\18\16\10\12STRING_PIECE\16\2*\9\8\232\7\16\128\128\128\128\2\"\141\1\10".. + "\11EnumOptions\18\19\10\11allow_alias\24\2 \1(\8\18\25\10\10deprecated".. + "\24\3 \1(\8:\5false\18C\10\20uninterpreted_option\24\231\7 \3(\0112$.goo".. + "gle.protobuf.UninterpretedOption*\9\8\232\7\16\128\128\128\128\2\"}\10".. + "\16EnumValueOptions\18\25\10\10deprecated\24\1 \1(\8:\5false\18C\10\20un".. + "interpreted_option\24\231\7 \3(\0112$.google.protobuf.UninterpretedOptio".. + "n*\9\8\232\7\16\128\128\128\128\2\"{\10\14ServiceOptions\18\25\10\10depr".. + "ecated\24! \1(\8:\5false\18C\10\20uninterpreted_option\24\231\7 \3(\0112".. + "$.google.protobuf.UninterpretedOption*\9\8\232\7\16\128\128\128\128\2\"z".. + "\10\13MethodOptions\18\25\10\10deprecated\24! \1(\8:\5false\18C\10\20uni".. + "nterpreted_option\24\231\7 \3(\0112$.google.protobuf.UninterpretedOption".. + "*\9\8\232\7\16\128\128\128\128\2\"\158\2\10\19UninterpretedOption\18;\10".. + "\4name\24\2 \3(\0112-.google.protobuf.UninterpretedOption.NamePart\18\24".. + "\10\16identifier_value\24\3 \1(\9\18\26\10\18positive_int_value\24\4 \1(".. + "\4\18\26\10\18negative_int_value\24\5 \1(\3\18\20\10\12double_value\24\6".. + "\32\1(\1\18\20\10\12string_value\24\7 \1(\12\18\23\10\15aggregate_value".. + "\24\8 \1(\9\0263\10\8NamePart\18\17\10\9name_part\24\1 \2(\9\18\20\10\12".. + "is_extension\24\2 \2(\8\"\213\1\10\14SourceCodeInfo\18:\10\8location\24".. + "\1 \3(\0112(.google.protobuf.SourceCodeInfo.Location\26\134\1\10\8Locati".. + "on\18\16\10\4path\24\1 \3(\5B\2\16\1\18\16\10\4span\24\2 \3(\5B\2\16\1".. + "\18\24\10\16leading_comments\24\3 \1(\9\18\25\10\17trailing_comments\24".. + "\4 \1(\9\18!\10\25leading_detached_comments\24\6 \3(\9B)\10\19com.google".. + ".protobufB\16DescriptorProtosH\1" + + function Parser.reload() + assert(pb.load(descriptor_pb), "load descriptor msg failed") + end + + local function do_compile(self, f, ...) + if self.include_imports then + local old = self.on_import + local infos = {} + function self.on_import(info) + insert_tab(infos, info) + end + local r = f(...) + insert_tab(infos, r) + self.on_import = old + return { file = infos } + end + return { file = { f(...) } } + end + + function Parser:compile(s, name) + if self == Parser then self = Parser.new() end + local set = do_compile(self, self.parse, self, s, name) + return pb.encode('.google.protobuf.FileDescriptorSet', set) + end + + function Parser:compilefile(fn) + if self == Parser then self = Parser.new() end + local set = do_compile(self, self.parsefile, self, fn) + return pb.encode('.google.protobuf.FileDescriptorSet', set) + end + + function Parser:load(s, name) + if self == Parser then self = Parser.new() end + local ret, pos = pb.load(self:compile(s, name)) + if ret then return ret, pos end + error("load failed at offset "..pos) + end + + function Parser:loadfile(fn) + if self == Parser then self = Parser.new() end + local ret, pos = pb.load(self:compilefile(fn)) + if ret then return ret, pos end + error("load failed at offset "..pos) + end + + Parser.reload() + + end +end + +return Parser \ No newline at end of file diff --git a/lualib/protocol/dns.lua b/lualib/protocol/dns.lua index ab5193f8..7806b0d5 100644 --- a/lualib/protocol/dns.lua +++ b/lualib/protocol/dns.lua @@ -1,14 +1,17 @@ local UDP = require "internal.UDP" -local co = require "internal.Co" -local sys = require "sys" -local log = require "log" -local prefix = '::ffff:' +local sys = require "sys" +local log = require "logging" +local Log = log:new({ dump = true, path = 'protocol-dns'}) -local co_self = co.self -local co_wait = co.wait -local co_wakeup = co.wakeup +local cf = require "cf" +local cf_fork = cf.fork +local cf_self = cf.self +local cf_wait = cf.wait +local cf_wakeup = cf.wakeup +local cf_sleep = cf.sleep +local new_tab = sys.new_tab local check_ipv4 = sys.ipv4 local check_ipv6 = sys.ipv6 @@ -19,18 +22,25 @@ local concat = table.concat local insert = table.insert local now = os.time +local random = math.random +local lower = string.lower local fmt = string.format +local find = string.find local match = string.match local splite = string.gmatch local pack = string.pack local unpack = string.unpack + local type = type +local ipairs = ipairs + +local prefix = '::ffff:' local dns = {} local QTYPE = { - A = 1, - AAAA = 28, + A = 1, + AAAA = 28, } local dns_cache = {} @@ -40,113 +50,119 @@ local dns_list = {} local thread_id = 0 local function gen_id() - thread_id = thread_id % MAX_THREAD_ID + 1 - return thread_id + thread_id = thread_id % MAX_THREAD_ID + 1 + return thread_id end local function check_cache(domain) - local query = dns_cache[domain] - if not query then - return - end - if query then - if not query.ttl or query.ttl > now() then - return query.ip - end - dns_cache[domain] = nil - end + local query = dns_cache[domain] + if not query then return + end + local ttl = query.ttl + if not ttl then + return query.ip + end + if ttl > now() then + return query.ip + end + dns_cache[domain] = nil + return +end + +local function add_cache(domain, ip) + dns_cache[domain] = { ip = ip } end local function check_ip(ip) - if ip then - if check_ipv4(ip) then - return true, 4 - end - if check_ipv6(ip) then - return true, 6 - end + if type(ip) == 'string' and ip ~= '' then + if check_ipv4(ip) then + return true, 4 + end + if check_ipv6(ip) then + return true, 6 end + end + return false, "Not a valid IP address." end local function gen_cache() - local file = io.open("/etc/hosts", "r") - dns_cache['localhost'] = {ip = '127.0.0.1'} + local file = io.open("/etc/hosts", "r") or io.open("/cygdrive/c/Windows/System32/drivers/etc/hosts", "r") or io.open("/c/Windows/System32/drivers/etc/hosts", "r") if file then - for line in file:lines() do - local ip, domain = match(line, '([^# %t]*)[%t ]*(.*)$') - local ok, v = check_ip(ip) - if ok then - if v == 4 then - if not dns_cache[domain] then - dns_cache[domain] = {ip = prefix..ip} - end - else - if not dns_cache[domain] then - dns_cache[domain] = {ip = ip} - end - end + for line in file:lines() do + if not find(line, "^([%G]*)#") then + local ip, domain = match(line, '([^#%G]*)[%G]+(.+)') + local ok, v = check_ip(ip) + if ok then + for d in splite(domain or '', "([^ ]+)") do + dns_cache[d] = { ip = v == 4 and prefix .. ip or ip } end + end end - file:close() + end + file:close() + end + if not dns_cache['localhost'] then + dns_cache['localhost'] = {ip = prefix..'127.0.0.1'} end end if #dns_list < 1 then - local file = io.open("/etc/resolv.conf", "r") - if file then - for line in file:lines() do - local ip = match(line, "nameserver (.-)$") - local ok = check_ip(ip) - if ok then - insert(dns_list, ip) - end + local file = io.open("/etc/resolv.conf", "r") + if file then + for line in file:lines() do + if not find(line, "^[ ]*#.-") then + local ip = match(line, "nameserver ([^ ]+)$") + local ok, v = check_ip(ip) + if ok and v then + insert(dns_list, ip) end - file:close() - end - if #dns_list < 1 then - dns_list = {"114.114.114.114", "8.8.8.8"} + end end - gen_cache() + file:close() + end + if #dns_list < 1 then + dns_list = {"1.2.4.8", "210.2.4.8"} + end + gen_cache() end -local function get_dns_client() - if #dns_list >= 1 then - local ip = dns_list[1] - local udp = UDP:new():timeout(dns._timeout or 5) - local ok, v = check_ip(ip) - if v == 4 then - ip = prefix..ip - end - local ok = udp:connect(ip, 53) - if not ok then - return nil, 'Create UDP Socket error.' - end - return udp, v +local function get_dns_client(ip_version) + if #dns_list >= 1 then + local udp = UDP:new():timeout(dns._timeout or 30) + local ip = dns_list[random(1, #dns_list)] + local _, v = check_ip(ip) + if v == 4 then + ip = prefix .. ip end - return nil, "Can't find system dns in /etc/resolve.conf." + local ok = udp:connect(ip, 53) + if not ok then + return nil, 'Create UDP Socket error.' + end + if ip_version then + v = ip_version + end + return udp, v + end + return nil, "Can't find system dns in /etc/resolve.conf." end local function pack_header() local tid = gen_id() - local flag = 0x100 + local flag = 0x120 local QCOUNT = 1 - -- QCount 永远是1, flags 永远是256 + -- QCount 永远是1, flags 永远是288 return pack(">HHHHHH", tid, flag, QCOUNT, 0, 0, 0) end local function pack_question(name, version) -- local Query_Type = QTYPE.A -- IPv4 -- local Query_Type = QTYPE.AAAA -- IPv6 - local qtype = QTYPE.A - if version == 6 then - qtype = QTYPE.AAAA - end - local Query_Type = qtype + local Query_Type = version == 6 and QTYPE.AAAA or QTYPE.A local Query_Class = 0x01 -- IN internet local question = {} for sp in splite(name, "([^%.]*)") do - insert(question, pack(">s1", sp)) + insert(question, pack(">s1", sp)) end insert(question, "\0") insert(question, pack(">HH", Query_Type, Query_Class)) @@ -154,8 +170,8 @@ local function pack_question(name, version) end local function unpack_header(chunk) - local tid, flags, qdcount, ancount, nscount, arcount, nbyte = unpack(">HHHHHH", chunk) - return { tid = tid, flags = flags, qdcount = qdcount, ancount = ancount}, nbyte + local tid, flags, qdcount, ancount, _, _, nbyte = unpack(">HHHHHH", chunk) + return { tid = tid, flags = flags, qdcount = qdcount, ancount = ancount}, nbyte end local function unpack_name(chunk, nbyte) @@ -163,20 +179,20 @@ local function unpack_name(chunk, nbyte) local jump_pointer local tag, offset, label while true do - tag, nbyte = unpack(">B", chunk, nbyte) - if tag & 0xc0 == 0xc0 then - offset,nbyte = unpack(">H", chunk, nbyte - 1) - offset = offset & 0x3fff - if not jump_pointer then - jump_pointer = nbyte - end - nbyte = offset + 1 - elseif tag == 0 then - break - else - label, nbyte = unpack(">s1", chunk, nbyte - 1) - domain[#domain+1] = label + tag, nbyte = unpack(">B", chunk, nbyte) + if tag & 0xc0 == 0xc0 then + offset,nbyte = unpack(">H", chunk, nbyte - 1) + offset = offset & 0x3fff + if not jump_pointer then + jump_pointer = nbyte end + nbyte = offset + 1 + elseif tag == 0 then + break + else + label, nbyte = unpack(">s1", chunk, nbyte - 1) + domain[#domain+1] = label + end end return concat(domain, "."), jump_pointer or nbyte end @@ -194,125 +210,147 @@ local function unpack_answer(chunk, nbyte) end local function unpack_rdata(chunk, qtype) - if qtype == QTYPE.AAAA then - return fmt('%x:%x:%x:%x:%x:%x:%x:%x', unpack(">HHHHHHHH", chunk)) - end - return fmt("%d.%d.%d.%d", unpack(">BBBB", chunk)) + return qtype == QTYPE.AAAA and fmt('%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x', unpack(">HHHHHHHH", chunk)) or fmt("%u.%u.%u.%u", unpack(">BBBB", chunk)) end - local cos = {} -local function dns_query(domain) - local wlist = {} - cos[domain] = wlist - -- local start = os.time() + os.clock() - -- print("开始解析域名:["..domain.."], 开始时间: ", start) - local dns_resp, len - local dns_client, msg = get_dns_client() - if not dns_client then - dns_client:close() - return nil, msg - end - local header = pack_header() - local question = pack_question(domain, msg) - while 1 do -- 如果一直没收到回应将会反复请求 - dns_client:send(header..question) - dns_resp, len = dns_client:recv() - if dns_resp then - dns_client:close() - dns_client = nil - break - end - if type(len) == "string" then - log.info("正在解析["..domain.."]:"..len) - end - end - if not len or len < LIMIT_HEADER_LEN then - local err = "Malformed dns response package." - return nil, err - end - local answer_header, nbyte = unpack_header(dns_resp) - if answer_header.qdcount ~= 1 then - local err = "Malformed dns response package." - return nil, err - end - if not answer_header.ancount or answer_header.ancount < 1 then - local err = "Can't find ip addr in nameserver." - return nil, err - end - local question, nbyte = unpack_question(dns_resp, nbyte) +-- 如果有其它协程也在等待查询, 那么一起唤醒它们 +local function check_wait(domain, wlist, ...) + for _, w in ipairs(wlist) do + cf_wakeup(w, ...) + end + cos[domain] = nil +end - if question.name ~= domain then - local err = "quetions not equal." - return nil, err - end - local answer - for i = 1, answer_header.ancount do - answer, nbyte = unpack_answer(dns_resp, nbyte) - if answer.atype == QTYPE.A or answer.atype == QTYPE.AAAA then - answer.ip = unpack_rdata(answer.rdata, answer.atype) - dns_cache[domain] = {ip = answer.ip, ttl = now() + answer.ttl} +local function dns_query(domain, ip_version) + local wlist = new_tab(32, 0) + cos[domain] = wlist + local dns_client, msg = get_dns_client(ip_version) + if not dns_client then + check_wait(domain, wlist, nil, msg) + return nil, msg + end + local responsed = false + cf_fork(function () + local req = pack_header() .. pack_question(domain, msg) + while not responsed do + for _ = 1, 10 do + -- 用数量来减少UDP丢包率的问题 + dns_client:send(req); dns_client:send(req); dns_client:send(req); cf_sleep(0.1) + if responsed then + return end + end + Log:WARN("Attempt to resolve domain name: [" .. domain .. "] failed.") end - local ok, v = check_ip(answer.ip) - if not ok then - return nil, "unknown ip in this domain: "..domain + end) + local dns_resp, len = dns_client:recv(); dns_client:close(); responsed = true; + if not dns_resp or not len or len < LIMIT_HEADER_LEN then + local err = "1. Malformed message length." + check_wait(domain, wlist, nil, err) + return nil, err + end + local answer_header, nbyte = unpack_header(dns_resp) + if answer_header.qdcount ~= 1 then + local err = "2. Malformed message response." + check_wait(domain, wlist, nil, err) + return nil, err + end + local ancount = answer_header.ancount + if not ancount or ancount < 1 then + if not ip_version then -- 如果IPv4无法解析则尝试ipv6, 反之亦然. + return dns_query(domain, msg == 4 and 6 or 4) end - if ok and v == 4 then - answer.ip = prefix..answer.ip + local err = "3. Unresolved domain name." + check_wait(domain, wlist, nil, err) + return nil, err + end + local question + question, nbyte = unpack_question(dns_resp, nbyte) + if lower(question.name) ~= lower(domain) then + local err = "4. Inconsistent query domain. " .. question.name + check_wait(domain, wlist, nil, err) + return nil, err + end + local answer + local t = now() + for _ = 1, ancount do + answer, nbyte = unpack_answer(dns_resp, nbyte) + if answer.atype == QTYPE.A or answer.atype == QTYPE.AAAA then + answer.ip = unpack_rdata(answer.rdata, answer.atype) + local cache = dns_cache[domain] + if not cache then + dns_cache[domain] = {ip = answer.ip, ttl = answer.ttl > 0 and (t + answer.ttl) or nil } + elseif cache.ttl and cache.ttl < t + answer.ttl then + dns_cache[domain] = {ip = answer.ip, ttl = t + answer.ttl} + end end - -- local e_n_d = os.time() + os.clock() - -- print("解析域名["..domain.."]完成, 结束时间:", e_n_d, ip, answer.ttl) - -- print("解析域名用时: ", tostring(e_n_d - start)..'s') - for i = #wlist, 1, -1 do - -- 如果有其它协程也在等待查询, 那么一起唤醒它们 - co_wakeup(wlist[i], true, answer.ip) + end + answer = dns_cache[domain] + if not answer then + if not ip_version then -- 如果IPv4无法解析则尝试ipv6, 反之亦然. + return dns_query(domain, msg == 4 and 6 or 4) end - cos[domain] = nil - return true, answer.ip + local err = "5. (" .. (domain) .. ") query failed." + check_wait(domain, wlist, nil, err) + return nil, err + end + local ip = answer.ip + local _, v = check_ip(ip) + if v == 4 then + ip = prefix..ip + answer.ip = ip + end + check_wait(domain, wlist, true, ip) + return true, ip end function dns.flush() - dns_cache = {} - gen_cache() + dns_cache = {} + gen_cache() end -- 这里设置每个dns查询请求的timeout与retry时间 function dns.timeout(timeout) - dns._timeout = timeout + dns._timeout = timeout end -function dns.resolve(domain) - -- 如果传进来一个空值, 则直接返回false - if not domain or domain == '' then - return nil, "attemp to resolve a nil or empty host name." - end - -- 如果是正确的ipv4地址直接返回 - local ok, v = check_ip(domain) - if ok then - if 6 == v then - return ok, domain - end - return ok, prefix..domain - end - -- 如果有dns缓存直接返回 - local ip = check_cache(domain) - if ip then - if check_ipv6(ip) then - return true, ip - end - return true, prefix..ip +function dns.resolve(domain, ip_version) + -- 检查参数是否有效 + if type(domain) ~= 'string' or domain == '' then + return nil, "attempt to pass an invalid domain." + end + -- 如果有dns缓存直接返回, 任何依据都要以缓存为主 + local ip = check_cache(domain) + if ip then + if check_ipv6(ip) then + return true, ip end - -- 如果有其他协程也正巧在查询这个域名, 那么就加入到等待列表内 - local wlist = cos[domain] - if wlist then - local co = co_self() - insert(wlist, co) - return co_wait() + return true, prefix..ip + end + -- 如果是正确的ipv4地址直接返回 + local ok, v = check_ip(domain) + if ok then + -- 缓存IP->IP的映射, 减少查表与字符串连接次数. + -- 这样可以降低大量的内存与CPU的使用. + if 6 == v then + add_cache(domain, domain) + return ok, domain end - return dns_query(domain) + add_cache(domain, prefix..domain) + return ok, prefix..domain + end + -- 如果有其他协程也正巧在查询这个域名, 那么就加入到等待列表内 + local wlist = cos[domain] + if wlist then + local co = cf_self() + insert(wlist, co) + return cf_wait() + end + return dns_query(domain, ip_version) end -- require "utils" -- var_dump(dns_cache) -return dns \ No newline at end of file +-- var_dump(dns_list) +return dns diff --git a/lualib/protocol/http.lua b/lualib/protocol/http.lua deleted file mode 100644 index 958db118..00000000 --- a/lualib/protocol/http.lua +++ /dev/null @@ -1,627 +0,0 @@ -local log = require "log" -local sys = require "system" -local tcp = require "internal.TCP" -local wsserver = require "protocol.websocket.server" - -local crypt = require "crypt" -local sha1 = crypt.sha1 -local base64 = crypt.base64encode -local now = sys.now -local DATE = os.date - -local form = require "httpd.Form" -local FILE_TYPE = form.FILE -local ARGS_TYPE = form.ARGS -local form_multipart = form.multipart -local form_urlencode = form.urlencode - -local Router = require "httpd.Router" -local ROUTE_FIND = Router.find -local ROUTE_REGISTERY = Router.registery - -local httpparser = require "httpparser" -local REQUEST_PROTOCOL_PARSER = httpparser.parser_request_protocol -local RESPONSE_PROTOCOL_PARSER = httpparser.parser_response_protocol -local REQUEST_HEADER_PARSER = httpparser.parser_request_header -local RESPONSE_HEADER_PARSER = httpparser.parser_response_header - -local type = type -local assert = assert -local setmetatable = setmetatable -local tostring = tostring -local next = next -local pcall = pcall -local ipairs = ipairs -local time = os.time -local char = string.char -local lower = string.lower -local upper = string.upper -local match = string.match -local fmt = string.format -local toint = math.tointeger -local find = string.find -local split = string.sub -local splite = string.gmatch -local spliter = string.gsub -local remove = table.remove -local concat = table.concat - -local CRLF = '\x0d\x0a' -local CRLF2 = '\x0d\x0a\x0d\x0a' - -local SERVER = 'cf web/0.1' - -local HTTP_CODE = { - - [100] = "HTTP/1.1 100 Continue", - [101] = "HTTP/1.1 101 Switching Protocol", - [102] = "HTTP/1.1 102 Processing", - - [200] = "HTTP/1.1 200 OK", - [201] = "HTTP/1.1 201 Created", - [202] = "HTTP/1.1 202 Accepted", - [203] = "HTTP/1.1 203 Non-Authoritative Information", - [204] = "HTTP/1.1 204 No Content", - [205] = "HTTP/1.1 205 Reset Content", - [206] = "HTTP/1.1 206 Partial Content", - [207] = "HTTP/1.1 207 Multi-Status", - [208] = "HTTP/1.1 208 Multi-Status", - [226] = "HTTP/1.1 226 IM Used", - - [300] = "HTTP/1.1 300 Multiple Choice", - [301] = "HTTP/1.1 301 Moved Permanently", - [302] = "HTTP/1.1 302 Found", - [303] = "HTTP/1.1 303 See Other", - [304] = "HTTP/1.1 304 Not Modified", - [305] = "HTTP/1.1 305 Use Proxy", - [306] = "HTTP/1.1 306 unused", - [307] = "HTTP/1.1 307 Temporary Redirect", - [305] = "HTTP/1.1 308 Permanent Redirect", - - [400] = "HTTP/1.1 400 Bad Request", - [401] = "HTTP/1.1 401 Unauthorized", - [402] = "HTTP/1.1 402 Payment Required", - [403] = "HTTP/1.1 403 Forbidden", - [404] = "HTTP/1.1 404 Not Found", - [405] = "HTTP/1.1 405 Method Not Allowed", - [406] = "HTTP/1.1 406 Not Acceptable", - [407] = "HTTP/1.1 407 Proxy Authentication Required", - [408] = "HTTP/1.1 408 Request Timeout", - [409] = "HTTP/1.1 409 Conflict", - - [410] = "HTTP/1.1 410 Gone", - [411] = "HTTP/1.1 411 Length Required", - [412] = "HTTP/1.1 412 Precondition Failed", - [413] = "HTTP/1.1 413 Payload Too Large", - [414] = "HTTP/1.1 414 URI Too Long", - [415] = "HTTP/1.1 415 Unsupported Media Type", - [416] = "HTTP/1.1 416 Requested Range Not Satisfiable", - [417] = "HTTP/1.1 417 Expectation Failed", - [418] = "HTTP/1.1 418 I'm a teapot", - - [421] = "HTTP/1.1 421 Misdirected Request", - [422] = "HTTP/1.1 422 Unprocessable Entity (WebDAV)", - [423] = "HTTP/1.1 423 Locked (WebDAV)", - [424] = "HTTP/1.1 424 Failed Dependency", - [426] = "HTTP/1.1 426 Upgrade Required", - [428] = "HTTP/1.1 428 Precondition Required", - [429] = "HTTP/1.1 429 Too Many Requests", - [431] = "HTTP/1.1 431 Request Header Fields Too Large", - [451] = "HTTP/1.1 451 Unavailable For Legal Reasons", - - [500] = "HTTP/1.1 500 Internal Server Error", - [501] = "HTTP/1.1 501 Not Implemented", - [502] = "HTTP/1.1 502 Bad Gateway", - [503] = "HTTP/1.1 503 Service Unavailable", - [504] = "HTTP/1.1 504 Gateway Timeout", - [505] = "HTTP/1.1 505 HTTP Version Not Supported", - [506] = "HTTP/1.1 506 Variant Also Negotiates", - [507] = "HTTP/1.1 507 Insufficient Storage", - [508] = "HTTP/1.1 508 Loop Detected (WebDAV)", - [510] = "HTTP/1.1 510 Not Extended", - [503] = "HTTP/1.1 511 Network Authentication Required", - -} - -local MIME = { - -- 文本格式 - ['xml'] = 'application/xml', - ['htm'] = 'text/html', - ['html'] = 'text/html', - ['xhtml'] = 'application/xhtml+xml', - ['txt'] = 'text/plain', - ['css'] = 'text/css', - ['js'] = 'application/javascript', - ['json'] = 'application/json', - -- 图片格式 - ['bmp'] = 'image/bmp', - ['png'] = 'image/png', - ['gif'] = 'image/gif', - ['jpeg'] = 'image/jpeg', - ['jpg'] = 'image/jpeg', - ['ico'] = 'image/x-icon', - ['tif'] = 'image/tiff', - ['tiff'] = 'image/tiff', - -- 音频 - ['wav'] = 'audio/wav', - ['acc'] = 'audio/aac', - -- 视频 - ['avi'] = 'video/x-msvideo', - ['mpeg'] = 'video/mpeg', - -- 文档 - ['csv'] = 'text/csv', - ['xls'] = 'application/vnd.ms-excel', - ['xlsx'] = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - ['docx'] = 'application/msword', - ['doc'] = 'application/msword', - ['pdf'] = 'application/pdf', - -- TODO -} - -local HTTP_PROTOCOL = { - API = 1, - [1] = "API", - USE = 2, - [2] = "USE", - STATIC = 3, - [3] = "STATIC", - WS = 4, - [4] = "WS", -} - --- 以下为 HTTP Client 所需所用方法 -function HTTP_PROTOCOL.RESPONSE_HEADER_PARSER(header) - return RESPONSE_HEADER_PARSER(header) -end - -function HTTP_PROTOCOL.RESPONSE_PROTOCOL_PARSER(protocol) - local VERSION, CODE, STATUS = RESPONSE_PROTOCOL_PARSER(protocol) - return CODE -end - --- 以下为 HTTP Server 所需所用方法 -local function REQUEST_STATUCODE_RESPONSE(code) - return HTTP_CODE[code] or "attempt to Passed A Invaid Code to response message." -end - -local function REQUEST_MIME_RESPONSE(mime) - return MIME[mime] -end - -function HTTP_PROTOCOL.FILEMIME(mime) - return MIME[mime] -end - --- -- 路由注册 -HTTP_PROTOCOL.ROUTE_REGISTERY = ROUTE_REGISTERY - --- -- 路由查找 -HTTP_PROTOCOL.ROUTE_FIND = ROUTE_FIND - -local function HTTP_DATE() - return DATE("Date: %a, %d %b %Y %X GMT") - -- return os.date("Date: %a, %d %b %Y %X GMT") -end - -local function HTTP_EXPIRES(timestamp) - return DATE("Expires: %a, %d %b %Y %X GMT", timestamp) - -- return os.date("Date: %a, %d %b %Y %X GMT") -end - -local function PASER_METHOD(http, sock, max_body_size, buffer, METHOD, PATH, HEADER) - local content = {} - if METHOD == "GET" then - local spl_pos = find(PATH, '%?') - if spl_pos and spl_pos < #PATH then - content['args'] = form_urlencode(PATH) - end - elseif METHOD == "POST" or METHOD == "PUT" then - local body_len = toint(HEADER['Content-Length']) or toint(HEADER['content-type']) - if body_len and body_len > 0 then - local BODY = '' - local RECV_BODY = true - local CRLF_START, CRLF_END = find(buffer, CRLF2) - if #buffer > CRLF_END then - BODY = split(buffer, CRLF_END + 1, -1) - if #BODY == body_len then - RECV_BODY = false - end - end - if RECV_BODY then - local buffers = {BODY} - while 1 do - local buf = sock:recv(1024) - if not buf then - return - end - buffers[#buffers+1] = buf - local buffer = concat(buffers) - if #buffer >= (max_body_size or 1024 * 1024) then - return nil, 413 - end - if #buffer == body_len then - BODY = buffer - break - end - end - end - local FILE_ENCODE = 'multipart/form-data' - local XML_ENCODE = 'application/xml' - local JSON_ENCODE = 'application/json' - local URL_ENCODE = 'application/x-www-form-urlencoded' - local format = match(HEADER['Content-Type'], '(.-/[^;]*)') - if format == FILE_ENCODE then - local BOUNDARY = match(HEADER['Content-Type'], '^.+=[%-]*(.+)') - if BOUNDARY and BOUNDARY ~= '' then - local typ, body = form_multipart(BODY, BOUNDARY) - if typ == FILE_TYPE then - content['files'] = body - elseif typ == ARGS_TYPE then - content['args'] = {} - for _, args in ipairs(body) do - content['args'][args[1]] = args[2] - end - end - end - elseif format == JSON_ENCODE then - content['json'] = true - content['body'] = BODY - elseif format == XML_ENCODE then - content['xml'] = true - content['body'] = BODY - elseif format == URL_ENCODE then - content['args'] = form_urlencode(BODY) - else - content['body'] = BODY - end - end - elseif METHOD == "HEAD" or METHOD == "OPTIONS" then - return true, nil - else - -- 暂未支持其他方法 - return - end - return true, content -end - -local function X_Forwarded_FORMAT(tab) - local ip_list - if tab and type(tab) == 'string' then - for ip in splite(tab, '([^ ,;]+)') do - if not ip_list then - ip_list = {ip} - else - if ip ~= ip_list[#ip_list] then - ip_list[#ip_list+1] = ip - end - end - end - return concat(ip_list, ' -> ') - end - return tab -end --- 一些错误返回 -local function ERROR_RESPONSE(http, code, path, ip, forword, method, speed) - http:tolog(code, path, ip, X_Forwarded_FORMAT(forword) or ip, method, speed) - return concat({concat({ - REQUEST_STATUCODE_RESPONSE(code), - HTTP_DATE(), - 'Origin: *', - 'Allow: GET, POST, PUT, HEAD, OPTIONS', - 'Allow: GET, POST, PUT, HEAD, OPTIONS', - 'Access-Control-Allow-Origin: *', - 'Access-Control-Allow-Headers: *', - 'Access-Control-Allow-Methods: GET, POST, PUT, HEAD, OPTIONS', - 'Access-Control-Max-Age: 86400', - 'Connection: close', - 'server: ' .. (http.__server or SERVER), - }, CRLF), CRLF2}) -end - --- WebSocket -local function Switch_Protocol(http, cls, sock, header, method, version, path, ip, start_time) - if version ~= 1.1 then - sock:send(ERROR_RESPONSE(http, 505, path, ip, header['X-Forwarded-For'] or ip, method, now() - start_time)) - sock:close() - return - end - if method ~= 'GET' then - sock:send(ERROR_RESPONSE(http, 405, path, ip, header['X-Forwarded-For'] or ip, method, now() - start_time)) - sock:close() - return - end - if not header['Upgrade'] or lower(header['Upgrade']) ~= 'websocket' then - sock:send(ERROR_RESPONSE(http, 400, path, ip, header['X-Forwarded-For'] or ip, method, now() - start_time)) - sock:close() - return - end - if not header['Upgrade'] or lower(header['Upgrade']) ~= 'websocket' then - sock:send(ERROR_RESPONSE(http, 406, path, ip, header['X-Forwarded-For'] or ip, method, now() - start_time)) - sock:close() - return - end - if header['Sec-WebSocket-Version'] ~= '13' then - sock:send(ERROR_RESPONSE(http, 505, path, ip, header['X-Forwarded-For'] or ip, method, now() - start_time)) - sock:close() - return - end - local sec_key = header['Sec-WebSocket-Key'] - if not sec_key or sec_key == '' then - sock:send(ERROR_RESPONSE(http, 505, path, ip, header['X-Forwarded-For'] or ip, method, now() - start_time)) - sock:close() - return - end - local response = { - REQUEST_STATUCODE_RESPONSE(101), - HTTP_DATE(), - 'Connection: Upgrade', - 'Server: '..(http.__server or SERVER), - 'Upgrade: WebSocket', - 'Sec-WebSocket-Accept: '..base64(sha1(sec_key..'258EAFA5-E914-47DA-95CA-C5AB0DC85B11')) - } - local protocol = header['Sec-Websocket-Protocol'] - if protocol then -- 仅支持协议回传, 具体实现由用户实现 - response[#response+1] = "Sec-Websocket-Protocol: "..tostring(protocol) - end - http:tolog(200, path, header['X-Real-IP'] or ip, X_Forwarded_FORMAT(header['X-Forwarded-For'] or ip), method, now() - start_time) - local ok = sock:send(concat(response, CRLF)..CRLF2) - if not ok then - return sock:close() - end - return wsserver:new({cls = cls, sock = sock}):start() -end - -function HTTP_PROTOCOL.EVENT_DISPATCH(fd, ipaddr, http) - local buffers = {} - local ttl = http.ttl - local server = http.__server - local timeout = http.__timeout - local before_func = http._before_func - local max_path_size = http.__max_path_size - local max_header_size = http.__max_header_size - local max_body_size = http.__max_body_size - local sock = tcp:new():set_fd(fd):timeout(timeout or 15) - while 1 do - local buf = sock:recv(1024) - if not buf then - return sock:close() - end - buffers[#buffers+1] = buf - local buffer = concat(buffers) - local CRLF_START, CRLF_END = find(buffer, CRLF2) - if CRLF_START and CRLF_END then - local start = now() - local PROTOCOL_START, PROTOCOL_END = find(buffer, CRLF) - local METHOD, PATH, VERSION = REQUEST_PROTOCOL_PARSER(buffer) - -- 协议有问题返回400 - if not METHOD or not PATH or not VERSION then - sock:send(ERROR_RESPONSE(http, 400, PATH, ipaddr, METHOD or "GET", now() - start)) - return sock:close() - end - -- 超过自定义最大PATH长度限制 - if PATH and #PATH > (max_path_size or 1024) then - sock:send(ERROR_RESPONSE(http, 414, PATH, ipaddr, METHOD, now() - start)) - return sock:close() - end - -- 没有HEADER返回400 - local HEADER = REQUEST_HEADER_PARSER(buffer) - if not HEADER then - sock:send(ERROR_RESPONSE(http, 400, PATH, ipaddr, METHOD, now() - start)) - return sock:close() - end - -- 超过自定义最大HEADER长度限制 - if #buffer - CRLF_START > (max_header_size or 65535) then - sock:send(ERROR_RESPONSE(http, 431, PATH, ipaddr, METHOD, now() - start)) - return sock:close() - end - -- 这里根据PATH先查找路由, 如果没有直接返回404. - local cls, typ = ROUTE_FIND(PATH) - if not cls or not typ then - sock:send(ERROR_RESPONSE(http, 404, PATH, HEADER['X-Real-IP'] or ipaddr, HEADER['X-Forwarded-For'] or ipaddr, METHOD, now() - start)) - return sock:close() - end - -- 根据请求方法进行解析, 解析失败返回501 - local ok, content = PASER_METHOD(http, sock, max_body_size, buffer, METHOD, PATH, HEADER) - if not ok then - if content == 413 then - sock:send(ERROR_RESPONSE(http, 413, PATH, HEADER['X-Real-IP'] or ipaddr, HEADER['X-Forwarded-For'] or ipaddr, METHOD, now() - start)) - return sock:close() - end - sock:send(ERROR_RESPONSE(http, 501, PATH, HEADER['X-Real-IP'] or ipaddr, HEADER['X-Forwarded-For'] or ipaddr, METHOD, now() - start)) - return sock:close() - end - if not content then -- 没有 Content则返回200; - http:tolog(200, PATH, HEADER['X-Real-IP'] or ipaddr, X_Forwarded_FORMAT(HEADER['X-Forwarded-For'] or ipaddr), METHOD, now() - start) - sock:send(concat({REQUEST_STATUCODE_RESPONSE(200), HTTP_DATE(), - 'Origin: *', - 'Allow: GET, POST, PUT, HEAD, OPTIONS', - 'Access-Control-Allow-Origin: *', - 'Access-Control-Allow-Headers: *', - 'Access-Control-Allow-Methods: GET, POST, PUT, HEAD, OPTIONS', - 'Access-Control-Max-Age: 86400', - 'Connection: keep-alive', - 'server: ' .. (server or SERVER), - }, CRLF)..CRLF2) - return sock:close() - end - content['method'], content['path'], content['headers'] = METHOD, PATH, HEADER - -- before 函数只影响接口与view - if before_func and (typ == HTTP_PROTOCOL.API or typ == HTTP_PROTOCOL.USE) then - local ok, code, data = pcall(before_func, content) - if not ok then -- before 函数执行出错 - log.error(code) - sock:send(ERROR_RESPONSE(http, 500, PATH, HEADER['X-Real-IP'] or ipaddr, HEADER['X-Forwarded-For'] or ipaddr, METHOD, now() - start)) - return sock:close() - end - if code then - if type(code) == "number" then - if code < 200 or code > 500 then - log.error("before function: Illegal return value") - sock:send(ERROR_RESPONSE(http, 500, PATH, HEADER['X-Real-IP'] or ipaddr, HEADER['X-Forwarded-For'] or ipaddr, METHOD, now() - start)) - return sock:close() - elseif code == 301 or code == 302 then - http:tolog(code, PATH, HEADER['X-Real-IP'] or ipaddr, X_Forwarded_FORMAT(HEADER['X-Forwarded-For'] or ipaddr), METHOD, now() - start) - sock:send(concat({ - REQUEST_STATUCODE_RESPONSE(code), HTTP_DATE(), - 'Origin: *', - 'Allow: GET, POST, PUT, HEAD, OPTIONS', - 'Access-Control-Allow-Origin: *', - 'Access-Control-Allow-Headers: *', - 'Access-Control-Allow-Methods: GET, POST, PUT, HEAD, OPTIONS', - 'Access-Control-Max-Age: 86400', - 'Connection: close', - 'server: ' .. (server or SERVER), - 'Location: ' .. (data or "https://github.com/CandyMi/core_framework"), - }, CRLF)..CRLF2) - return sock:close() - elseif code ~= 200 then - if data then - if type(data) == 'string' and data ~= '' then - http:tolog(code, PATH, HEADER['X-Real-IP'] or ipaddr, X_Forwarded_FORMAT(HEADER['X-Forwarded-For'] or ipaddr), METHOD, now() - start) - sock:send(concat({concat({ - REQUEST_STATUCODE_RESPONSE(code), HTTP_DATE(), - 'Origin: *', - 'Allow: GET, POST, PUT, HEAD, OPTIONS', - 'Access-Control-Allow-Origin: *', - 'Access-Control-Allow-Headers: *', - 'Access-Control-Allow-Methods: GET, POST, PUT, HEAD, OPTIONS', - 'Access-Control-Max-Age: 86400', - 'server: ' .. (server or SERVER), - 'Connection: close', - 'Content-Type: ' .. REQUEST_MIME_RESPONSE('html'), - 'Content-Length: '..tostring(#data), - }, CRLF), CRLF2, data})) - return sock:close() - end - end - sock:send(ERROR_RESPONSE(http, code, PATH, HEADER['X-Real-IP'] or ipaddr, HEADER['X-Forwarded-For'] or ipaddr, METHOD, now() - start)) - return sock:close() - end - else - sock:send(ERROR_RESPONSE(http, 401, PATH, HEADER['X-Real-IP'] or ipaddr, HEADER['X-Forwarded-For'] or ipaddr, METHOD, now() - start)) - return sock:close() - end - else - sock:send(ERROR_RESPONSE(http, 401, PATH, HEADER['X-Real-IP'] or ipaddr, HEADER['X-Forwarded-For'] or ipaddr, METHOD, now() - start)) - return sock:close() - end - end - - local header = { } - local ok, body, static, statucode - - if typ == HTTP_PROTOCOL.API or typ == HTTP_PROTOCOL.USE then - if type(cls) == "table" then - local method = cls[lower(METHOD)] - if not method or type(method) ~= 'function' then -- 注册的路由未实现这个方法 - sock:send(ERROR_RESPONSE(http, 405, PATH, HEADER['X-Real-IP'] or ipaddr, HEADER['X-Forwarded-For'] or ipaddr, METHOD, now() - start)) - return sock:close() - end - local c = cls:new(content) - ok, body = pcall(method, c) - else - ok, body = pcall(cls, content) - end - if not ok then - log.error(body) - statucode = 500 - sock:send(ERROR_RESPONSE(http, statucode, PATH, HEADER['X-Real-IP'] or ipaddr, HEADER['X-Forwarded-For'] or ipaddr, METHOD, now() - start)) - return sock:close() - end - statucode = 200 - header[#header+1] = REQUEST_STATUCODE_RESPONSE(statucode) - elseif typ == HTTP_PROTOCOL.WS then - local ok, msg = pcall(Switch_Protocol, http, cls, sock, HEADER, METHOD, VERSION, PATH, HEADER['X-Real-IP'] or ipaddr, start) - if not ok then - log.error(msg) - return sock:close() - end - return - else - local file_type - local path = PATH - local pos, _ = find(PATH, '%?') - if pos then - path = split(path, 1, pos - 1) - end - ok, body, file_type = pcall(cls, './'..path) - if not ok then - log.error(body) - statucode = 500 - sock:send(ERROR_RESPONSE(http, statucode, PATH, HEADER['X-Real-IP'] or ipaddr, HEADER['X-Forwarded-For'] or ipaddr, METHOD, now() - start)) - return sock:close() - end - if not body then - statucode = 404 - sock:send(ERROR_RESPONSE(http, statucode, PATH, HEADER['X-Real-IP'] or ipaddr, HEADER['X-Forwarded-For'] or ipaddr, METHOD, now() - start)) - return sock:close() - end - statucode = 200 - header[#header+1] = REQUEST_STATUCODE_RESPONSE(statucode) - local conten_type = REQUEST_MIME_RESPONSE(lower(file_type or '')) - if not conten_type then - header[#header+1] = 'Content-Disposition: attachment' -- 确保浏览器提示需要下载 - static = fmt('Content-Type: %s', 'application/octet-stream') - else - static = fmt('Content-Type: %s', conten_type) - end - end - header[#header+1] = HTTP_DATE() - header[#header+1] = 'Origin: *' - header[#header+1] = 'Allow: GET, POST, PUT, HEAD, OPTIONS' - header[#header+1] = 'Access-Control-Allow-Origin: *' - header[#header+1] = 'Access-Control-Allow-Headers: *' - header[#header+1] = 'Access-Control-Allow-Methods: GET, POST, PUT, HEAD, OPTIONS' - header[#header+1] = 'Access-Control-Max-Age: 86400' - header[#header+1] = 'server: ' .. (server or SERVER) - local Connection = 'Connection: keep-alive' - if not HEADER['Connection'] or lower(HEADER['Connection']) == 'close' then - Connection = 'Connection: close' - end - header[#header+1] = Connection - if body then - if type(body) == 'string' then - if #body >= 1 then - header[#header+1] = 'Transfer-Encoding: identity' - header[#header+1] = 'Content-Length: '.. #body - end - else - log.warn('response body not a string type.'..'('..tostring(body)..')') - body = '' - end - else - body = '' - end - if typ == HTTP_PROTOCOL.API then - if #body > 0 then - header[#header+1] = 'Content-Type: '..REQUEST_MIME_RESPONSE('json') - end - header[#header+1] = 'Cache-Control: no-cache, no-store, must-revalidate' - header[#header+1] = 'Cache-Control: no-cache' - elseif typ == HTTP_PROTOCOL.USE then - if #body > 0 then - header[#header+1] = concat({'Content-Type: ', REQUEST_MIME_RESPONSE('html'), ';charset=utf-8'}) - end - header[#header+1] = 'Cache-Control: no-cache, no-store, must-revalidate' - header[#header+1] = 'Cache-Control: no-cache' - else - if ttl then - header[#header+1] = HTTP_EXPIRES(time() + ttl) - end - header[#header+1] = static - end - sock:send(concat({concat(header, CRLF), CRLF2, body})) - http:tolog(statucode, PATH, HEADER['X-Real-IP'] or ipaddr, X_Forwarded_FORMAT(HEADER['X-Forwarded-For'] or ipaddr), METHOD, now() - start) - if statucode ~= 200 or Connection ~= 'Connection: keep-alive' then - return sock:close() - end - buffers = {} - end - if #buffers ~= 0 and #buffer > (max_header_size or 65535) then - sock:send(ERROR_RESPONSE(http, 431, PATH, HEADER['X-Real-IP'] or ipaddr, HEADER['X-Forwarded-For'] or ipaddr, METHOD, now() - start)) - return sock:close() - end - end -end - -return HTTP_PROTOCOL diff --git a/lualib/protocol/http/code.lua b/lualib/protocol/http/code.lua new file mode 100644 index 00000000..d59f3f6a --- /dev/null +++ b/lualib/protocol/http/code.lua @@ -0,0 +1,71 @@ +return { + + [100] = "HTTP/1.1 100 Continue", + [101] = "HTTP/1.1 101 Switching Protocol", + [102] = "HTTP/1.1 102 Processing", + + [200] = "HTTP/1.1 200 OK", + [201] = "HTTP/1.1 201 Created", + [202] = "HTTP/1.1 202 Accepted", + [203] = "HTTP/1.1 203 Non-Authoritative Information", + [204] = "HTTP/1.1 204 No Content", + [205] = "HTTP/1.1 205 Reset Content", + [206] = "HTTP/1.1 206 Partial Content", + [207] = "HTTP/1.1 207 Multi-Status", + [208] = "HTTP/1.1 208 Multi-Status", + [226] = "HTTP/1.1 226 IM Used", + + [300] = "HTTP/1.1 300 Multiple Choice", + [301] = "HTTP/1.1 301 Moved Permanently", + [302] = "HTTP/1.1 302 Found", + [303] = "HTTP/1.1 303 See Other", + [304] = "HTTP/1.1 304 Not Modified", + [305] = "HTTP/1.1 305 Use Proxy", + [306] = "HTTP/1.1 306 unused", + [307] = "HTTP/1.1 307 Temporary Redirect", + [308] = "HTTP/1.1 308 Permanent Redirect", + + [400] = "HTTP/1.1 400 Bad Request", + [401] = "HTTP/1.1 401 Unauthorized", + [402] = "HTTP/1.1 402 Payment Required", + [403] = "HTTP/1.1 403 Forbidden", + [404] = "HTTP/1.1 404 Not Found", + [405] = "HTTP/1.1 405 Method Not Allowed", + [406] = "HTTP/1.1 406 Not Acceptable", + [407] = "HTTP/1.1 407 Proxy Authentication Required", + [408] = "HTTP/1.1 408 Request Timeout", + [409] = "HTTP/1.1 409 Conflict", + + [410] = "HTTP/1.1 410 Gone", + [411] = "HTTP/1.1 411 Length Required", + [412] = "HTTP/1.1 412 Precondition Failed", + [413] = "HTTP/1.1 413 Payload Too Large", + [414] = "HTTP/1.1 414 URI Too Long", + [415] = "HTTP/1.1 415 Unsupported Media Type", + [416] = "HTTP/1.1 416 Requested Range Not Satisfiable", + [417] = "HTTP/1.1 417 Expectation Failed", + [418] = "HTTP/1.1 418 I'm a teapot", + + [421] = "HTTP/1.1 421 Misdirected Request", + [422] = "HTTP/1.1 422 Unprocessable Entity (WebDAV)", + [423] = "HTTP/1.1 423 Locked (WebDAV)", + [424] = "HTTP/1.1 424 Failed Dependency", + [426] = "HTTP/1.1 426 Upgrade Required", + [428] = "HTTP/1.1 428 Precondition Required", + [429] = "HTTP/1.1 429 Too Many Requests", + [431] = "HTTP/1.1 431 Request Header Fields Too Large", + [451] = "HTTP/1.1 451 Unavailable For Legal Reasons", + + [500] = "HTTP/1.1 500 Internal Server Error", + [501] = "HTTP/1.1 501 Not Implemented", + [502] = "HTTP/1.1 502 Bad Gateway", + [503] = "HTTP/1.1 503 Service Unavailable", + [504] = "HTTP/1.1 504 Gateway Timeout", + [505] = "HTTP/1.1 505 HTTP Version Not Supported", + [506] = "HTTP/1.1 506 Variant Also Negotiates", + [507] = "HTTP/1.1 507 Insufficient Storage", + [508] = "HTTP/1.1 508 Loop Detected (WebDAV)", + [510] = "HTTP/1.1 510 Not Extended", + [511] = "HTTP/1.1 511 Network Authentication Required", + +} diff --git a/lualib/protocol/http/init.lua b/lualib/protocol/http/init.lua new file mode 100644 index 00000000..617313dd --- /dev/null +++ b/lualib/protocol/http/init.lua @@ -0,0 +1,659 @@ +local log = require "logging" +local tcp = require "internal.TCP" +local wsserver = require "protocol.websocket.server" + +local Log = log:new({ dump = true, path = 'protocol-http'}) + +local crypt = require "crypt" +local sha1 = crypt.sha1 +local base64encode = crypt.base64encode + +local sys = require "sys" +local now = sys.now +local new_tab = sys.new_tab + +local lz = require "lz" +local decompress = lz.compress +local gzcompress = lz.gzcompress + +-- 如果有安装lua-br, 可以优先使用支持的Brotli算法. +local brcompress +local ok, br = pcall(require, "lbr") +if ok and type(br) == "table" and type(br.compress) == "function" then + brcompress = br.compress +end + +local form = require "httpd.Form" +local FILE_TYPE = form.FILE +local ARGS_TYPE = form.ARGS +local form_multipart = form.multipart +local form_urlencode = form.urlencode +local form_argsencode = form.get_args + +local Cookie = require "httpd.Cookie" +local clCookie = Cookie.clean -- 清理 +local secCookie = Cookie.setSecure -- 设置Cookie加密字段 +local seCookie = Cookie.serialization -- 序列化 +local deCookie = Cookie.deserialization -- 反序列化 + +local null = null +local type = type +local tostring = tostring +local next = next +local xpcall = xpcall +local pairs = pairs +local ipairs = ipairs +local lower = string.lower +local match = string.match +local fmt = string.format +local toint = math.tointeger +local find = string.find +local split = string.sub +local upper = string.upper + +local DATE = os.date +local time = os.time + +local concat = table.concat +local insert = table.insert + +local CRLF = '\x0d\x0a' +local CRLF2 = '\x0d\x0a\x0d\x0a' +local RE_CRLF2 = '[\x0d]?\x0a[\x0d]?\x0a' +local COMMA = '\x2c' + +local SERVER = 'cf web/0.1' + +local HTTP_CODE = require "protocol.http.code" +local PAGES = require "protocol.http.pages" +local MIME = require "protocol.http.mime" + +local HTTP_PARSER = require "protocol.http.parser" +local PARSER_HTTP_REQUEST = HTTP_PARSER.PARSER_HTTP_REQUEST +local PARSER_HTTP_RESPONSE = HTTP_PARSER.PARSER_HTTP_RESPONSE +local RESPONSE_CHUNKED_PARSER = HTTP_PARSER.RESPONSE_CHUNKED_PARSER + +-- OPCODE +local OPCODE_RESP = -128 +local OPCODE_THROW = -256 +local OPCODE_REDIRECT = -65536 + +local HTTP_PROTOCOL = { + API = 1, + [1] = "API", + USE = 2, + [2] = "USE", + STATIC = 3, + [3] = "STATIC", + WS = 4, + [4] = "WS", + PARSER_HTTP_REQUEST = PARSER_HTTP_REQUEST, + PARSER_HTTP_RESPONSE = PARSER_HTTP_RESPONSE, + RESPONSE_CHUNKED_PARSER = RESPONSE_CHUNKED_PARSER, +} + +-- 以下为 HTTP Server 所需所用方法 +local function REQUEST_STATUCODE_RESPONSE(code) + return HTTP_CODE[code] or "attempt to passed a invalid code to response message." +end + +local function REQUEST_MIME_RESPONSE(mime) + return MIME[mime] +end + +function HTTP_PROTOCOL.FILEMIME(mime) + return MIME[mime] +end + +local function HTTP_DATE() + return DATE("Date: %a, %d %b %Y %X GMT") + -- return os.date("Date: %a, %d %b %Y %X GMT") +end + +local function HTTP_EXPIRES(timestamp) + return DATE("Expires: %a, %d %b %Y %X GMT", timestamp) + -- return os.date("Date: %a, %d %b %Y %X GMT") +end + +local function req_time(ts) + return now() - (ts or now()) +end + +local tab_copy +tab_copy = function (src) + local dst = new_tab(0, 32) + for k, v in pairs(src) do + dst[k] = type(v) == 'table' and tab_copy(v) or v + end + return dst +end + +-- 追踪调用栈信息 +local function trace (msg) + return debug.traceback(coroutine.running(), msg, 2) +end + +-- 安全运行回调函数 +local function safe_call (f, ...) + local ok, r1, r2, r3, r4, t5 = xpcall(f, trace, ...) + return ok, r1, r2, r3, r4, t5 +end + +local function readall(sock, bsize, buffers) + local sock_recv = sock.recv + while 1 do + local buffer = sock_recv(sock, bsize) + if not buffer then + return + end + bsize = bsize - #buffer + insert(buffers, buffer) + if bsize == 0 then + break + end + end + return true +end + +local function cros_append(header, timeout) + insert(header, 'Access-Control-Allow-Origin: *') + insert(header, 'Access-Control-Allow-Headers: *') + insert(header, 'Access-Control-Allow-Methods: GET, POST, PUT, HEAD, OPTIONS') + insert(header, 'Access-Control-Allow-Credentials: true') + insert(header, 'Access-Control-Max-Age: ' .. (timeout or 86400)) +end + +local AllowMethod = { + GET = true, + POST = true, + DELETE = true, + PUT = true, + PATCH = true, +} + +local function PASER_METHOD(http, sock, max_body_size, buffer, METHOD, PATH, HEADER) + local body_len = toint(HEADER['Content-Length'] or HEADER['content-length']) + local BODY + if body_len and body_len > 0 then + if body_len >= (max_body_size or (1024 * 1024)) then + return nil, 413 + end + local buffers = new_tab(16, 0) + local bsize = body_len + local _, CRLF_END = find(buffer, RE_CRLF2) + if #buffer > CRLF_END then + bsize = bsize - (#buffer - CRLF_END) + buffers[#buffers+1] = split(buffer, CRLF_END + 1) + end + if bsize > 0 and not readall(sock, bsize, buffers) then + return nil, null + end + BODY = concat(buffers) + end + + local FILE_ENCODE = 'multipart/form-data' + local XML_ENCODE_1 = 'text/xml' + local XML_ENCODE_2 = 'application/xml' + local JSON_ENCODE = 'application/json' + local URL_ENCODE = 'application/x-www-form-urlencoded' + local format = match(HEADER['Content-Type'] or HEADER['content-type'] or '', '(.-/[^;]*)') + if not AllowMethod[upper(METHOD)] then + return true + end + + local content = new_tab(0, 16) + if format == JSON_ENCODE then + content['json'] = true + elseif format == XML_ENCODE_1 or format == XML_ENCODE_2 then + content['xml'] = true + elseif format == FILE_ENCODE then + if format == FILE_ENCODE then + local BOUNDARY = match(HEADER['Content-Type'] or HEADER['content-type'] or '', '^.+=[%-]*(.+)') + if BOUNDARY and BOUNDARY ~= '' then + local files, formargs = form_multipart(BODY, BOUNDARY) + if files then + content['files'] = files + end + if formargs then + content['formargs'] = {} + for _, args in ipairs(formargs) do + content['formargs'][args[1]] = args[2] + end + end + end + end + end + + local spl_pos = find(PATH, '%?') + local queryParams = form_argsencode(PATH) + if spl_pos and spl_pos < #PATH then + content['query'] = queryParams + end + if METHOD == "GET" or METHOD == "DELETE" then + content['args'] = queryParams + elseif METHOD == "POST" then + if format == FILE_ENCODE then + content['args'] = content['formargs'] + elseif format == URL_ENCODE then + content['args'] = form_urlencode(BODY) + end + end + content['body'] = BODY + return true, content +end + +local function X_Forwarded_FORMAT(ip_list) + if find(ip_list, COMMA) then + return ip_list:gsub(COMMA, " ->") + end + return ip_list +end +-- 一些错误返回 +local function ERROR_RESPONSE(http, code, path, ip, forword, method, speed) + http:tolog(code, path, ip, X_Forwarded_FORMAT(forword) or ip, method, speed) + local response = { + REQUEST_STATUCODE_RESPONSE(code), + HTTP_DATE(), + 'Connection: keep-alive', + 'Server: ' .. (http.__server or SERVER), + } + local error_page = PAGES[code] + if error_page and http.__enable_error_pages then + insert(response, 'Content-Length: ' .. #error_page) + return concat({concat(response, CRLF), error_page}, CRLF2) + else + insert(response, 'Content-Length: 0') + return concat({concat(response, CRLF), CRLF2}) + end +end + +-- WebSocket +local function Switch_Protocol(http, cls, sock, header, method, version, path, ip, start_time) + if method ~= 'GET' then + return sock:send(ERROR_RESPONSE(http, 400, path, ip, header['X-Forwarded-For'] or ip, method, req_time(start_time))) + end + if version ~= 1.1 then + return sock:send(ERROR_RESPONSE(http, 400, path, ip, header['X-Forwarded-For'] or ip, method, req_time(start_time))) + end + if not header['Upgrade'] or lower(header['Upgrade']) ~= 'websocket' then + return sock:send(ERROR_RESPONSE(http, 401, path, ip, header['X-Forwarded-For'] or ip, method, req_time(start_time))) + end + if header['Sec-WebSocket-Version'] ~= '13' then + return sock:send(ERROR_RESPONSE(http, 403, path, ip, header['X-Forwarded-For'] or ip, method, req_time(start_time))) + end + local sec_key = header['Sec-WebSocket-Key'] + if not sec_key or sec_key == '' then + return sock:send(ERROR_RESPONSE(http, 505, path, ip, header['X-Forwarded-For'] or ip, method, req_time(start_time))) + end + local response = { + REQUEST_STATUCODE_RESPONSE(101), + HTTP_DATE(), + 'Server: '..(http.__server or SERVER), + 'Upgrade: WebSocket', + 'Connection: Upgrade', + 'Sec-WebSocket-Accept: '..base64encode(sha1(sec_key..'258EAFA5-E914-47DA-95CA-C5AB0DC85B11')) + } + local protocol = header['Sec-Websocket-Protocol'] + if protocol then -- 仅支持协议回传 + response[#response+1] = "Sec-Websocket-Protocol: "..tostring(protocol) + end + local ext = nil + local extension = header['Sec-WebSocket-Extensions'] + if type(extension) == 'string' and extension ~= '' then + if find(extension, "permessage%-deflate") then + response[#response+1] = "Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits=15; server_no_context_takeover; client_no_context_takeover" + ext = "deflate" + elseif find(extension, "x%-webkit%-deflate%-frame") then + response[#response+1] = "Sec-WebSocket-Extensions: x-webkit-deflate-frame; no_context_takeover" + ext = "deflate" + end + end + -- require "utils" + -- var_dump(header) + if not sock:send(concat(response, CRLF)..CRLF2) then + return + end + http:tolog(101, path, header['X-Real-IP'] or ip, X_Forwarded_FORMAT(header['X-Forwarded-For'] or ip), method, req_time(start_time)) + return wsserver.start(sock, cls, form_argsencode(path), header, ext) +end + +local function send_header (sock, header) + header[#header+1] = CRLF + return sock:send(concat(header, CRLF)) +end + +local function send_body (sock, body, filepath) + if not body then + return sock:sendfile(filepath, 65535) + end + return sock:send(body) +end + +function HTTP_PROTOCOL.DISPATCH(sock, opt, http) + local buffers = {} + local ttl = http.ttl + local server = http.__server or SERVER + local enable_cookie = http.__enable_cookie + local enable_gzip = http.__enable_gzip + local compress_bytes = http.__compress_bytes + local enable_cros_timeout = http.__enable_cros_timeout + local cookie_secure = http.__cookie_secure + local before_func = http.__before_func + local max_path_size = http.__max_path_size + local max_header_size = http.__max_header_size + local max_body_size = http.__max_body_size + local http_router = http.router + local route_find = http_router.find + local tolog = http.tolog + local ipaddr = opt.ipaddr + local port = opt.port + secCookie(cookie_secure) -- 如果需要 + while 1 do + local buf = sock:recv(8192) + if not buf then + return sock:close() + end + buffers[#buffers+1] = buf + local buffer = concat(buffers) + local CRLF_START, CRLF_END = find(buffer, RE_CRLF2) + if CRLF_START and CRLF_END then + local start = now() + -- 协议有问题返回400 + local METHOD, PATH, VERSION, HEADER = PARSER_HTTP_REQUEST(buffer) + if not METHOD or not PATH or not VERSION then + sock:send(ERROR_RESPONSE(http, 400, PATH, HEADER and HEADER['X-Real-IP'] or ipaddr, HEADER and HEADER['X-Forwarded-For'] or ipaddr, METHOD or "GET", req_time(start))) + return sock:close() + end + -- 超过自定义最大PATH长度限制 + if PATH and #PATH > (max_path_size or 1024) then + sock:send(ERROR_RESPONSE(http, 414, PATH, HEADER and HEADER['X-Real-IP'] or ipaddr, HEADER and HEADER['X-Forwarded-For'] or ipaddr, METHOD, req_time(start))) + return sock:close() + end + -- 没有HEADER返回400 + if not HEADER or not next(HEADER) then + sock:send(ERROR_RESPONSE(http, 400, PATH, HEADER and HEADER['X-Real-IP'] or ipaddr, HEADER and HEADER['X-Forwarded-For'] or ipaddr, METHOD, req_time(start))) + return sock:close() + end + -- 超过自定义最大HEADER长度限制 + if #buffer - CRLF_START > (max_header_size or 65535) then + sock:send(ERROR_RESPONSE(http, 431, PATH, HEADER and HEADER['X-Real-IP'] or ipaddr, HEADER and HEADER['X-Forwarded-For'] or ipaddr, METHOD, req_time(start))) + return sock:close() + end + -- 这里根据PATH先查找路由, 如果没有直接返回404. + -- local cls, typ, rest_args = http_router:find(METHOD, PATH) + local cls, typ, rest_args = route_find(http_router, METHOD, PATH) + if not cls or not typ then + sock:send(ERROR_RESPONSE(http, 404, PATH, HEADER['X-Real-IP'] or ipaddr, HEADER['X-Forwarded-For'] or ipaddr, METHOD, req_time(start))) + return sock:close() + end + -- 根据请求方法进行解析, 解析失败返回501 + local content + ok, content = PASER_METHOD(http, sock, max_body_size, buffer, METHOD, PATH, HEADER) + if not ok then + if content == 413 then + sock:send(ERROR_RESPONSE(http, 413, PATH, HEADER['X-Real-IP'] or ipaddr, HEADER['X-Forwarded-For'] or ipaddr, METHOD, req_time(start))) + elseif content ~= null then + sock:send(ERROR_RESPONSE(http, 501, PATH, HEADER['X-Real-IP'] or ipaddr, HEADER['X-Forwarded-For'] or ipaddr, METHOD, req_time(start))) + else -- 断开连接 + return sock:close() + end + goto CONTINUE + end + -- 如果请求使用了 HEAD 与 OPTIONS 方法, 这里会根据配置检查是否需要返回跨域标识. (除非您手动设置请求头部, 否则一般不会遇到此处逻辑.) + -- 值得一提的是: 由于框架不支持范围请求(Accept-Ranges), 所以目前的处理方式将HEAD与OPTIONS都将返回0. 这样可以有助于快速完成检查请求. + if not content then + tolog(http, 200, PATH, HEADER['X-Real-IP'] or ipaddr, X_Forwarded_FORMAT(HEADER['X-Forwarded-For'] or ipaddr), METHOD, req_time(start)) + local res = new_tab(16, 0) + res[#res+1] = REQUEST_STATUCODE_RESPONSE(200) + res[#res+1] = HTTP_DATE() + if enable_cros_timeout then + cros_append(res, enable_cros_timeout) + end + res[#res+1] = 'Server: ' .. server + res[#res+1] = 'Connection: keep-alive' + res[#res+1] = 'Content-Length: 0' + sock:send(concat(res, CRLF)..CRLF2) + goto CONTINUE + end + content['ROUTE'] = HTTP_PROTOCOL[typ] + content['method'], content['path'], content['headers'], content['client_ip'], content['client_port'] = METHOD, PATH, HEADER, ipaddr, port + -- before 函数只影响接口与view + if before_func and (typ == HTTP_PROTOCOL.API or typ == HTTP_PROTOCOL.USE) then + local code, data + ok, code, data = safe_call(before_func, tab_copy(content)) + if not ok then -- before 函数执行出错 + Log:ERROR(code) + sock:send(ERROR_RESPONSE(http, 500, PATH, HEADER['X-Real-IP'] or ipaddr, HEADER['X-Forwarded-For'] or ipaddr, METHOD, req_time(start))) + goto CONTINUE + end + if type(code) == "number" then + if code == 301 or code == 302 or code == 303 or code == 307 or code == 308 then + tolog(http, code, PATH, HEADER['X-Real-IP'] or ipaddr, X_Forwarded_FORMAT(HEADER['X-Forwarded-For'] or ipaddr), METHOD, req_time(start)) + sock:send(concat({ + REQUEST_STATUCODE_RESPONSE(code), HTTP_DATE(), + 'Connection: keep-alive', + 'Server: ' .. server, + 'Content-Length: 0', + 'Location: ' .. (data or "https://cfadmin.cn/"), + CRLF + }, CRLF)) + goto CONTINUE + elseif code >= 400 and code < 600 then + if type(data) == 'string' and data ~= '' then + tolog(http, code, PATH, HEADER['X-Real-IP'] or ipaddr, X_Forwarded_FORMAT(HEADER['X-Forwarded-For'] or ipaddr), METHOD, req_time(start)) + sock:send(concat({concat({ + REQUEST_STATUCODE_RESPONSE(code), HTTP_DATE(), + 'Server: ' .. server, + 'Connection: keep-alive', + 'Content-Type: ' .. (typ == HTTP_PROTOCOL.API and REQUEST_MIME_RESPONSE('json') or REQUEST_MIME_RESPONSE('html')), + 'Content-Length: ' .. tostring(#data), + }, CRLF), CRLF2, data})) + goto CONTINUE + end + sock:send(ERROR_RESPONSE(http, code, PATH, HEADER['X-Real-IP'] or ipaddr, HEADER['X-Forwarded-For'] or ipaddr, METHOD, req_time(start))) + goto CONTINUE + elseif code ~= 200 then + Log:ERROR("before function: Illegal return value") + sock:send(ERROR_RESPONSE(http, 500, PATH, HEADER['X-Real-IP'] or ipaddr, HEADER['X-Forwarded-For'] or ipaddr, METHOD, req_time(start))) + goto CONTINUE + end + else + sock:send(ERROR_RESPONSE(http, 401, PATH, HEADER['X-Real-IP'] or ipaddr, HEADER['X-Forwarded-For'] or ipaddr, METHOD, req_time(start))) + goto CONTINUE + end + end + + local header = new_tab(16, 0) + local body, body_len, filepath, static, statucode + + if typ == HTTP_PROTOCOL.API or typ == HTTP_PROTOCOL.USE then + -- 如果httpd开启了记录Cookie字段, 则每次尝试是否deCookie + if enable_cookie then + deCookie(HEADER["Cookie"] or HEADER["cookie"]) + end + if http_router.enable_rest then + if content['query'] then + -- 原则上说,不应该出现 /rest/{id}?id=1 这种设计 + content['query'] = table.rmerge(content['query'], rest_args) + else + content['query'] = rest_args + end + end + if type(cls) == "table" then + local method = cls[lower(METHOD)] + if not method or type(method) ~= 'function' then -- 注册的路由未实现这个方法 + sock:send(ERROR_RESPONSE(http, 405, PATH, HEADER['X-Real-IP'] or ipaddr, HEADER['X-Forwarded-For'] or ipaddr, METHOD, req_time(start))) + goto CONTINUE + end + local c = cls:new(tab_copy(content)) + ok, body = safe_call(method, c) + else + ok, body = safe_call(cls, tab_copy(content)) + end + -- 如果httpd开启了记录Cookie字段, 则每次尝试是否需要seCookie + if enable_cookie then + local Cookies = seCookie() + for _, Cookie in ipairs(Cookies) do + header[#header+1] = Cookie + end + clCookie() + end + if not ok then + Log:ERROR(body or "empty response.") + sock:send(ERROR_RESPONSE(http, 500, PATH, HEADER['X-Real-IP'] or ipaddr, HEADER['X-Forwarded-For'] or ipaddr, METHOD, req_time(start))) + goto CONTINUE + end + statucode = 200 + -- 开发者主动`抛出异常`与`重定向`的时候需要特殊处理. + if type(body) == "table" and body.__OPCODE__ then + statucode = body.__CODE__ + local opcode = body.__OPCODE__ + if opcode == OPCODE_THROW then -- `异常`构造器 + tolog(http, statucode, PATH, HEADER['X-Real-IP'] or ipaddr, X_Forwarded_FORMAT(HEADER['X-Forwarded-For'] or ipaddr), METHOD, req_time(start)) + if not send_header(sock, { REQUEST_STATUCODE_RESPONSE(statucode), HTTP_DATE(), 'Server: ' .. server, 'Connection: keep-alive', 'Content-Length: ' .. toint(#body.__MSG__), 'Content-Type: ' .. (typ == HTTP_PROTOCOL.API and REQUEST_MIME_RESPONSE('json') or REQUEST_MIME_RESPONSE('html'))}) or not send_body(sock, body.__MSG__) then + return sock:close() + end + -- goto CONTINUE + elseif opcode == OPCODE_RESP then -- `响应`构造器 + local resp = new_tab(16, 0) + insert(resp, REQUEST_STATUCODE_RESPONSE(statucode)) + insert(resp, HTTP_DATE()) + insert(resp, 'Server: ' .. server) + insert(resp, 'Connection: keep-alive') + insert(resp, 'Content-Length: ' .. (toint(body.__FILESIZE__) or toint(#body.__MSG__))) + insert(resp, 'Content-Type: ' .. (body.__TYPE__ or body.__FILETYPE__ or "application/octet-stream")) + if body.__FILETYPE__ then + insert(resp, fmt('Content-Disposition: %s; filename="%s"', body.__FILEINLINE__ and "inline" or "attachment", body.__FILENAME__:match("[/]?([^/]+)$"))) + end + tolog(http, statucode, PATH, HEADER['X-Real-IP'] or ipaddr, X_Forwarded_FORMAT(HEADER['X-Forwarded-For'] or ipaddr), METHOD, req_time(start)) + if not send_header(sock, resp) or not send_body(sock, body.__MSG__, body.__FILENAME__) then + return sock:close() + end + -- goto CONTINUE + elseif opcode == OPCODE_REDIRECT then -- `跳转`构造器 + tolog(http, statucode, PATH, HEADER['X-Real-IP'] or ipaddr, X_Forwarded_FORMAT(HEADER['X-Forwarded-For'] or ipaddr), METHOD, req_time(start)) + send_header(sock, { REQUEST_STATUCODE_RESPONSE(statucode), HTTP_DATE(), 'Server: ' .. server, 'Connection: keep-alive', 'Content-Length: 0', 'Location: ' .. body.__MSG__}) + end + return sock:close() + end + insert(header, 1, REQUEST_STATUCODE_RESPONSE(statucode)) + elseif typ == HTTP_PROTOCOL.WS then + local ok, msg = safe_call(Switch_Protocol, http, cls, sock, tab_copy(HEADER), METHOD, VERSION, PATH, HEADER['X-Real-IP'] or ipaddr, start) + if not ok then + Log:ERROR(msg) + end + return sock:close() + else + local filetype + local path = PATH + local pos, _ = find(PATH, '%?') + if pos then + path = split(PATH, 1, pos - 1) + end + body_len, filepath, filetype = cls(path) + if not body_len then + statucode = 404 + sock:send(ERROR_RESPONSE(http, statucode, PATH, HEADER['X-Real-IP'] or ipaddr, HEADER['X-Forwarded-For'] or ipaddr, METHOD, req_time(start))) + return sock:close() + end + statucode = 200 + header[#header+1] = REQUEST_STATUCODE_RESPONSE(statucode) + local conten_type = REQUEST_MIME_RESPONSE(lower(filetype or '')) + if type(conten_type) ~= 'string' then + -- 确保浏览器提示需要下载 + local s, e = find(filepath, "/[^/]+$") + header[#header+1] = fmt('Content-Disposition: attachment; filename="%s"', filepath:sub(s + 1, e)) + static = fmt('Content-Type: %s', type(conten_type) ~= "table" and "application/octet-stream" or conten_type.type) + else + -- 确保内容展示在浏览器内 + header[#header+1] = 'Content-Disposition: inline' + static = fmt('Content-Type: %s; charset=utf-8', conten_type) + end + end + header[#header+1] = HTTP_DATE() + header[#header+1] = 'Accept-Ranges: none' + header[#header+1] = 'Server: ' .. server + local Connection = 'Connection: keep-alive' + local keepalive = HEADER['Connection'] or HEADER['connection'] + if not keepalive or lower(keepalive) == 'close' then + Connection = 'Connection: close' + end + header[#header+1] = Connection + if typ == HTTP_PROTOCOL.API or typ == HTTP_PROTOCOL.USE then + if typ == HTTP_PROTOCOL.API then + header[#header+1] = "Content-Type: application/json; charset=utf-8" + else + header[#header+1] = "Content-Type: text/html; charset=utf-8" + end + if type(body) ~= 'string' or body == '' then + Log:ERROR("Response Error ["..(split(PATH , 1, (find(PATH, '?') or 0 ) - 1)).."]: response must be a string and not empty.") + sock:send(ERROR_RESPONSE(http, 500, PATH, HEADER['X-Real-IP'] or ipaddr, HEADER['X-Forwarded-For'] or ipaddr, METHOD, req_time(start))) + goto CONTINUE + end + local accept_encoding = HEADER['Accept-Encoding'] or HEADER['accept-encoding'] + if type(accept_encoding) == 'string' and enable_gzip and #body >= (toint(compress_bytes) or 128) then + local ac = lower(accept_encoding) + -- 压缩选择优先br,其次使用gzip, 最后使用deflate, 如果都没有则使用原始字符串. + if brcompress and find(ac, "br") then + local compress_body = brcompress(body) + if compress_body then + header[#header+1] = 'Content-Encoding: br' + body = compress_body + end + elseif find(ac, "gzip") then + local compress_body = gzcompress(body) + if compress_body then + header[#header+1] = 'Content-Encoding: gzip' + body = compress_body + end + elseif find(ac, "deflate") then + local compress_body = decompress(body) + if compress_body then + header[#header+1] = 'Content-Encoding: deflate' + body = compress_body + end + end + end + header[#header+1] = 'Content-Length: ' .. #body + header[#header+1] = 'Cache-Control: no-cache, no-store, must-revalidate' + else + if ttl then + header[#header+1] = HTTP_EXPIRES(time() + ttl) + end + if body_len then + header[#header+1] = 'Content-Length: '.. toint(body_len) + end + header[#header+1] = static + end + -- 如果启用了跨域, 则开启头部支持. + if enable_cros_timeout then + cros_append(header, enable_cros_timeout) + end + -- 不计算数据传输时间, 仅计算实际回调处理所用时间. + tolog(http, statucode, PATH, HEADER['X-Real-IP'] or ipaddr, X_Forwarded_FORMAT(HEADER['X-Forwarded-For'] or ipaddr), METHOD, req_time(start)) + -- 根据实际情况分块发送 + if not send_header(sock, header) or not send_body(sock, body, filepath) then + return sock:close() + end + if statucode ~= 200 or Connection ~= 'Connection: keep-alive' then + return sock:close() + end + buffers = {} + end + if #buffers ~= 0 and #buffer > (max_header_size or 65535) then + -- sock:send(ERROR_RESPONSE(http, 431, PATH, HEADER['X-Real-IP'] or ipaddr, HEADER['X-Forwarded-For'] or ipaddr, METHOD, req_time(start))) + return sock:close() + end + -- 大部分情况下不需要主动关闭TCP连接, 这样有利于减少负载均衡器对连接池频繁销毁与建立. + :: CONTINUE :: + end +end + +function HTTP_PROTOCOL.RAW_DISPATCH(s, opt, http) + if type(s) == 'table' then + return HTTP_PROTOCOL.DISPATCH(s, opt, http) + end + return HTTP_PROTOCOL.DISPATCH(tcp:new():set_fd(s):timeout(http.__timeout), opt, http) +end + +return HTTP_PROTOCOL \ No newline at end of file diff --git a/lualib/protocol/http/mime.lua b/lualib/protocol/http/mime.lua new file mode 100644 index 00000000..d4af15fe --- /dev/null +++ b/lualib/protocol/http/mime.lua @@ -0,0 +1,98 @@ +return { + -- 文本格式 + ['xml'] = 'application/xml', + ['htm'] = 'text/html', + ['html'] = 'text/html', + ['shtml'] = 'text/html', + ['mml'] = 'text/mathml', + ['xhtml'] = 'application/xhtml+xml', + ['txt'] = 'text/plain', + ['htc'] = 'text/x-component', + ['wml'] = 'text/vnd.wap.wml', + ['css'] = 'text/css', + ['rss'] = 'application/rss+xml', + ['json'] = 'application/json', + ['rtf'] = 'application/rtf', + ['atom'] = 'application/atom+xml', + ['ogx'] = 'application/ogg', + -- 图片格式 + ['bmp'] = 'image/bmp', + ['png'] = 'image/png', + ['gif'] = 'image/gif', + ['jpeg'] = 'image/jpeg', + ['jpg'] = 'image/jpeg', + ['ico'] = 'image/x-icon', + ['tif'] = 'image/tiff', + ['tiff'] = 'image/tiff', + ['svg'] = 'image/svg+xml', + -- 音频 + ['au'] = 'audio/basic', + ['wav'] = 'audio/wav', + ['acc'] = 'audio/aac', + ['mp3'] = 'audio/mpeg', + ['oga'] = 'audio/ogg', + ['kar'] = 'audio/midi', + ['mid'] = 'audio/midi', + ['midi'] = 'audio/midi', + ['m4a'] = 'audio/x-m4a', + ['ra'] = 'audio/x-realaudio', + -- 视频 + ['avi'] = { type = 'video/x-msvideo' }, + ['mpa'] = { type = 'video/mpeg' }, + ['mpe'] = { type = 'video/mpeg'}, + ['mp2'] = { type = 'video/mpeg' }, + ['mpeg'] = { type = 'video/mpeg' }, + ['mp4'] = { type = 'audio/mp4' }, + ['qt'] = { type = 'video/mpeg' }, + ['mov'] = { type = 'video/mpeg' }, + ['webm'] = { type = 'video/webm' }, + ['flv'] = { type = 'video/x-flv' }, + ['m4v'] = { type = 'video/x-m4v' }, + ['ts'] = { type = 'video/mp2t' }, + ['ogv'] = { type = 'video/ogg' }, + -- 文档 + ['csv'] = { type = 'text/csv'}, + ['dot'] = { type = 'application/msword' }, + ['doc'] = { type = 'application/msword' }, + ['mdb'] = { type = 'application/x-msaccess' }, + ['xls'] = { type = 'application/vnd.ms-excel' }, + ['ppt'] = { type = 'application/vnd.ms-powerpoint' }, + ['chm'] = { type = 'application/vnd.ms-htmlhelp' }, + ['xlsx'] = { type = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }, + ['docx'] = { type = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' }, + ['pptx'] = { type = 'application/vnd.openxmlformats-officedocument.presentationml.presentation' }, + ['pdf'] = 'application/pdf', + -- 密匙 + ['der'] = 'application/x-x509-ca-cert', + ['cer'] = 'application/x-x509-ca-cert', + ['pem'] = 'application/x-x509-ca-cert', + ['crt'] = 'application/x-x509-ca-cert', + ['p10'] = 'application/pkcs10', + ['p12'] = 'application/x-pkcs12', + ['pfx'] = 'application/x-pkcs12', + ['p7c'] = 'application/x-pkcs7-mime', + ['p7m'] = 'application/x-pkcs7-mime', + ['p7b'] = 'application/x-pkcs7-certificates', + ['spc'] = 'application/x-pkcs7-certificates', + ['p7r'] = 'application/x-pkcs7-certreqresp', + ['p7s'] = 'application/x-pkcs7-signature', + -- 压缩文件 + ['7z'] = { type = 'application/x-7z-compressed' }, + ['zip'] = { type = 'application/zip' }, + ['gz'] = { type = 'application/x-gzip' }, + ['tar'] = { type = 'application/x-tar' }, + ['gtar'] = { type = 'application/x-gtar' }, + ['tgz'] = { type = 'application/x-compressed' }, + ['rar'] = { type = 'application/x-rar-compressed' }, + -- 源文件 + ['c'] = 'text/plain', + ['cxx'] = 'text/plain', + ['cpp'] = 'text/plain', + ['h'] = 'text/plain', + ['hpp'] = 'text/plain', + ['py'] = 'text/plain', + ['cs'] = 'text/plain', + ['lua'] = 'text/plain', + ['js'] = 'application/javascript', + -- TODO +} \ No newline at end of file diff --git a/lualib/protocol/http/pages.lua b/lualib/protocol/http/pages.lua new file mode 100644 index 00000000..74fe1383 --- /dev/null +++ b/lualib/protocol/http/pages.lua @@ -0,0 +1,788 @@ +local pages = {} + +pages[400] = [[ + + + + + 400 Bad Request + + + +
      +

      + 4 + 0 + 0 +

      +

      Bad Request

      +
      + + +]] + +pages[401] = [[ + + + + + 401 Unauthorized + + + +
      +

      + 4 + 0 + 1 +

      +

      Unauthorized

      +
      + + +]] + +pages[403] = [[ + + + + + 403 Forbidden + + + +
      +

      + 4 + 0 + 3 +

      +

      Forbidden

      +
      + + +]] + +pages[404] = [[ + + + + + 404 Page Not Found + + + +
      +

      + 4 + 0 + 4 +

      +

      Page Not Found

      +
      + + +]] + +pages[413] = [[ + + + + + 413 Payload Too Large + + + +
      +

      + 4 + 1 + 3 +

      +

      Payload Too Large

      +
      + + +]] + +pages[431] = [[ + + + + + 431 Request Header Fields Too Large + + + +
      +

      + 4 + 3 + 1 +

      +

      Request Header Fields Too Large

      +
      + + +]] + +pages[500] = [[ + + + + + 500 Internal Server Error + + + +
      +

      + 5 + 0 + 0 +

      +

      Internal Server Error

      +
      + + +]] + +pages[501] = [[ + + + + + 501 Not Implemented + + + +
      +

      + 5 + 0 + 1 +

      +

      Not Implemented

      +
      + + +]] + +pages[505] = [[ + + + + + 505 HTTP Version Not Supported + + + +
      +

      + 5 + 0 + 5 +

      +

      HTTP Version Not Supported

      +
      + + +]] + + + +return pages diff --git a/lualib/protocol/http/parser.lua b/lualib/protocol/http/parser.lua new file mode 100644 index 00000000..ec415a41 --- /dev/null +++ b/lualib/protocol/http/parser.lua @@ -0,0 +1,36 @@ +local httpparser = require "lhttpparser" +local PARSER_HTTP_REQUEST = httpparser.parser_http_request +local PARSER_HTTP_RESPONSE = httpparser.parser_http_response +local RESPONSE_CHUNKED_PARSER = httpparser.parser_response_chunked + +local pcall = pcall + +local http_parser = {} + +function http_parser.PARSER_HTTP_REQUEST (buffer) + local ok, method, path, version, header = pcall(PARSER_HTTP_REQUEST, buffer) + if not ok then + return nil + end + return method, path, version, header +end + +-- 解析http回应 +function http_parser.PARSER_HTTP_RESPONSE (buffer) + local ok, version, code, status, header = pcall(PARSER_HTTP_RESPONSE, buffer) + if not ok then + return nil + end + return version, code, status, header +end + +-- 解析回应chunked +function http_parser.RESPONSE_CHUNKED_PARSER (data) + local ok, data, pos = pcall(RESPONSE_CHUNKED_PARSER, data) + if not ok then + return nil, -1 + end + return data, pos +end + +return http_parser \ No newline at end of file diff --git a/lualib/protocol/http/ua.lua b/lualib/protocol/http/ua.lua new file mode 100644 index 00000000..d14736cc --- /dev/null +++ b/lualib/protocol/http/ua.lua @@ -0,0 +1,62 @@ +local random = math.random + +local ua = { + -- Windows + "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:76.0) Gecko/20100101 Firefox/76.0", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:77.0) Gecko/20100101 Firefox/77.0", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:80.0) Gecko/20100101 Firefox/80.0", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:82.0) Gecko/20100101 Firefox/82.0", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:83.0) Gecko/20100101 Firefox/83.0", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36 LBBROWSER", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3314.0 Safari/537.36 SE 2.X MetaSr 1.0", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3756.400 QQBrowser/10.5.4039.400", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 UBrowser/6.2.4098.3 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3947.100 Safari/537.36 2345Explorer/10.7.0.20186", + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36 Maxthon/5.3.8.2000", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36 OPR/68.0.3618.63", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.66", + -- Apple + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:86.0) Gecko/20100101 Firefox/86.0", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:85.0) Gecko/20100101 Firefox/85.0", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:84.0) Gecko/20100101 Firefox/84.0", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:83.0) Gecko/20100101 Firefox/83.0", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:82.0) Gecko/20100101 Firefox/82.0", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:81.0) Gecko/20100101 Firefox/81.0", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:80.0) Gecko/20100101 Firefox/80.0", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:79.0) Gecko/20100101 Firefox/79.0", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:78.0) Gecko/20100101 Firefox/78.0", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:77.0) Gecko/20100101 Firefox/77.0", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.88 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.87 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.86 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.2 Safari/605.1.15 QQBrowserLite/1.2.9", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.1 Safari/605.1.15 QQBrowserLite/1.2.7", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.0 Safari/605.1.15 QQBrowserLite/1.2.5", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.0 Safari/605.1.15 QQBrowserLite/1.2.9", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.2 Safari/605.1.15 QQBrowserLite/1.2.7", +} + +function ua.get_user_agent() + return ua[random(1, #ua)] +end + +return ua diff --git a/lualib/protocol/mqtt/bit.lua b/lualib/protocol/mqtt/bit.lua index 70e28c87..895d9211 100644 --- a/lualib/protocol/mqtt/bit.lua +++ b/lualib/protocol/mqtt/bit.lua @@ -1,9 +1,4 @@ -- wrapper around BitOp module - -if _VERSION == "Lua 5.1" or _VERSION == "Lua 5.2" or type(jit) == "table" then - return require("bit") -else - return require("protocol.mqtt.bit53") -end +return require("protocol.mqtt.bit53") -- vim: ts=4 sts=4 sw=4 noet ft=lua diff --git a/lualib/protocol/mqtt/init.lua b/lualib/protocol/mqtt/init.lua index b3e8c03a..8f6fc489 100644 --- a/lualib/protocol/mqtt/init.lua +++ b/lualib/protocol/mqtt/init.lua @@ -7,10 +7,10 @@ local library_version = "1.4.2" local require = require local string = require "string" -local calss = require "class" +local class = require "class" local tcp = require "internal.TCP" local Co = require "internal.Co" -local log = require "log" +local log = require "logging" local protocol = require "protocol.mqtt.protocol" local protocol4 = require "protocol.mqtt.protocol4" local co = require "internal.Co" @@ -23,6 +23,8 @@ local make_packet4 = protocol4.make_packet local parse_packet4 = protocol4.parse_packet local connack_return_code = protocol4.connack_return_code +local Log = log:new({ dump = true, path = 'protocol-MQTT'}) + -- cache to locals local type = type local pairs = pairs @@ -34,7 +36,7 @@ local str_match = string.match local str_format = string.format local str_gsub = string.gsub local tbl_remove = table.remove -local co_spwan = Co.spwan +local co_spawn = Co.spawn -- Empty function to do nothing on MQTT client events local empty_func = function() end @@ -54,21 +56,22 @@ function client:ctor(opt) end function client:subscribe(opt, func) - local args = { type = packet_type.SUBSCRIBE, subscriptions = {opt} } self.handle = func - self:_send_packet(args) + local args = { type = packet_type.SUBSCRIBE, subscriptions = {opt} } + local ok, err = self:_send_packet(args) + if not ok then + return false, 'SUBSCRIBE send failed' + end local ok, err = self:_wait_packet_exact{type=packet_type.SUBACK, packet_id=args.packet_id} if not ok then return false, 'SUBSCRIBE wait for SUBACK failed' end - co_spwan(function ( ... ) - local co_current = Co.self() + co_spawn(function ( ... ) local time = os_time() local timer = Timer.at(self.keep_alive, function ( ... ) if os_time() >= time + self.keep_alive then - return co.wakeup(co_current) + return self:ping() end - return self:ping() end) while 1 do local packet, perr = self:_wait_packet_queue() @@ -79,7 +82,7 @@ function client:subscribe(opt, func) end local ok, err = pcall(self.handle, nil) if not ok then - log.error(err) + Log:ERROR(err) end return false, 'waiting for the next packet failed' end @@ -87,16 +90,17 @@ function client:subscribe(opt, func) if packet.type == packet_type.PUBLISH then local ok, err = pcall(self.handle, packet) if not ok then - log.error(err) + Log:ERROR(err) end self:acknowledge(packet) elseif packet.type == packet_type.PUBACK then self:acknowledge(packet) -- elseif packet.type == packet_type.PINGRESP then - -- -- pass + -- pass -- else -- return false, "unexpected packet received: "..tostring(packet) end + end end) return true @@ -330,4 +334,4 @@ function client:close( ... ) self.sock = nil end -return client \ No newline at end of file +return client diff --git a/lualib/protocol/mqtt/protocol.lua b/lualib/protocol/mqtt/protocol.lua index 4e3ff76a..acdafffc 100644 --- a/lualib/protocol/mqtt/protocol.lua +++ b/lualib/protocol/mqtt/protocol.lua @@ -40,7 +40,7 @@ local band = bit.band local lshift = bit.lshift local rshift = bit.rshift local div = tools.div -local unpack = unpack or table.unpack +local unpack = table.unpack -- Create uint8 value data diff --git a/lualib/protocol/mssql.lua b/lualib/protocol/mssql.lua new file mode 100644 index 00000000..c5bdfd14 --- /dev/null +++ b/lualib/protocol/mssql.lua @@ -0,0 +1,1050 @@ +--[[ + LICENSE: BSD + Author: CandyMi[https://github.com/candymi] +]] + +local stream = require "stream" +local tcp = require "internal.TCP" + +local crypt = require "crypt" +local hexencode = crypt.hexencode + +local sys = require "sys" +local now = sys.now +local new_tab = sys.new_tab +local hostname = sys.hostname + +local null = null +local type = type +local error = error +local tostring = tostring +local strpack = string.pack +local strunpack = string.unpack + +local strchar = string.char +local strbyte = string.byte + +local fmt = string.format +local strgsub = string.gsub +local strsub = string.sub + +local toint = math.tointeger + +local os_date = os.date +local os_time = os.time + +local tabconcat = table.concat + +-- TDS公共头部类型 + +local PTYPE_QUERY = 0x01 -- 回应包类型 + +local PTYPE_RESPONSE = 0x04 -- 回应包类型 + +local PTYPE_LOGIN = 0x10 -- TDS-7.0 登录类型 + +-- TDS控制TOKEN类型 + +local ORDER_TOKEN = 0xA9 + +local ERROR_TOKEN = 0xAA + +local INFO_TOKEN = 0xAB + +local ACK_TOKEN = 0xAD + +local ENVCHANGE_TOKEN = 0xE3 + +local COLMETADATA_TOKEN = 0x81 + +local COLMETAROW_TOKEN = 0xD1 + +local DONE_TOKEN = 0xFD + +-- TDS ENVCHANGE 字段表 +local TDS_ENV = { + "Database", + "Language", + "Character_set", + "Packet_size", + "Unicode_sorting_local_id", + "Unicode_sorting_comparison_flags", + "SQL_Collation", + "Begin_Transaction (described in [MSDN-BEGIN])", + "Commit_Transaction (described in [MSDN-COMMIT])", + "Rollback_Transaction", + "Enlist_DTC_Transaction", + "Defect_Transaction", + "Real_Time_Log_Shipping", + "Promote_Transaction", + "Transaction_Manager_Address", + "Transaction_ended", + "Acknowledgement", + "BackInfo", + "Routing", +} + +-- TDS字符集之间的转换兼容函数 + +-- 引入libiconv库实现 USC-2LE 与 UTF8 之间的转换 +local ok, iconv = pcall(require, "liconv") +if not ok then + error("MSSQL Driver Must have Install libiconv.") +end +local liconv_to, liconv_from = iconv.to, iconv.from + +local function iconv_to(s, code) + return liconv_to(code, s) +end + +local function iconv_from(s, code) + return liconv_from(code, s) +end + +local function TO_UCS2LE (s) + return liconv_to("UCS-2LE", s) +end + +local function FROM_UCS2LE(s) + return liconv_from("UCS-2LE", s) +end + +--[[ +加密算法原理: + 1. 对每个字符同时进行高/低位位移(4位); + 2. 将位移后的结果高位与低位进行"或"运算; + 3. 再将之后的运算结果异或0xA5(10100101); + 4. 最终的结果按位"与"0xFF取范围0~255; +C 函数原型: +uint8_t* tds7_crypt_pass(const uint8_t *clear_pass, int len, uint8_t *crypt_pass) { + for (int i = 0; i < len; i++) + crypt_pass[i] = ((clear_pass[i] << 4) | (clear_pass[i] >> 4)) ^ 0xA5; + return crypt_pass; +} +--]] +local function password_encrypt(password) + return strgsub(password, ".", function (ch) + return strchar(((strbyte(ch) << 4 | strbyte(ch) >> 4) ~ 0xA5 ) & 0xff) + end) +end + +-- TDS数据类型(N的位置不固定是因为官方文档命名的问题) +local TYPE_BITN = 0x68 -- (104) BITN +local TYPE_BIT = 0x32 -- (50) BIT + +local TYPE_INTN = 0x26 -- (38) INTN +local TYPE_INT1 = 0x30 -- (48) INT1 +local TYPE_INT2 = 0x34 -- (54) INT2 +local TYPE_INT4 = 0x38 -- (56) INT4 +local TYPE_INT8 = 0x7F -- (127) INT8 + +local TYPE_FLOAT32 = 0x3B -- (59) Float32 +local TYPE_DATETIME = 0x3d -- (59) Float32 +local TYPE_FLOAT64 = 0x3E -- (62) Float64 +local TYPE_DECIMAL = 0x6A -- (106) Decimal +local TYPE_NUMERIC = 0x6C -- (108) Numeric +local TYPE_FLOATN = 0x6D -- (109) float32/64 + +local TYPE_MONEY8 = 0x3C -- (60) Money +local TYPE_MONEY4 = 0x7A -- (122) SmallMoney +local TYPE_MONEYN = 0x6E -- (110) NMoney + +local TYPE_BIGBIN = 0xAD -- (173) NBINARY +local TYPE_CHAR = 0xAF -- (175) Char +local TYPE_VARCHAR = 0xA7 -- (167) VarChar +local TYPE_NVARCHAR = 0xE7 -- (231) NVarChar +local TYPE_NCHAR = 0xEF -- (239) NChar +local TYPE_TEXT = 0x23 -- (35) TEXT +local TYPE_NTEXT = 0x63 -- (99) NTEXT + +local TYPE_DATETIMEN = 0x6F -- (111) DATETIME + +local TYPE_GEO = 0x22 -- (34) GEO + +local TYPE_GUID = 0x24 -- (36) GUID + +local TYPE_HIER = 0xA5 -- (165) Hierarchyid + +-- TDS Field转换方法 +local FTYPE_TAB = {} + +FTYPE_TAB[TYPE_HIER] = function (packet, pos) + return pos + 2 +end + +FTYPE_TAB[TYPE_GUID] = function (packet, pos) + return pos + 1 +end + +FTYPE_TAB[TYPE_GEO] = function (packet, pos) + local large_type_size, table_name_len + large_type_size, table_name_len, pos = strunpack("I2", packet, pos + 8)), + fmt("%02X", strunpack(">I6", packet, pos + 10)) + }, "-"), pos + 16 +end + +RTYPE_TAB[TYPE_BIT] = function (packet, pos) + local value + value, pos = strunpack("BBI2I2BB", OP_CODE, STATUS, LENGTH, CHANNEL, PACKNO, WINDOW) +end + +local function tds_unpack_header(packet) + return strunpack(">BBI2I2BB", packet) +end + +local function tds_login7( self ) + + local HOSTNAME = hostname() + -- print(HOSTNAME, #HOSTNAME) + + local APP_NAME = "cfadmin" + -- print(APP_NAME, #APP_NAME) + + local LOCALE = "us_english" + -- print(LOCALE, #LOCALE) + + local SERVER_NAME = self.host + local DATABASE = self.database + local USERNAME = self.username + local PASSWORD = self.password + + local msg = { + -- LOGIN 7 默认头部信息 + strpack("BBI2", 7, 1, 1), -- Client Version (7.1.1) + strpack(" 0 then + field_name = FROM_UCS2LE(strsub(packet, pos, pos + field_length * 2 - 1)) + pos = pos + field_length * 2 + end + fields[#fields+1] = { field_name = field_name, field_type = field_type, precision = precision, scale = scale } + end + return fields, pos +end + +local function tds_get_row_data (packet, pos, fields) + -- var_dump(fields) + local rows = new_tab(#fields, 0) + local value + for index = 1, #fields do + local field = fields[index] + local f = RTYPE_TAB[field.field_type] + -- print(pos) + if type(f) == 'function' then + value, pos = f(packet, pos, field.precision, field.scale) + else + error("Error: Unknown data type [" .. field.field_type .. "] in " .. index) + end + -- print(field.field_type, value, pos) + rows[#rows+1] = value + end + return rows, pos +end + +local function tds_done_to_tab(tab, status, operation, row_count) + -- 这个值应该被忽略 + tab["DONE_OPERATION"] = operation + -- 是否是最终的数据包 + tab["DONE_FINAL"] = status & 0x01 == 0x00 and true or false + -- 是否还有其他数据包 + tab["DONE_MORE"] = status & 0x01 == 0x01 and true or false + -- 是否是一个错误数据包 + tab["DONE_ERROR"] = status & 0x01 == 0x01 and true or false + -- 是否正在处理一个事务 + tab["DONE_TRANSACTION"] = status & 0x04 == 0x04 and true or false + -- TODO + tab["DONE_COUNT"] = status & 0x10 == 0x10 and row_count or 0 + -- TODO + tab["DONE_ATTN"] = status & 0x20 == 0x20 and true or false +end + +local function tds_get_done (packet, pos) + return strunpack("= #tds_data then + self:write(tds_pack_header(PTYPE_QUERY, 0x01, #tds_data + 8, 0, 1, 0) .. tds_data) + else + local fin = 0x00 + while 1 do + local body = strsub(tds_data, 1, self.max_packet_size) + if #tds_data <= self.max_packet_size then + fin = 0x01 + end + self:write(tds_pack_header(PTYPE_QUERY, fin, #body + 8, 0, 1, 0) .. body) + if fin == 0x01 then + break + end + tds_data = strsub(tds_data, self.max_packet_size + 1, -1) + end + end + return tds_read_response(self) +end + +local class = require "class" + +local mssql = class("mssql") + +function mssql:ctor(opt) + self.sock = stream(tcp()) + self.host = opt.host or "localhost" + self.port = opt.port or 1433 + self.unixdomain = opt.unixdomain + self.TSQL = opt.TSQL == 1 and 1 or 0 + self.max_packet_size = opt.max_packet_size or 10240 + self.database = opt.database or "master" + self.username = opt.username or "sa" + self.password = opt.password + -- self.state = true +end + +function mssql:read( bytes ) + return self.sock:readbytes(bytes) +end + +function mssql:write(data) + return self.sock:send(data) +end + +function mssql:connect( ... ) + if not self.sock then + return nil, "Connection failed: please recreate the socket object." + end + + if self.unixdomain then + if not self.sock:connectx(self.unixdomain) then + return nil, "MSSQL Server [" .. tostring(self.unixdomain) .. "] Connect failed." + end + elseif self.host and self.port then + if not self.sock:connect(self.host, self.port) then + return nil, "MSSQL Server TCP Connect failed." + end + else + return nil, "MSSQL Server driver Invalid Configure." + end + + -- 发送TDS-7.0登录协议 + self:write(tds_login7(self)) + + local packet = tds_read_head(self) + if not packet then + return nil, "1. After sending the LOGIN data, the server disconnected." + end + + local OPCODE, STATUS, LENGTH, CHANNEL, PACKNO, WINDOW = tds_unpack_header(packet) + if OPCODE ~= PTYPE_RESPONSE then + return nil, "A protocol type not supported by TDS-7.0 was received." + end + + local packet = tds_read_body(self, LENGTH) + if not packet then + return nil, "2. After sending the LOGIN data, the server disconnected." + end + + local sever = new_tab(0, 6) + local pos + while 1 do + local token_type + token_type, pos = strunpack(" 0 then + if env_type ~= 0x07 then + new_value = FROM_UCS2LE(strsub(packet, pos, pos + new_len * 2 - 1)) + pos = pos + new_len * 2 + else + collate_codepage, collate_flags, collate_charset_id, pos = strunpack(" 0 then + old_value = FROM_UCS2LE(strsub(packet, pos, pos + old_len * 2 - 1)) + pos = pos + old_len * 2 + end + sever[type_name] = { + new_value = new_value, + old_value = old_value, + collate_codepage = collate_codepage, + collate_flags = collate_flags, + collate_charset_id = collate_charset_id + } + -- return sever + elseif token_type == INFO_TOKEN then + local info + info, pos = tds_get_errorinfo(packet, pos) + elseif token_type == ACK_TOKEN then + local len, interface, tds_version, server_name_len, server_name, server_version_max1, server_version_max2, server_version_min + len, pos = strunpack(" self._max_packet_size then - return nil, nil, "packet size too big: " .. len - end - - local num = strbyte(data, pos) - - --print("recv packet: packet no: ", num) - - self.packet_no = num - - data, err = sock:recv(len) - - --print("receive returned") - - if not data then - self.state = nil - return nil, nil, "failed to read packet content: "..(err or "nil") - end - - local field_count = strbyte(data, 1) +local function get_lenenc_str (packet, pos) + local bit, len + bit, pos = strunpack("= 0 and first <= 250 then - return first, pos + 1 - end - - if first == 251 then - return null, pos + 1 - end - - if first == 252 then - pos = pos + 1 - return _get_byte2(data, pos) - end - - if first == 253 then - pos = pos + 1 - return _get_byte3(data, pos) - end - - if first == 254 then - pos = pos + 1 - return _get_byte8(data, pos) - end - + local first = strbyte(data, pos) + if not first then + return nil, pos + end + if first >= 0 and first <= 250 then + return first, pos + 1 + end + if first == 251 then return nil, pos + 1 + end + if first == 252 then + pos = pos + 1 + return strunpack(" 2 then + null_fields[field_index - 2] = byte & (1 << j) ~= 0 and true or false + end + field_index = field_index + 1 + end + end + + local parser + local row = new_tab(0, ncols) + for i = 1, ncols do + local col = cols[i] + -- var_dump(col) + if not null_fields[i] then + parser = binary_parser[col.field_type] + if not parser then + error("error! field key[" .. col.field_name .."] unsupported type " .. col.field_type) + end + row[col.field_name], pos = parser(data, pos, col.is_signed) + else + row[col.field_name] = null end - - return errno, sub(packet, pos), sqlstate + end + return row end - -local function _parse_result_set_header_packet(packet) - local field_count, pos = _from_length_coded_bin(packet, 1) - - local extra - extra = _from_length_coded_bin(packet, pos) - - return field_count, extra +local function get_eof_packet (packet) + local pos = 1 + local warning_count, status_flags + warning_count, pos = strunpack(" pos then + message, pos = strunpack("s1", packet, pos) + end + return { + auto_commit = server_status & 0x02 == 0x02 and true or false, transaction = server_status & 0x01 == 0x01 and true or false, + last_insertid = last_insertid, affected_rows = affected_rows, server_warnings = server_warnings, message = message + }, server_status & SERVER_MORE_RESULTS == SERVER_MORE_RESULTS end - -local function _recv_field_packet(self) - local packet, typ, err = _recv_packet(self) +local function read_response (self, results) + local packet = read_packet(self) + if not packet then + self.state = nil + return nil, "1. mysql server closed when client sended query packet." + end + local status = strbyte(packet, 1) + if status == RESP_ERROR then + return nil, get_mysql_error_packet(packet:sub(2)) + end + if status == RESP_OK then + local tab, again = get_ok(packet:sub(2), #packet - 1) + if again then -- 如果是`多结果集的数据 + if type(results) == 'table' then + results[#results+1] = tab + return read_response(self, results) + end + return read_response(self, { tab }) + end + if type(results) == 'table' then + results[#results+1] = tab + tab = results + end + return tab + end + + local fields = new_tab(status, 0) + for index = 1, status do + local field, err = get_field(self) + if not field then + self.state = nil + return nil, err + end + fields[index] = field + end + + local again = false + local len = read_head(self) + if not len then + self.state = nil + return nil, "2. mysql server closed when client sended query packet." + end + local status, err = read_status(self) + if not status then + self.state = nil + return nil, err + end + local sever = get_eof(self, len - 1) + if sever.status_flags & SERVER_MORE_RESULTS == SERVER_MORE_RESULTS then + again = true + end + + local rows = new_tab(32, 0) + while 1 do + packet, err = read_packet(self) if not packet then - return nil, err - end - - if typ == "ERR" then - local errno, msg, sqlstate = _parse_err_packet(packet) - return nil, msg, errno, sqlstate + self.state = nil + return nil, err + end + local b = strbyte(packet, 1) + if b == RESP_EOF and #packet < 9 then + local sever = get_eof_packet(packet) + if sever.status_flags & SERVER_MORE_RESULTS == SERVER_MORE_RESULTS then + again = true + end + break + end + if b == RESP_ERROR then + return nil, get_mysql_error_packet(packet:sub(2)) + end + rows[#rows+1] = get_rows(packet, #fields) + end + + local result = new_tab(#rows, 0) + for _, row in ipairs(rows) do + local tab = new_tab(0, #fields) + for index, item in ipairs(row) do + local field = fields[index] + local call = converts[field.field_type] + if not call then + -- print("not call") + tab[field.field_name] = item + else + -- print(field.field_type, field.field_name, item, call(item), item == null) + tab[field.field_name] = call(item) + end + end + result[#result+1] = tab + end + + if again then + if type(results) == 'table' and #results > 0 then + results[#results+1] = result + return read_response(self, results) + else + return read_response(self, { result }) end + end - if typ ~= 'DATA' then - return nil, "bad field packet type: " .. typ - end + if results then + results[#results+1] = result + end + return results or result +end - return _parse_field_packet(packet) +local function send_packet (self, request) + self.packet_no = self.packet_no + 1 + return sock_write(self, strpack(" 0 then + for _ = 1, num do + local len = read_head(self) + if not len then + return false + end + sock_read(self, len) + end + local len = read_head(self) + if not sock_read(self, len) then + return false + end + end + return true +end -function MySQL.ctor(self) +local function read_prepare_response(self) + local packet = read_packet(self) + if not packet then + self.state = nil + return nil, "1. mysql server closed when client sended query packet." + end + -- 预编译status只有OK或ERROR + local status = strbyte(packet, 1) + if status == RESP_ERROR then + return nil, get_mysql_error_packet(packet:sub(2)) + end + if status ~= RESP_OK then + return nil, "2. Invalid mysql prepare protocol." + end + local info = {} + info.sid, info.fields, info.params, info.warnings = strunpack(" 0 then + local field_index = 1 + local null_map = "" + local null_count = (argn + 7) // 8 + -- null-bitmap 必须检查正确. + for _ = 1, null_count do + local nbyte = 0 + for offset = 0, 7 do + if field_index <= argn then + local v = args[field_index] + nbyte = nbyte | (((v == nil or type(v) == 'userdata') and 1 or 0) << offset) + end + field_index = field_index + 1 + end + null_map = null_map .. strchar(nbyte) + end + local tb_idx, vb_idx = 1, 1 + local types_buf, values_buf = new_tab(16, 0), new_tab(16, 0) + for i = 1, argn, 1 do + local v = args[i] + local f = fmap[type(v)] + if not f then + error("invalid parameter type :" .. type(v)) + end + types_buf[tb_idx], values_buf[vb_idx] = f(v) + tb_idx, vb_idx = tb_idx + 1, vb_idx + 1 + end + sql = concat{sql, null_map, '\x01', concat(types_buf), concat(values_buf)} + end + send_packet(self, sql) + return read_execute_reponse(self) +end - if typ == 'EOF' then - return nil, "old pre-4.1 authentication protocol not supported" - end +local function mysql_prepare(self, stmt) + send_packet(self, strpack("zzzzzzz", msg) + return strsub(severity, 2), strsub(text, 2), strsub(code, 2), strsub(message, 2), strsub(file, 2), strsub(line, 2), strsub(routine, 2) +end + +local function get_error_message_fmt(msg) + local _, etype, code, message = get_error_message(msg) + return nil, fmt("[%s] : {'%s', '%s'}", code, etype, message) +end + +local function get_error_message_tab(msg) + local severity, text, code, message, file, line, routine = string.unpack(">zzzzzzz", msg) + return { + severity = severity, + text = text, + code = code, + message = message, + file = file, + line = line, + routine = routine + } +end + +local function read_opcode_and_len (self) + local opstr = self:read(1) + if not opstr then + return nil, "client read: server close this session." + end + local opcode = RESPONSES[strbyte(opstr)] + local len_byte, err = self:read(4) + if not len_byte then + return nil, "client read: server close this session." + end + local len = strunpack(">I4", len_byte) + if not len then + return nil, "An unrecognized message type was received." + end + return opcode, len +end + +local function read_head (self) + local opcode, len = read_opcode_and_len(self) + return opcode, len, self:read(4) +end + +local function get_query_error_msg (self, data) + local _, etype, code, message = get_error_message(data) + local msg = fmt("[%s] : {'%s', '%s'}", code, etype, message) + local opcode, len = read_opcode_and_len(self) + if opcode ~= RESP_READY then + return nil, len + end + local _ = self:read(len - 4) + return nil, msg +end + +local function read_column_data (self, data_len) + local row_data = self:read(data_len) + if not row_data then + return nil, "client read: server close this session. " + end + local len, index = strunpack(">I2", row_data) + local columns = new_tab(len, 0) + for i = 1, len do + local column_name, pos = strunpack(">z", row_data, index) + -- print(column_name, pos) + local column_table_oid, pos = strunpack(">I4", row_data, pos) + -- print(column_table_oid, pos) + local column_index, pos = strunpack(">I2", row_data, pos) + -- print(column_index, pos) + local column_type_oid, pos = strunpack(">I4", row_data, pos) + -- print(column_type_oid, pos) + local column_length, pos = strunpack(">I2", row_data, pos) + -- print(column_length, pos) + local column_type_modifier, pos = strunpack(">i4", row_data, pos) + -- print(column_type_modifier, pos) + local column_format, pos = strunpack(">I2", row_data, pos) + -- print(column_format, pos) + index = pos + columns[#columns+1] = { + column_name = column_name, + column_type_oid = column_type_oid, + -- column_index = column_index, + -- column_length = column_length, + -- column_format = column_format, + -- column_table_oid = column_table_oid, + -- column_type_modifier = column_type_modifier, + } + end + return columns +end + +local function read_row_data (self) + local opcode, len = read_opcode_and_len(self) + if not opcode then + return nil, "server close this session." + end + if opcode == RESP_CMD_COMPLETION then + return opcode, len + end + local row_data = self:read(len - 4) + if not row_data then + return nil, "server close this session." + end + local index = 3 + local count = strunpack(">I2", row_data) + local row = new_tab(count, 0) + for i = 1, count do + local raw_len, pos = strunpack(">i4", row_data, index) + -- print(raw_len) + if raw_len > -1 then + row[#row + 1] = row_data:sub(pos, pos + raw_len - 1) + index = pos + raw_len + else + row[#row + 1] = null + index = pos + end + end + return row +end + +local function read_response (self) + local results = {} + while 1 do + local opcode, len = read_opcode_and_len(self) + if not opcode then + self.state = "closed" + return nil, "1. server close this session." + end + if opcode == RESP_ERROR then + return get_query_error_msg(self, self:read(len - 4):sub(5)) + end + local result + if opcode == RESP_STATUS then + local kv = self:read(len - 4) + if not kv then + self.state = "closed" + return nil, "2. server close this session." + end + local k, v = strunpack("zz", kv) + if not result then + result = { ok = true, [k] = v } + else + result[k] = v + end + result['ok'] = true + result['action'] = "SET" + result['status'] = "Idle" + results[#results + 1] = result + local opcode, len = read_opcode_and_len(self) + if not opcode then + self.state = "closed" + return nil, "3. server close this session." + end + if opcode == RESP_CMD_COMPLETION then + local _ = self:read(len - 4) + end + elseif opcode == RESP_CMD_COMPLETION then + local tab = new_tab(3, 0) + local content = self:read(len - 4) + if not content then + self.state = "closed" + return nil, "4. server close this session." + end + for v in strgmatch(content, "[^ \x00]+") do + tab[#tab+1] = v + end + if not result then + result = new_tab(0, 5) + end + local action = tab[1] + result['ok'] = true + result['status'] = "Idle" + result['action'] = action + if action == "INSERT" then + result['oid'] = toint(tab[2]) + result['affected_rows'] = toint(tab[3]) + elseif action == "UPDATE" or action == "DELETE" then + result['affected_rows'] = toint(tab[2]) + else + result["rows"] = toint(tab[2]) + end + results[#results + 1] = result + -- var_dump(results) + elseif opcode == RESP_READY then + local v = self:read(len - 4) + if not v then + self.state = "closed" + return nil, "5. server close this session." + end + -- if v == "T" then + -- results[#results].transaction = true + -- else + -- results[#results].transaction = false + -- end + break + elseif opcode == RESP_COLUMN then + local columns, err = read_column_data(self, len - 4) + if not columns then + self.state = "closed" + return nil, err + end + -- var_dump(columns) + local row, len + local rows = new_tab(128, 0) + while 1 do + row, len = read_row_data(self) + if type(row) == 'table' then + rows[#rows + 1] = row + elseif type(row) == 'number' then + break + else + self.state = "closed" + return nil, "6. server close this session." + end + end + -- var_dump(rows) + result = new_tab(#rows, 0) + for _, row in ipairs(rows) do + local tab = {} + for index, column in ipairs(columns) do + tab[column.column_name] = convert(column.column_type_oid, row[index]) + end + result[#result + 1] = tab + end + results[#results+1] = result + if row == RESP_CMD_COMPLETION then + local v = self:read(len - 4) + if not v then + self.state = "closed" + return nil, "7. server close this session." + end + end + end + end + return #results == 1 and results[1] or results +end + +-- AUTH_MD5 +local function auth_md5(auth_user, auth_password, auth_salt) + return "md5" .. md5(md5(auth_password .. auth_user, true) .. auth_salt, true) +end + +-- 设置客户端连接参数 +local function set_conn_param(key, value) + return strpack("zz", key, value) +end + +local class = require "class" + +local pgsql = class("pgsql") + +function pgsql:ctor(opt) + self.sock = tcp:new() + self.host = opt.host or "localhost" + self.port = opt.port or 5432 + self.unixdomain = opt.unixdomain + self.database = opt.database or "postgres" + self.username = opt.username or "postgres" + self.password = opt.password or "postgres" + self.charset = opt.charset or "UTF8" + self.application_name = opt.application_name or "cfadmin" + -- self.state = "connected" +end + +function pgsql:read(bytes) + return self.sock:readbytes(bytes) +end + +function pgsql:write(data) + return self.sock:send(data) +end + +function pgsql:startup () + local connect_params = tconcat { + strpack(">I2I2", 3, 0), -- 使用3.0交互协议 + set_conn_param("user", self.username), -- 设置客户端登录名 + set_conn_param("database", self.database), -- 设置客户端数据库 + set_conn_param("client_encoding", self.charset), -- 设置客户端字符集 + set_conn_param("application_name", self.application_name), -- 设置客户端应用名 + "\x00", + } + return strpack(">I4", #connect_params + 4) .. connect_params +end + +function pgsql:authmd5 (data) + return strpack(">BI4z", strbyte("p"), 40, auth_md5(self.username, self.password, data)) +end + +function pgsql:connect() + + if self.unixdomain then + if not self.sock:connect_ex(self.unixdomain or "") then + return nil, "PGSQL Server [" .. tostring(self.unixdomain) .. "] Connect failed." + end + elseif self.host and self.port then + if not self.sock:connect(self.host, self.port) then + return nil, "PGSQL Server TCP Connect failed." + end + else + return nil, "PGSQL Server driver Invalid Configure." + end + + -- Socket Stream Wrapper. + self.sock = stream(self.sock) + + -- 发送启动协议 + self:write(self:startup()) + + local opcode, len, auth_type = read_head(self) + if opcode ~= RESP_OK or not auth_type then + return nil, "1. Malformed response packets." + end + if opcode == RESP_ERROR then + return get_error_message_fmt(auth_type .. self:read(len - 8) ) + end + + auth_type = strunpack(">I4", auth_type) + if auth_type == RESP_AUTHMD5 then + local status + self:write(self:authmd5(self:read(len - 8))) + opcode, len, status = read_head(self) + if not opcode or not status then + return nil, "2. Malformed response packets." + end + if opcode == RESP_ERROR then + return get_error_message_fmt(status .. self:read(len - 8) ) + end + end + + -- 获取服务器配置信息 + local server = new_tab(0, 16) + while 1 do + opcode, len = read_opcode_and_len(self) + if opcode == RESP_STATUS then + local k, v = strunpack("zz", self:read(len - 4)) + server[k] = v == 'on' and true or v + elseif opcode == RESP_STATUS_END then + server["pid"] = strunpack(">I4", self:read(4)) + server["key"] = strunpack(">I4", self:read(4)) + elseif opcode == RESP_READY then + server["status"] = "Idle" + self:read(len - 4) -- 读取并丢弃无用的数据 + break + elseif opcode == RESP_ERROR then + return get_error_message_fmt(self:read(len - 4) ) + else + return nil, "3. Malformed response packets." + end + end + + self.state = "connected" + self.server = server + return server +end + +function pgsql:query (sql) + if type(sql) ~= 'string' or sql == '' then + return nil, "Invalid SQL." + end + self:write(strpack(">BI4z", OP_QUERY, #sql + 5, sql)) + return read_response(self) +end + +local escape_map = { + ['\0'] = "\\0", + ['\b'] = "\\b", + ['\n'] = "\\n", + ['\r'] = "\\r", + ['\t'] = "\\t", + ['\26'] = "\\Z", + ['\\'] = "\\\\", + ["'"] = "\\'", + ['"'] = '\\"', +} + +function pgsql.quote_to_str (sql) + return fmt("%s", string.gsub(sql, "[\0\b\n\r\t\26\\\'\"]", escape_map)) +end + +function pgsql:set_timeout(timeout) + if self.sock and tonumber(timeout) then + self.sock:timeout(timeout) + end +end + +function pgsql:close() + if self.state == "connected" then + self.state = "closed" + self:write(strpack(">BI4", OP_TERMINATE, 4)) + end + if self.sock then + self.sock:close() + self.sock = nil + end +end + +return pgsql \ No newline at end of file diff --git a/lualib/protocol/redis.lua b/lualib/protocol/redis.lua index 132a9b49..34118641 100644 --- a/lualib/protocol/redis.lua +++ b/lualib/protocol/redis.lua @@ -1,212 +1,258 @@ -local log = require "log" +local log = require "logging" local class = require "class" local Co = require "internal.Co" local tcp = require "internal.TCP" -local table = table -local concat = table.concat +local stream = require "stream" +local tcp_send = stream.send +local tcp_readline = stream.readline +local tcp_readbytes = stream.readbytes + +local new_tab = require "sys".new_tab + +local Log = log:new({ dump = true, path = 'protocol-redis' }) + +local co_spawn = Co.spawn -local co_spwan = Co.spwan +local type = type +local pcall = pcall +local ipairs = ipairs +local assert = assert +local tonumber = tonumber +local tostring = tostring + +local insert = table.insert +local concat = table.concat +local unpack = table.unpack local sub = string.sub -local string = string -local match = string.match -local find = string.find local byte = string.byte -local upper = string.upper +local toint = math.tointeger local CRLF = '\x0d\x0a' local redcmd = {} local function read_response(sock) - local result = "" - while 1 do - local data = sock:recv(1) - if not data then - return nil, 'server close!!' - end - result = result .. data - if find(result, CRLF) then - break - end - end - local firstchar = byte(result) - return redcmd[firstchar](sock, sub(result, 2)) + local result = tcp_readline(sock, CRLF) + if not result then + sock.state = false + return nil, 'server close!!' + end + -- 断言redis 协议是否支持用于快速排错 + return assert(redcmd[byte(result)], "Invalid protocol command : " .. sub(result, 1, 1))(sock, sub(result, 2)) +end + +local function sock_readbytes(sock, bytes) + return tcp_readbytes(sock, bytes) end redcmd[36] = function(sock, data) -- '$' - local bytes = tonumber(data) - if bytes < 0 then - return true, nil - end - local firstline = sock:recv(bytes + 2) - return true, sub(firstline, 1, -3) + local bytes = tonumber(data) + if bytes < 0 then + return true, nil + end + local firstline = sock_readbytes(sock, bytes + 2) + return true, sub(firstline, 1, -3) end redcmd[43] = function(sock, data) -- '+' - return true, match(data, '(.+)'..CRLF) + return true, sub(data, 1, -3) end redcmd[45] = function(sock, data) -- '-' - return false, data + return false, sub(data, 1, -3) end redcmd[58] = function(sock, data) -- ':' - -- todo: return string later - return true, tonumber(data) + -- todo: return string later + return true, tonumber(data) end redcmd[42] = function(sock, data) -- '*' - local n = tonumber(data) - if n < 0 then - return true, nil - end - local bulk = {} - local noerr = true - for i = 1,n do - local ok, v = read_response(sock) - if not ok then - noerr = false - end - bulk[i] = v - end - return noerr, bulk + local n = tonumber(data) + if n < 0 then + return true, nil + end + local bulk = new_tab(n, 0) + local noerr = true + for i = 1, n do + local ok, v = read_response(sock) + if not ok then + noerr = false + end + bulk[i] = v + end + return noerr, bulk end -- 格式化命令为redis protocol local function CMD(...) - local tab = {...} - local lines = { "*"..#tab} - for index = 1, #tab do - lines[#lines+1] = "$"..#tostring(tab[index]) - lines[#lines+1] = tab[index] - if index == #tab then - lines[#lines+1] = "" - end - end - return concat(lines, CRLF) + local tab = {...} + local lines = new_tab(#tab, 0) + lines[#lines+1] = "*"..#tab + for index = 1, #tab do + lines[#lines+1] = "$"..#tostring(tab[index]) + lines[#lines+1] = tab[index] + if index == #tab then + lines[#lines+1] = "" + end + end + return concat(lines, CRLF) end local function read_boolean(sock) - local ok, result = read_response(sock) - if ok then - return ok, result ~= 0 or result == "OK" - end - return ok, result + local ok, result = read_response(sock) + if ok then + return ok, result ~= 0 or result == "OK" + end + return ok, result end local function redis_login(sock, auth, db) - if auth then - sock:send(CMD("AUTH", auth)) - local ok, err = read_response(sock) - if not ok then - return nil, err - end - end - if db then - sock:send(CMD("SELECT", db)) - local ok, err = read_response(sock) - if not ok then - return nil, err - end - end - return true + if type(auth) == 'string' and auth ~= '' then + tcp_send(sock, CMD("AUTH", auth)) + local ok, err = read_response(sock) + if not ok then + return nil, err + end + end + if toint(db) and toint(db) >= 0 then + tcp_send(sock, CMD("SELECT", db)) + local ok, err = read_response(sock) + if not ok then + return nil, err + end + end + sock.state = true + return true end local redis = class("redis") function redis:ctor(opt) - self.sock = tcp:new() - self.host = opt.host - self.port = opt.port - self.db = opt.db - self.auth = opt.auth + self.sock = tcp:new() + self.host = opt.host + self.port = opt.port + self.unixdomain = opt.unixdomain + self.auth = opt.auth + self.db = opt.db +end + +function redis:isconnected() + return self.sock and self.sock.state or false end function redis:connect() - local sock = self.sock - if not sock then - return nil, "Can't Create redis Socket" - end - local ok, err = sock:connect(self.host, self.port or 6379) - if not ok then - return nil, "redis connect error: please check network" - end - local ok, err = redis_login(sock, self.auth, self.db) - if not ok then - return nil, "redis login error:"..(err or 'close') - end - return true + -- 尝试多种连接渠道 + if not self.sock:connect_ex(self.unixdomain or "") and not self.sock:connect(self.host, toint(self.port) or 6379) then + return nil, "redis network connect failed." + end + -- Socket Stream Wrapper. + self.sock = stream(self.sock) + -- 登录状态检查 + local ok, err = redis_login(self.sock, self.auth, self.db) + if not ok then + return nil, "redis login error:" .. (err or 'close') + end + return true +end + +function redis:set_timeout(timeout) + self.sock._timeout = timeout + return self end -- 订阅 function redis:psubscribe(pattern, func) - local sock = self.sock - sock:send(CMD("PSUBSCRIBE", pattern)) - local ok, msg = read_response(sock) - if not ok or not msg[2] then - return nil, "PSUBSCRIBE error: 订阅"..tostring(pattern).."失败." - end - co_spwan(function ( ... ) - while 1 do - local ok, msg = read_response(sock) - if not ok or not msg or not self.sock then - local ok, err = pcall(func, nil) - if not ok then - log.error(err) - end - return - end - local data = {type = msg[1], source = msg[2], pattern = pattern, payload = msg[3]} - if #msg > 3 then - data = {type = msg[1], source = msg[3], pattern = pattern, payload = msg[4]} - end - local ok, err = pcall(func, data) - if not ok then - return log.error(err) - end - end - end) - return ok, msg + local sock = self.sock + tcp_send(sock, CMD("PSUBSCRIBE", pattern)) + local ok, msg = read_response(sock) + if not ok or not msg[2] then + return nil, "PSUBSCRIBE error: 订阅"..tostring(pattern).."失败." + end + co_spawn(function () + while 1 do + local ok, msg = read_response(sock) + if not ok or not msg or not self.sock then + local ok, err = pcall(func, nil) + if not ok then + Log:ERROR(err) + end + return + end + local data = {type = msg[1], source = msg[2], pattern = pattern, payload = msg[3]} + if #msg > 3 then + data = {type = msg[1], source = msg[3], pattern = pattern, payload = msg[4]} + end + local ok, err = pcall(func, data) + if not ok then + return Log:ERROR(err) + end + end + end) + return ok, msg end -- 订阅 function redis:subscribe(pattern, func) - return self:psubscribe(pattern, func) + return self:psubscribe(pattern, func) end -- 发布 function redis:publish(pattern, data) - local sock = self.sock - sock:send(CMD("PUBLISH", pattern, data)) - return read_response(sock) + local sock = self.sock + tcp_send(sock, CMD("PUBLISH", pattern, data)) + return read_response(sock) end -- 查询键是否存在 function redis:exists(key) - local sock = self.sock - sock:send(CMD("EXISTS", key)) - return read_boolean(sock) + local sock = self.sock + tcp_send(sock, CMD("EXISTS", key)) + return read_boolean(sock) end -- 查询元素是否集合成员 function redis:sismember(key, value) - local sock = self.sock - sock:send(CMD("SISMEMBER", key, value)) - return read_boolean(sock) + local sock = self.sock + tcp_send(sock, CMD("SISMEMBER", key, value)) + return read_boolean(sock) end -- 执行命令 function redis:cmd(...) - local sock = self.sock - sock:send(CMD(...)) - return read_response(sock) + local sock = self.sock + tcp_send(sock, CMD(...)) + return read_response(sock) +end + +-- 管道命令 +function redis:pipeline(opt) + local cmds = {} + if opt and #opt > 0 then + for _, cmd in ipairs(opt) do + cmds[#cmds+1] = CMD(unpack(cmd)) + end + end + local max_read_times = #cmds + if max_read_times > 0 then + local sock = self.sock + tcp_send(sock, concat(cmds)) + local rets = new_tab(max_read_times, 0) + for index = 1, max_read_times do + rets[index] = {read_response(sock)} + end + return true, rets + end + return nil end function redis:close() - if self.sock then - self.sock:close() - end + if self.sock then + self.sock.state = false + self.sock:close() + self.sock = nil + end end -return redis \ No newline at end of file +return redis diff --git a/lualib/protocol/smtp/init.lua b/lualib/protocol/smtp/init.lua new file mode 100644 index 00000000..672fa864 --- /dev/null +++ b/lualib/protocol/smtp/init.lua @@ -0,0 +1,257 @@ +local class = require "class" +local tcp = require "internal.TCP" + +local crypt = require "crypt" +local base64encode = crypt.base64encode + +local type = type +local toint = math.tointeger +local tonumber = tonumber +local tostring = tostring +local match = string.match +local fmt = string.format +local os_date = os.date +local concat = table.concat + +local MAX_PACKET_SIZE = 4096 + +local CRLF = '\x0d\x0a' + +local function read_packet(str) + local str_code, err = match(str, "(%d+) (.+)") + local code = tonumber(str_code) + if not code then + return + end + return code, err +end + +local function time() + return os_date("[%Y/%m/%d %H:%M:%S]") +end + +local smtp = class("smtp") + +function smtp:ctor (opt) + self.ssl = opt.SSL or opt.ssl + self.host = opt.host + self.port = opt.port + self.to = opt.to + self.from = opt.from + self.fromName = opt.fromName + self.mime = opt.mime + self.subject = opt.subject + self.content = opt.content + self.username = opt.username + self.password = opt.password + self.sock = tcp:new() +end + +-- 发送握手包 +function smtp:hello_packet () + local code, data, info + -- 接收服务端信息 + data = self:readline(CRLF) + if not data then + return nil, "SMTP Client Can't connect to server." + end + code, info = read_packet(data) + if not code then + return nil, "[HELO ERROR]: Unsupported protocol." + end + -- 发送HELO命令 + if not self:sendline("HELO cf_smtp/0.1", CRLF) then + return nil, "[HELO ERROR]: Failed to send HELO message." + end + -- 接收HELO回应 + data = self:recv(MAX_PACKET_SIZE) + if not data then + return nil, "SMTP Server Close this session." + end + code, info = data:sub(1, 3), data:sub(4) + if toint(code) ~= 250 and toint(code) ~= 220 then + return nil, "[HELO ERROR]: " .. (info or "Invalid Response." ) + end + return true +end + +-- 登录认证 +function smtp:auth_packet () + local code, data, err + -- 发送登录认证请求 + if not self:sendline("AUTH LOGIN", CRLF) then + return nil, "AUTH LOGIN ERROR]: SMTP Server Close this session." + end + data = self:readline(CRLF) + if not data then + return nil, '[AUTH LOGIN ERROR]: 1. SMTP Server Close this session.' + end + code, err = read_packet(data) + if not code or code ~= 334 then + return nil, '[AUTH LOGIN ERROR]: 1. 验证失败('.. tostring(code) .. (err or '未知错误') ..')' + end + -- 发送base64用户名 + if not self:sendline(base64encode(self.username), CRLF) then + return nil, "[AUTH LOGIN ERROR]: SMTP Server Close this session when sending username." + end + data = self:readline(CRLF) + if not data then + return nil, '[AUTH LOGIN ERROR]: 2. SMTP Server Close this session.' + end + code, err = read_packet(data) + if not code or code ~= 334 then + return nil, '[AUTH LOGIN ERROR]: 2. verification failed('.. (err or '未知错误') ..')' + end + -- 发送base64密码 + if not self:sendline(base64encode(self.password), CRLF) then + return nil, "[AUTH LOGIN ERROR]: SMTP Server Close this session when sending password." + end + data = self:readline(CRLF) + if not data then + return nil, '[AUTH LOGIN ERROR]: 3. SMTP Server Close this session.' + end + code, err = read_packet(data) + if not code or code ~= 235 then + return nil, '[AUTH LOGIN ERROR]: ' .. (err or 'Token verification failed.') + end + return true +end + +-- 发送邮件头部 +function smtp:send_header () + local code, data, err + -- 发送邮件来源 + if not self:sendline(fmt("MAIL FROM: <%s>", self.from), CRLF) then + return nil, "[MAIL FROM ERROR]: Sending `MAIL FROM` Failed." + end + data = self:readline(CRLF) + if not data then + return nil, '[MAIL FROM ERROR]: SMTP Server Close this session.' + end + code, err = read_packet(data) + if not code or code ~= 250 then + return nil, '[MAIL FROM ERROR]: ('.. tostring(code) .. (err or 'Unknown Error.') ..')' + end + -- 发送邮件接收者 + local ok = self:sendline(fmt("RCPT TO: <%s>", self.to), CRLF) + if not ok then + return nil, "[RCPT TO ERROR]: Sending `RCPT TO` Failed." + end + data = self:readline(CRLF) + if not data then + return nil, '[RCPT TO ERROR]: SMTP Server Close this session.' + end + code, err = read_packet(data) + if not code or code ~= 250 then + return nil, '[RCPT TO ERROR]: ('.. tostring(code) .. (err or '未知错误') ..')' + end + return true +end + +-- 发送邮件内容 +function smtp:send_content () + local code, data, err + -- 发送DATA命令, 开始发送邮件实体 + if not self:sendline("DATA", CRLF) then + return nil, "[MAIL CONTENT ERROR]: Sending `DATA` Failed." + end + data = self:readline(CRLF) + if not data then + return nil, '[MAIL CONTENT ERROR]: SMTP Server Close this session.' + end + code, err = read_packet(data) + if not code or code ~= 354 then + return nil, '[MAIL CONTENT ERROR]: ('.. tostring(code) .. (err or '未知错误') ..')' + end + if self.mime and self.mime == 'html' then + self.mime = "MIME-Version: 1.0\r\nContent-Type: text/html; charset=utf-8\r\nContent-Transfer-Encoding: base64\r\n" + else + self.mime = "MIME-Version: 1.0\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Transfer-Encoding: base64\r\n" + end + -- 发送邮件实体头部 + local ok = self:send(concat({fmt("From: %s <%s>\r\n", self.fromName or "", self.from), fmt("To: <%s>\r\n", self.to), fmt("Subject: %s\r\n", self.subject), self.mime, CRLF})) + if not ok then + return nil, "[MAIL CONTENT ERROR]: 发送Content Headers失败." + end + -- 发送邮件实体内容 + if not self:sendline(base64encode(self.content), "\r\n\r\n.\r\n") then + return nil, "[MAIL CONTENT ERROR]: 发送Content Body失败." + end + data = self:readline(CRLF) + if not data then + return nil, time()..'[MAIL CONTENT ERROR]: ' .. (err or '服务器关闭了连接. ') + end + code, err = read_packet(data) + if not code or code ~= 250 then + return nil, time()..'[MAIL CONTENT ERROR]: ('.. tostring(code) .. (err or '未知错误') ..')' + end + return self:sendline("QUIT", CRLF) +end + +function smtp:send_mail () + local ok, err + ok, err = self:send_header() + if not ok then + return false, err + end + ok, err = self:send_content() + if not ok then + return false, err + end + return true +end + +-- 超时时间 +function smtp:set_timeout (timeout) + if type(timeout) == 'number' and timeout > 0 then + self.timeout = timeout + end + return self +end + +-- 连接到smtp服务器 +function smtp:connect () + self.sock:timeout(self.timeout or 15) + if self.ssl then + return self.sock:ssl_connect(self.host, self.port) + end + return self.sock:connect(self.host, self.port) +end + +-- 接收数据 +function smtp:recv (bytes) + if self.ssl then + return self.sock:ssl_recv(bytes) + end + return self.sock:recv(bytes) +end + +-- 发送数据 +function smtp:send (data) + if self.ssl then + return self.sock:ssl_send(data) + end + return self.sock:send(data) +end + +-- sendline +function smtp:readline(sp) + if self.ssl then + return self.sock:ssl_readline(sp, true) + end + return self.sock:readline(sp, true) +end + +-- sendline +function smtp:sendline(data, sp) + return self.sock:send(data) and self.sock:send(sp) +end + +function smtp:close () + if self.sock then + self.sock:close() + self.sock = nil + end +end + +return smtp diff --git a/lualib/protocol/stomp/init.lua b/lualib/protocol/stomp/init.lua new file mode 100644 index 00000000..d360a810 --- /dev/null +++ b/lualib/protocol/stomp/init.lua @@ -0,0 +1,138 @@ +local class = require "class" +local tcp = require "internal.TCP" + +local cf = require "cf" +local cf_fork = cf.fork + +local logging = require "logging" +local Log = logging:new{ dump = true, path = "protocol-stomp"} + +local protocol = require "protocol.stomp.protocol" +local protocol_send = protocol.send +local protocol_subscribe = protocol.subscribe +local protocol_unsubscribe = protocol.unsubscribe +local protocol_disconnect = protocol.disconnect +local connect_and_login = protocol.connect + +local type = type +local assert = assert +local fmt = string.format +local random = math.random + +local stomp = class("stomp") + +function stomp:ctor (opt) + self.id = opt.id or fmt('luastomp-cf-v1-0x%08X', random(0xFFFFFFFF)) + self.ssl = opt.ssl + self.sock = tcp:new() + self.host = opt.host + self.port = opt.port + self.header = opt.header + self.vhost = opt.vhost or "/exchange" + self.username = opt.username + self.password = opt.password +end + +-- 连接 +function stomp:connect () + -- 如果需要扩展头部 + local opt = { + ['id'] = self.id, + ['client_id'] = self.id, + ['vhost'] = self.vhost, + ['login'] = self.username, + ['username'] = self.username, + ['passcode'] = self.password, + } + if type(self.header) == 'table' then + for key, value in pairs(self.header) do + opt[key] = value + end + end + -- 登录授权 + local ok, data = connect_and_login(self, opt) + if not ok then + return ok, data + end + self.session = data.session + self.state = true + return true +end + +function stomp:send (...) + return self:publish(...) +end + +function stomp:publish (topic, payload) + if not self.state then + return nil, "[STOMP ERROR] : client not connected." + end + if type(topic) ~= 'string' or topic == '' or type(payload) ~= 'string' or payload == '' then + return nil, "[STOMP ERROR] : Invalide `topic` or `payload` arguments." + end + return protocol_send(self, { topic = topic, payload = payload }) +end + +function stomp:subscribe (topic, func) + if not self.state then + return nil, "[STOMP ERROR] : client not connected." + end + if type(topic) ~= 'string' or topic == '' or type(func) ~= 'function' then + return nil, '[STOMP ERROR] : Invalide `topic` or `func` arguments.' + end + local errinfo + assert(protocol_subscribe(self, topic)) + cf_fork(function () + while 1 do + local ok, msg = protocol_subscribe(self, topic, true) + if not ok then + Log:ERROR(msg) + ok, msg = pcall(func, false, msg) + if not ok then + Log:ERROR(msg) + end + return + end + ok, errinfo = pcall(func, { + len = msg['content-length'], + id = msg['message-id'], + session = msg['session'], + payload = msg['body'], + pattern = msg['destination'], + }) + if not ok then + Log:ERROR(errinfo) + end + end + end) + return true +end + +function stomp:unsubscribe () + if not self.state then + return nil, "stomp未连接" + end + if not self.topic then + return nil, '没有需要取消订阅的topic' + end + return protocol_unsubscribe(self, self.topic) +end + +function stomp:disconnect () + if self.state then + self.state = nil + protocol_disconnect(self) + self:close() + end +end + +-- 关闭 +function stomp:close () + if self.sock then + self.state = nil + self.sock:close() + self.sock = nil + end +end + +return stomp diff --git a/lualib/protocol/stomp/protocol.lua b/lualib/protocol/stomp/protocol.lua new file mode 100644 index 00000000..e706a834 --- /dev/null +++ b/lualib/protocol/stomp/protocol.lua @@ -0,0 +1,233 @@ +local pairs = pairs +local toint = math.tointeger +local splite = string.gmatch +local insert = table.insert +local concat = table.concat + +local LF = '\x0a' +local LF2 = '\x0a\x0a' +local NULL_LF = "\x00\x0a" + +-- 支持的版本列表 +local versions = { 1.2, 1.1, 1.0} + +local VERSION = { + ['1.2'] = 1.2, + ['1.1'] = 1.1, + ['1.0'] = 1.0, + ['1'] = 1.0, +} + +local CMDS = { + ["CONNECTED"] = true, + ["SEND"] = true, + ["MESSAGE"] = true, + ["SUBSCRIBE"] = true, + ["UNSUBSCRIBE"] = true, + ["ABORT"] = true, + ["DISCONNECT"] = true, + ["ERROR"] = true, +} + +-- 支持版本 +local function version_support (list) + for _, v in pairs(list) do + if VERSION[v] then + return true + end + end + return false +end + +local function sock_send (sock, data) + return sock:send(data) +end + +local function sock_read (sock, bytes) + local buffer = sock:recv(bytes) + if not buffer then + return + end + if #buffer == bytes then + return buffer + end + bytes = bytes - #buffer + local buffers = {buffer} + local sock_recv = sock.recv + while 1 do + buffer = sock_recv(sock, bytes) + if not buffer then + return + end + bytes = bytes - #buffer + insert(buffers, buffer) + if bytes == 0 then + return concat(buffers) + end + end +end + +local function sock_readline(sock, sp, nosp) + return sock:readline(sp, nosp) +end + +local function sock_connect (sock, ssl, host, port) + local ok, err = sock:connect(host, port) + if not ok then + return false, err + end + if ssl then + ok = sock:ssl_handshake() + if not ok then + return false, "[STOMP ERROR] : SSL handshake failed." + end + end + return true +end + +local function parser_header (data) + local HEADERS = {} + for key, value in splite(data, "([^:]+):([^\x0a]+)[\x0a]?") do + if key:lower() == 'version' then + local tab = {} + for ver in splite(value, '([^,]+)') do + tab[#tab+1] = ver + end + value = tab + end + if key:lower() == 'heart-beat' then + local tab = {} + for num in splite(value, '([^,]+)') do + tab[#tab+1] = num + end + value = tab + end + HEADERS[key] = value + end + return HEADERS +end + +local function build_frame (CMD, opt, body) + local req = { CMD, "version:" .. concat(versions, ',') } + for key, value in pairs(opt) do + req[#req+1] = key..":"..value + end + if body then + -- req[#req+1] = "content-type:text/plain;charset=utf-8" + req[#req+1] = "content-length:"..#body + end + return concat{concat(req, LF), LF2, (body or ''), NULL_LF} +end + +local function read_response (sock) + local response_cmd = sock_readline(sock, LF, true) + if not response_cmd then + return false, "[STOMP ERROR] : Server Close this session when receiving `cmd` data." + end + if not CMDS[response_cmd] then + return false, "[STOMP ERROR] : client get Invalid `cmd` data : " .. response_cmd + end + -- print(response_cmd) + local response_header = sock_readline(sock, LF2) + if not response_header then + return false, "[STOMP ERROR] : Server Close this session when receiving `headers` data." + end + local response = parser_header(response_header) + local v = response['version'] or response['Version'] + if not v or not version_support(v) then + -- print(v, version_support(v)) + return false, "[STOMP ERROR] : Unsupported Stomp protocol version." + end + -- var_dump(response) + response["COMMAND"] = response_cmd + local body_len = toint(response['content-length'] or response['Content-length'] or response['Content-Length']) + if body_len and body_len > 0 then + -- print("长度: ", body_len) + local body = sock_read(sock, body_len) + if not body then + return false, "[STOMP ERROR] : Server Close this session when receiving `body` data." + end + -- print(body) + response['body'] = body + end + sock_readline(sock, NULL_LF) + -- var_dump(response) + return true, response +end + + +local protocol = {} + + +-- 连接 +function protocol.connect (self, opt) + local ok = sock_connect(self.sock, self.ssl, self.host, self.port) + if not ok then + self.state = nil + return nil, "[STOMP ERROR] : Server connnect refuse." + end + if not sock_send(self.sock, build_frame("CONNECT", opt)) then + self.state = nil + return nil, '[STOMP ERROR] : client send `CONNECT` failed.' + end + return read_response(self.sock) +end + +-- 发布消息 +function protocol.send (self, opt) + local ok = sock_send(self.sock, build_frame("SEND", { ['id'] = self.id, ['session'] = self.session, ['destination'] = self.vhost .. opt.topic }, opt.payload)) + if not ok then + self.state = nil + return nil, '[STOMP ERROR] : `SEND` failed.' + end + return true +end + +-- 订阅消息 +function protocol.subscribe (self, topic, already) + if not already then + local ok = sock_send(self.sock, build_frame("SUBSCRIBE", { ['id'] = self.id, ['session'] = self.session, ['destination'] = self.vhost .. topic })) + if not ok then + self.state = nil + return nil, '[STOMP ERROR] : `SUBSCRIBE` failed.' + end + self.topic = topic + return true + end + local ok, pack = read_response(self.sock) + if not ok then + self.state = nil + return nil, pack + end + self.topic = topic + return true, pack +end + +-- 取消订阅 +function protocol.unsubscribe (self, topic) + local ok = sock_send(self.sock, build_frame("UNSUBSCRIBE", { ['id'] = self.id, ['session'] = self.session, ['destination'] = self.vhost .. topic })) + self.topic = nil + if not ok then + self.state = nil + return nil, '[STOMP ERROR] : `UNSUBSCRIBE` failed.' + end + return read_response(self.sock) +end + +-- 回应 +function protocol.ack (self, opt) + local ok = sock_send(self.sock, build_frame("ACK", { ['id'] = self.id, ['session'] = self.session, ['message-id'] = opt['message-id'], ['transaction'] = opt['transaction'] })) + if not ok then + self.state = nil + return nil, "[STOMP ERROR] : `ACK` failed." + end + return true +end + +-- 断开连接 +function protocol.disconnect (self) + self.state = nil + return sock_send(self.sock, build_frame("DISCONNECT", { ['receipt'] = 1 })) +end + +return protocol diff --git a/lualib/protocol/websocket/client.lua b/lualib/protocol/websocket/client.lua new file mode 100644 index 00000000..5672c843 --- /dev/null +++ b/lualib/protocol/websocket/client.lua @@ -0,0 +1,296 @@ +local class = require "class" + +local log = require "logging" +local Log = log:new{ dump = true, path = "protocol-wsclient"} + +local tcp = require "internal.TCP" +local stream = require "stream" + +local cf = require "cf" +local cf_fork = cf.fork + +local new_tab = require "sys".new_tab + +local wbproto = require "protocol.websocket.protocol" +local _recv_frame = wbproto.recv_frame +local _send_frame = wbproto.send_frame + +local HTTP = require "protocol.http" +local PARSER_HTTP_RESPONSE = HTTP.PARSER_HTTP_RESPONSE + +local crypt = require "crypt" +local sha1 = crypt.sha1 +local base64encode = crypt.base64encode + +local type = type +local next = next +local pcall = pcall +local ipairs = ipairs + +local random = math.random +local toint = math.tointeger +local os_date = os.date +local concat = table.concat +local insert = table.insert + +local char = string.char +local byte = string.byte +local find = string.find +local fmt = string.format +local match = string.match +local strpack = string.pack + +local CRLF = '\x0d\x0a' +local CRLF2 = '\x0d\x0a\x0d\x0a' + +local function sock_readline (sock, sp) + return sock:readline(sp) +end + +local function sock_send (self, data) + local sock = self.sock + if self.ssl then + return sock:ssl_send(data) + end + return sock:send(data) +end + +local function sock_connect (self, domain, port) + local sock = self.sock + if self.ssl then + return sock:ssl_connect(domain, port) + end + return sock:connect(domain, port) +end + +local function sock_close (self) + self.sock:close() + self.sock = nil +end + +local function check_response (self, secure) + + -- 读取协议头 + local buffer = sock_readline(self.sock, CRLF2) + if not buffer then + return false, '[WS ERROR] : Server Close this session when recv http handshake.' + end + + -- 解析协议头 + local version, code, _, headers = PARSER_HTTP_RESPONSE(buffer) + if tonumber(version) ~= 1.1 or tonumber(code) ~= 101 or not headers then + sock_close(self) + return nil, "Error: protocol upgrade failed." + end + if not next(headers) then + sock_close(self) + return nil, "Error: unsupported response header." + end + + -- 验证握手信息 + local connection = headers['Connection'] + if not connection or connection:lower() ~= 'upgrade' then + sock_close(self) + return nil, 'Error: Unsupported websocket protocol version.' + end + if headers['Sec-WebSocket-Accept'] ~= secure then + sock_close(self) + return nil, 'Error: `Sec-WebSocket-Accept` verification failed.' + end + if type(headers['Sec-WebSocket-Extensions']) == 'string' and find(headers['Sec-WebSocket-Extensions'], "permessage%-deflate") then + self.ext = 'deflate' + end + + return true +end + +-- HTTP[s] Over WebSocket Upgrade +local function do_handshake (self) + + local ok, err + + ok, err = sock_connect(self, self.domain, self.port) + if not ok then + sock_close(self) + return nil, err + end + + local key = char( + byte('c'), byte('f'), byte('a'), byte('d'), byte('m'), byte('i'), byte('n'), + random(256) - 1, random(256) - 1, random(256) - 1, + random(256) - 1, random(256) - 1, random(256) - 1, + random(256) - 1, random(256) - 1, random(256) - 1 + ) + + local sec_key = base64encode(key) + local req = { + fmt('GET %s HTTP/1.1', self.path), + fmt('Data: %s', os_date("Date: %a, %d %b %Y %X GMT")), + fmt('Host: %s:%s', self.domain, self.port), + fmt('Sec-WebSocket-Key: %s', sec_key), + 'Origin: http://'..self.domain, + 'Upgrade: websocket', + 'Connection: Upgrade', + 'Sec-WebSocket-Version: 13', + 'User-Agent: cf-websocket/0.1', + 'Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits', + CRLF + } + + ok, err = sock_send(self, concat(req, CRLF)) + if not ok then + sock_close(self) + return ok, err + end + + return check_response(self, base64encode(sha1(sec_key .. '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'))) +end + +local function url_split (self) + local scheme, domain_port, path = match(self.url, '^(ws[s]?)://([^/]+)(.*)') + if not scheme or not domain_port then + return nil, "Connection failed: invalid url parameter." + end + + if not path or path == '' then + path = '/' + end + + local domain, port + if find(domain_port, ':') then + local _, Bracket_Pos = find(domain_port, '[%[%]]') + if Bracket_Pos then + domain, port = match(domain_port, '%[(.+)%][:]?(%d*)') + else + domain, port = match(domain_port, '([^:]+):(%d*)') + end + if not domain then + return nil, "Invalid or illegal hostname: "..domain_port + end + port = toint(port) + if not port then + port = scheme == 'wss' and 443 or 80 + end + else + domain, port = domain_port, scheme == 'ws' and 80 or 443 + end + + -- 判断是否需要ssl socket + if scheme == 'wss' then + self.ssl = true + end + + self.domain = domain + self.port = port + self.path = path + return true +end + +local websocket = class("websocket-client") + +function websocket:ctor (opt) + self.ssl = nil + self.ext = nil + self.url = opt.url + self.sock = stream(tcp:new()) + self.send_masked = true + self.sock._timeout = toint(opt.timeout) + self.max_payload_len = opt.max_payload_len or 65535 +end + +-- 设置超时 +function websocket:set_timeout (timeout) + self.sock._timeout = timeout +end + +function websocket:connect () + if self.state then + return nil, 'already connected.' + end + local ok, err + -- 切割URL + ok, err = url_split(self) + if not ok then + return nil, err + end + -- Websocket握手流程 + ok, err = do_handshake(self) + if not ok then + return nil, err + end + self.state = true + return true, err +end + +function websocket:request(func) + if not self.queue then + self.queue = new_tab(16, 0) + self.co = cf_fork(function () + -- print("do") + for _, f in ipairs(self.queue) do + local ok, err = pcall(f) + if not ok then + Log:ERROR(err) + end + end + -- print("end") + self.co, self.queue = nil, nil + end) + end + return insert(self.queue, func) +end + +-- 接受数据 +function websocket:recv() + if not self.state then + return nil, 'not connected.' + end + local data, typ, err = _recv_frame(self.sock, self.max_payload_len, false) + if not data then + self.state = nil + return false, err + end + return data, typ +end + +-- 发送 text/binary +function websocket:send (data, bin) + if not self.state then + return nil, 'not connected.' + end + assert(type(data) == 'string' and data ~= '', "Invalid websocket send data.") + local sock = self.sock + return _send_frame(sock, true, bin and 0x02 or 0x01, data, self.send_masked, self.ext) +end + +-- 发送ping +function websocket:ping(data) + if not self.state then + return nil, 'not connected.' + end + local sock = self.sock + return _send_frame(sock, true, 0x09, type(data) == 'string' and #data <= 125 and data or "", self.send_masked, self.ext) +end + +-- 发送pong +function websocket:pong(data) + if not self.state then + return nil, 'not connected.' + end + local sock = self.sock + return _send_frame(sock, true, 0x0A, type(data) == 'string' and #data <= 125 and data or "", self.send_masked, self.ext) +end + +-- 清理连接 +function websocket:close () + if self.sock then + local sock = self.sock + return self:request(function () + if self.state then + return _send_frame(sock, true, 0x08, strpack(">H", 1000), self.send_masked, self.ext) + end + end) + end +end + +return websocket diff --git a/lualib/protocol/websocket/protocol.lua b/lualib/protocol/websocket/protocol.lua index e5cae73a..18343a78 100644 --- a/lualib/protocol/websocket/protocol.lua +++ b/lualib/protocol/websocket/protocol.lua @@ -1,278 +1,235 @@ --- Copyright (C) Yichun Zhang (agentzh) --- modify by CandyMi in 2019.1.12 - -local byte = string.byte -local char = string.char -local sub = string.sub -local concat = table.concat -local str_char = string.char -local rand = math.random -local tostring = tostring -local type = type - -local new_tab = function (narr, nrec) return {} end - - -local _M = new_tab(0, 5) - -_M.new_tab = new_tab -_M._VERSION = '0.07' - - -local types = { - [0x0] = "continuation", - [0x1] = "text", - [0x2] = "binary", - [0x8] = "close", - [0x9] = "ping", - [0xa] = "pong", -} - - -function _M.recv_frame(sock, max_payload_len, force_masking) - local data, err = sock:recv(2) - if not data then - return nil, nil, err - end - - local fst, snd = byte(data, 1, 2) - - local fin = fst & 0x80 ~= 0 - - if fst & 0x70 ~= 0 then - return nil, nil, "bad RSV1, RSV2, or RSV3 bits" - end - - local opcode = fst & 0x0f - - if opcode >= 0x3 and opcode <= 0x7 then - return nil, nil, "reserved non-control frames" - end - - if opcode >= 0xb and opcode <= 0xf then - return nil, nil, "reserved control frames" - end - - local mask = snd & 0x80 ~= 0 - - if force_masking and not mask then - return nil, nil, "frame unmasked" - end - - local payload_len = snd & 0x7f - - if payload_len == 126 then - local data, err = sock:recv(2) - if not data then - return nil, nil, "failed to receive the 2 byte payload length: " - .. (err or "unknown") - end - payload_len = (byte(data, 1) >> 8) | byte(data, 2) - - elseif payload_len == 127 then - local data, err = sock:recv(8) - if not data then - return nil, nil, "failed to receive the 8 byte payload length: " - .. (err or "unknown") - end - - if byte(data, 1) ~= 0 - or byte(data, 2) ~= 0 - or byte(data, 3) ~= 0 - or byte(data, 4) ~= 0 - then - return nil, nil, "payload len too large" - end - - local fifth = byte(data, 5) - if fifth & 0x80 ~= 0 then - return nil, nil, "payload len too large" - end - payload = fifth << 24 | byte(data, 6) << 16 | byte(data, 7) | byte(data, 8) - end - - if opcode & 0x8 ~= 0 then - -- being a control frame - if payload_len > 125 then - return nil, nil, "too long payload for control frame" - end - - if not fin then - return nil, nil, "fragmented control frame" - end - end - - -- print("payload len: ", payload_len, ", max payload len: ", - -- max_payload_len) - - if payload_len > max_payload_len then - return nil, nil, "exceeding max payload len" - end - - local rest - if mask then - rest = payload_len + 4 - - else - rest = payload_len - end - -- print("rest: ", rest) - - local data - if rest > 0 then - data, err = sock:recv(rest) - if not data then - return nil, nil, "failed to read masking-len and payload: " - .. (err or "unknown") - end - else - data = "" - end - - if opcode == 0x8 then - - if payload_len > 0 then - if payload_len < 2 then - return nil, nil, "close frame with a body must carry a 2-byte status code" - end - - local msg, code - if mask then - local fst = byte(data, 4 + 1) ~ byte(data, 1) - local snd = byte(data, 4 + 2) ~ byte(data, 2) - code = (fst << 8) | snd - - if payload_len > 2 then - -- TODO string.buffer optimizations - local bytes = new_tab(payload_len - 2, 0) - for i = 3, payload_len do - bytes[i - 2] = str_char(byte(data, 4 + i) ~ byte(data, (i - 1) % 4 + 1)) - end - msg = concat(bytes) - - else - msg = "" - end - - else - local fst = byte(data, 1) - local snd = byte(data, 2) - code = (fst << 8) | snd - - if payload_len > 2 then - msg = sub(data, 3) - - else - msg = "" - end - end - - return msg, "close" - end - return "", "close", nil - end - - local msg - if mask then - -- TODO string.buffer optimizations - local bytes = new_tab(payload_len, 0) - for i = 1, payload_len do - bytes[i] = str_char(byte(data, 4 + i) ~ byte(data, (i - 1) % 4 + 1)) - end - msg = concat(bytes) - - else - msg = data - end - - return msg, types[opcode], not fin and "again" or nil -end - - -local function build_frame(fin, opcode, payload_len, payload, masking) - -- XXX optimize this when we have string.buffer in LuaJIT 2.1 - local fst - if fin then - fst = 0x80 | opcode - else - fst = opcode - end - - local snd, extra_len_bytes - if payload_len <= 125 then - snd = payload_len - extra_len_bytes = "" - - elseif payload_len <= 65535 then - snd = 126 - extra_len_bytes = char((payload_len >> 8) & 0xff, payload_len & 0xff) - - else - if payload_len & 0x7fffffff < payload_len then - return nil, "payload too big" - end - - snd = 127 - -- XXX we only support 31-bit length here - extra_len_bytes = char(0, 0, 0, 0, - (payload_len >> 24) & 0xff, - (payload_len >> 16) & 0xff, - (payload_len >> 8) & 0xff, - payload_len & 0xff) - end - - local masking_key - if masking then - -- set the mask bit - snd = snd | 0x80 - local key = rand(0xffffffff) - masking_key = char((key >> 24)& 0xff, (key >> 16)& 0xff, (key >> 8)& 0xff, key & 0xff) - - -- TODO string.buffer optimizations - local bytes = new_tab(payload_len, 0) - for i = 1, payload_len do - bytes[i] = str_char(byte(payload, i) ~ byte(masking_key, (i - 1) % 4 + 1)) - end - payload = concat(bytes) - - else - masking_key = "" - end - - return char(fst, snd) .. extra_len_bytes .. masking_key .. payload -end -_M.build_frame = build_frame - - -function _M.send_frame(sock, fin, opcode, payload, max_payload_len, masking) - - if not payload then - payload = "" - - elseif type(payload) ~= "string" then - payload = tostring(payload) - end - - local payload_len = #payload - - if payload_len > max_payload_len then - return nil, "payload too big" - end - - if opcode & 0x8 ~= 0 then - if payload_len > 125 then - return nil, "too much payload for control frame" - end - if not fin then - return nil, "fragmented control frame" - end - end - - local frame, err = build_frame(fin, opcode, payload_len, payload, masking) - if not frame then - return nil, "failed to build frame: " .. err - end - return sock:send(frame) -end - -return _M \ No newline at end of file +local crypt = require "crypt" +local xor_str = crypt.xor_str + +local lz = require "lz" +local wscompress = lz.wscompress +local wsuncompress = lz.wsuncompress + +local new_tab = require("sys").new_tab + +local error = error +local assert = assert + +local strpack = string.pack +local strunpack = string.unpack +local random = math.random +local concat = table.concat +local insert = table.insert + +local WS_TYPE = { + [0x00] = "continuation", + [0x01] = "text", + [0x02] = "binary", + [0x08] = "close", + [0x09] = "ping", + [0x0A] = "pong", +} + +local function sock_recv (sock, bytes) + local buf = sock:readbytes(bytes) + if not buf then + sock.closed = true + end + return buf +end + +local function sock_send (sock, data) + return sock:write(data) +end + +local function wsdeflate(data) + return wscompress(data) +end + +local function wsinflate(data) + return wsuncompress(data) +end + + +local protocol = { __VERSION__ = 0.1 } + +---@comment 接收数据 +---@param sock table @socket对象 +---@param max_payload_len integer @最大长度限制 +---@param force_masking boolean @强制检查掩码 +---@param buffers? table @内部的缓冲区 +---@return string | boolean @消息数据 +---@return string? @消息类型 +---@return string? @错误信息 +function protocol.recv_frame(sock, max_payload_len, force_masking, buffers) + local hdata = sock_recv(sock, 2) + if not hdata then + return false, 'close' + end + + local h1, h2 = strunpack("BB", hdata) + -- 检查协议头部是否符合规范 + local fin, rsv = h1 & 0x80 == 0x80, (h1 >> 4) & 0x07 + if rsv ~= 0x00 and rsv ~= 0x04 then + return false, 'error', "[WS ERROR] : Invalid RSV1 or RSV2 or RSV3." + end + + -- 检查 OPCODE 是否有效 + local opcode = WS_TYPE[h1 & 0x0F] + if not opcode then + return false, 'error', "[WS ERROR] : received Invalid opcode." + end + + -- 判断数据载荷的实际长度 + local plen = h2 & 0x7F + if plen == 126 then + local body_size_str = sock_recv(sock, 2) + if not body_size_str then + return false, 'error', "[WS ERROR] : Client Close this session when read 2 bytes body size." + end + plen = strunpack(">I2", body_size_str) + elseif plen == 127 then + local body_size_str = sock_recv(sock, 8) + if not body_size_str then + return false, 'error', "[WS ERROR] : Client Close this session when read 8 bytes body size." + end + plen = strunpack(">I8", body_size_str) + end + + local mask_key = nil + -- 如果最高位是1表明PAYLOAD有掩码位. + if h2 & 0x80 == 0x80 then + mask_key = sock_recv(sock, 4) + if not mask_key then + return false, 'error', "[WS ERROR] : Client Close this session when read mask_key data." + end + end + + -- 如果强制要求但是还是未检查 + if force_masking and not mask_key then + return false, 'error', "[WS ERROR] : Mask must be present." + end + + -- 需要提前断言数据长度是否超出限制 + if plen >= max_payload_len then + return false, 'error', "[WS ERROR] : Content exceeding the length limit." + end + + -- 控制帧必须不允许有扩展长度 + if (opcode == 'close' or opcode == 'ping' or opcode == 'pong') and plen > 125 then + return false, 'error', "[WS ERROR] : The payload length of the control frame is too long." + end + + local data = "" + + -- 读取数据载荷 + if plen > 0 then + + data = sock_recv(sock, plen) + if not data then + return false, 'error', "[WS ERROR] : Client Close this session when read real payload." + end + + -- 如果还有后续的`CONTINUATION`帧 + if not fin or opcode == 'continuation' then + + -- 等所有buffer读取完毕后再连接起来 + if not buffers then + buffers = new_tab(8, 0) + end + buffers[#buffers+1] = data + + -- 接受数据完毕. + if opcode == 'continuation' and fin then + return true + end + + -- 接受数据期间产生错误, 则不再处理后续数据. + local d, typ, errinfo = protocol.recv_frame(sock, max_payload_len, force_masking, buffers) + if not d then + return false, typ, errinfo + end + data = concat(buffers) + + end + + -- 有掩码位需要异或还原数据. + if mask_key then + data = xor_str(data, mask_key) + end + + -- 支持 permessage-deflate 必须解压缩数据载荷 + if rsv == 0x04 then + local buf = wsinflate(data) + if not buf then + return false, 'error', "[WS ERROR] : received Invalid deflate buffers." + end + data = buf + end + + -- close帧有状态码 + if opcode == 'close' then + data = data:sub(3) + end + + end + + return data, opcode +end + +---@comment 发送数据 +---@param sock table @socket对象 +---@param fin boolean @结束帧标志 +---@param opcode integer @消息类型 +---@param payload string @数据载荷 +---@param masking boolean @数据掩码 +---@param ext string @协议扩展 +function protocol.send_frame(sock, fin, opcode, payload, masking, ext) + + local payload_len = #payload + + local opc = assert(WS_TYPE[opcode], "[WS ERROR] : attempted pass invalid websocket opcode.") + if opc == 'close' or opc == 'ping' or opc == 'pong' then + if payload_len > 125 then + return error("[WS ERROR] : The payload length of the control frame is too long.") + end + end + + -- 结束位标志位 + 保留位 + 消息类型 + local h1 = (fin and 0x80 or 0x00) | opcode + -- 如果有扩展协议则加上扩展响应头部 + if (opc ~= 'close' and opc ~= 'ping' and opc ~= 'pong') and payload_len > 125 and ext == 'deflate' then + h1 = h1 | 0x40 + payload = wsdeflate(payload) + payload_len = #payload + end + + -- 掩码位与长度位 + local h2 = masking and 0x80 or 0x00 + local len_ext + if payload_len < 126 then + h2 = h2 | payload_len + elseif payload_len < 65536 then + h2, len_ext = h2 | 0x7E, strpack(">I2", payload_len) + else + h2, len_ext = h2 | 0x7F, strpack(">I8", payload_len) + end + + local idx = 1 + local buffers = new_tab(4, 0) + buffers[idx] = strpack(">BB", h1, h2) + + if len_ext then + idx = idx + 1 + buffers[idx] = len_ext + end + + -- 创建随机掩码并与数据载荷进行异或 + if masking and payload_len > 0 then + local mask = strpack(">BBBB", random(255), random(255), random(255), random(255)) + payload = xor_str(payload, mask) + idx = idx + 1 + buffers[idx] = mask + end + + -- 根据实际情况需要减少发送数据次数. + buffers[idx + 1] = payload + sock_send(sock, concat(buffers)) +end + +return protocol \ No newline at end of file diff --git a/lualib/protocol/websocket/server.lua b/lualib/protocol/websocket/server.lua index 58da2d36..098c7565 100644 --- a/lualib/protocol/websocket/server.lua +++ b/lualib/protocol/websocket/server.lua @@ -1,177 +1,113 @@ -local log = require "log" -local class = require "class" -local co = require "internal.Co" -local wbproto = require "protocol.websocket.protocol" -local _recv_frame = wbproto.recv_frame -local _send_frame = wbproto.send_frame +local stream = require "stream" + +local cf = require "cf" +local cf_fork = cf.fork +local cf_sleep = cf.sleep + +local wsproto = require "protocol.websocket.protocol" +local _recv_frame = wsproto.recv_frame +local _send_frame = wsproto.send_frame -local co_self = co.self -local co_wait = co.wait -local co_spwan = co.spwan -local co_wakeup = co.wakeup +local LOG = require "logging":new { dump = true, path = 'protocol-websocket-server'} local type = type -local next = next local pcall = pcall local ipairs = ipairs local assert = assert -local setmetatable = setmetatable -local tostring = tostring -local char = string.char +local insert = table.insert +local strpack = string.pack +local class = require "class" -local websocket = class("websocket-server") +local ws = class("ws") -function websocket:ctor(opt) - self.cls = opt.cls - self.sock = opt.sock - self.sock._timeout = nil +function ws:ctor(opt) + self.ext = opt.ext + self.sock = opt.sock + self.closed = false + self.max_payload_len = 65535 end --- 将回调函数写入到队列内 -local function add_to_queue(queue, func) - queue[#queue + 1] = func +function ws:set_timeout(timeout) + self.sock:timeout(timeout) end --- 一次将多条回调函数写入到队列内 -local function more_add_to_queue(queue, list) - for _, func in ipairs(list) do - add_to_queue(queue, func) - end +-- 设置发送掩码 +function ws:set_send_masked(send_masked) + self.send_masked = send_masked end --- 唤醒write queue -local function wakeup(co) - return co and co_wakeup(co) +-- 设置最大数据载荷长度 +function ws:set_max_payload_len(max_payload_len) + self.max_payload_len = max_payload_len end -function websocket:start() - local cls - local sock = self.sock - local current_co = co_self() - local write_list = {} - local write_co = co_spwan(function (...) - while 1 do - for _, f in ipairs(write_list) do - local ok, err = pcall(f) - if not ok then - log.error(err) - end - end - write_list = {} - co_wait() - if #write_list == 0 then - -- print("写入协程退出了") - return - end - end - end) - local ws = { - CLOSE = false, - send = function (self, data, binary) - if self.CLOSE then return end - if data and type(data) == 'string' then - local code = 0x1 - if binary then - code = 0x2 - end - add_to_queue(write_list, function () - return _send_frame( - sock, - true, - code, - data, - cls.max_payload_len or 65535, - cls.send_masked or false - ) - end) - return wakeup(write_co) - end - end, - close = function (self, data) - if self.CLOSE then return end - self.CLOSE = true - more_add_to_queue(write_list, { - function() - return _send_frame( - sock, - true, - 0x8, - char(((1000 >> 8) & 0xff),(1000 & 0xff))..(data or ""), - cls.max_payload_len or 65535, - cls.send_masked or false - ) - end, - function() return sock:close() end, - }) - return wakeup(current_co), wakeup(write_co) - end, - -- ping = function (self, data) - -- if self.CLOSE then return end - -- add_to_queue(write_list, function() - -- _send_frame(sock, true, 0x9, data, cls.max_payload_len or 65535, cls.send_masked or false) - -- end) - -- return wakeup(write_co) - -- end, - -- pong = function (self, data) - -- if self.CLOSE then return end - -- add_to_queue(write_list, function() - -- _send_frame(sock, true, 0xa, data, cls.max_payload_len or 65535, cls.send_masked or false) - -- end) - -- return wakeup(write_co) - -- end, - } - cls = self.cls:new { ws = ws } - local on_open = cls.on_open - local on_message = cls.on_message - local on_error = cls.on_error - local on_close = cls.on_close - local ok, err = pcall(on_open, cls) - if not ok then - log.error(err) - return sock:close() +-- 发送TEXT/BINARY帧 +function ws:send(data, binary) + if self.closed or self.sock.closed then + return + end + _send_frame(self.sock, true, binary and 0x02 or 0x01, data, false, self.ext) +end + +-- 发送PING帧 +function ws:ping(data) + if self.closed or self.sock.closed then + return + end + return _send_frame(self.sock, true, 0x09, data or '', false, self.ext) +end + +-- 发送CLOSE帧 +function ws:close(data) + if self.closed or self.sock.closed then + return + end + self.closed = true + _send_frame(self.sock, true, 0x08, strpack(">H", 1000) .. (type(data) == 'string' and data or ""), false, self.ext) +end + +local Websocket = { __Version__ = 0.1 } + +function Websocket.start(sock, cls, args, headers, ext) + local on_open = assert(type(cls.on_open) == 'function' and cls.on_open, "'on_open' method is not implemented.") + local on_message = assert(type(cls.on_message) == 'function' and cls.on_message, "'on_message' method is not implemented.") + local on_error = assert(type(cls.on_error) == 'function' and cls.on_error, "'on_error' method is not implemented.") + local on_close = assert(type(cls.on_close) == 'function' and cls.on_close, "'on_close' method is not implemented.") + + sock = stream(sock) + local w = ws { sock = sock, ext = ext } + local obj = cls{ ws = w, args = args, headers = headers } + + local timeout = obj.timeout or 0 + local max_payload_len = obj.max_payload_len or 65535 + w:set_timeout(timeout); w:set_max_payload_len(max_payload_len) + + local ok, err = pcall(on_open, obj) + if not ok then + return LOG:ERROR(err) + end + -- 开始监听 + :: CONTINUE :: + local data, typ, errinfo = _recv_frame(sock, max_payload_len, true) + if not typ or typ == 'close' or typ == 'error' then + if typ == 'error' then + ok, err = pcall(on_error, obj, errinfo) + if not ok then + LOG:ERROR(err) + end end - while 1 do - local data, typ, err = _recv_frame(sock, cls.max_payload_len, true) - if (not data and not typ) or typ == 'close' then - -- 客户端主动关闭: ws.CLOSE == flase - -- 服务端主动关闭: ws.CLOSE == true - if not ws.CLOSE then - ws.CLOSE = true - write_list = {} - sock:close() - end - if err then - local ok, err = pcall(on_error, cls, err) - if not ok then - log.error(err) - end - end - local ok, err = pcall(on_close, cls, data) - if not ok then - log.error(err) - end - -- print("读取协程退出了") - return wakeup(write_co) - end - if typ == 'ping' then - add_to_queue(write_list, function() - _send_frame( - sock, - true, - 0xa, - data, - cls.max_payload_len or 65535, - cls.send_masked or false - ) - end) - elseif typ == 'text' or typ == 'binary' then - co_spwan(on_message, cls, data, typ) - else - -- 目前将设计精简为: 除了需要回应的ping协议, 其他协议均不会触发任何Server端回调响应; - -- 如需特殊需求, 请自行在业务逻辑中解决(或使用定时器进行循环探测); - end + ok, err = pcall(on_close, obj, data or errinfo) + if not ok then + LOG:ERROR(err) end + return w:close(), cf_sleep(0) + elseif typ == 'ping' then + _send_frame(sock, true, 0x0A, data or '', false, ext) + elseif typ == 'text' or typ == 'binary' then + cf_fork(on_message, obj, data, typ == 'binary') + end + goto CONTINUE end -return websocket \ No newline at end of file +return Websocket \ No newline at end of file diff --git a/lualib/stream/init.lua b/lualib/stream/init.lua new file mode 100644 index 00000000..6e1ff7ed --- /dev/null +++ b/lualib/stream/init.lua @@ -0,0 +1,184 @@ +local TCP = require "internal.TCP" +local sock_read = TCP.recv +local sock_write = TCP.send +local sock_readline = TCP.readline +local sock_connect = TCP.connect +local sock_connectx = TCP.connect_ex +local sock_sslconnect = TCP.ssl_connect + +local new_tab = require "sys".new_tab + +local cf = require "cf" +local cf_fork = cf.fork + +local type = type +local error = error +local ipairs = ipairs +local getmetatable = getmetatable + +local mtype = math.type +local strfmt = string.format +local tconcat = table.concat + +local class = require "class" + +local Stream = class("Stream") + +function Stream:ctor(sock) + if getmetatable(sock) ~= TCP then + error(strfmt("[Stream Error]: Invalid Socket object in (%s:%d).", debug.getinfo(3, "S").source, debug.getinfo(3, "l").currentline)) + end + self.tcp = sock +end + +function Stream:set_fd(fd) + self.tcp:set_fd(fd) + return self +end + +function Stream:timeout(ts) + self.tcp:timeout(ts) + return self +end + +function Stream:connect(domain, port) + return sock_connect(self.tcp, domain, port) +end + +function Stream:ssl_connect(domain, port) + return sock_sslconnect(self.tcp, domain, port) +end + +function Stream:connectx(path) + return sock_connectx(self.tcp, path) +end + +---comment @同步写入(阻塞当前协程) +---@param buf string @待写入的数据 +---@return boolean @写入成功返回`true`, 写入失败返回`false` +function Stream:send(buf) + if type (buf) ~= 'string' or buf == '' then + error(strfmt("[Stream Error]: pass Invalid send buffer in (%s:%d)", debug.getinfo(2, "S").source, debug.getinfo(2, "l").currentline)) + end + return sock_write(self.tcp, buf) +end + +---comment @异步写入(不会阻塞当前协程) +---@param buf string @待写入的数据 +function Stream:write(buf) + if type (buf) ~= 'string' or buf == '' then + error(strfmt("[Stream Error]: pass Invalid write buffer in (%s:%d)", debug.getinfo(2, "S").source, debug.getinfo(2, "l").currentline)) + end + -- 异步写入队列 + if not self.wqueue then + self.wqueue = new_tab(8, 0) + cf_fork(function () + local sock = self.tcp + if sock and self.wqueue then + for _, buffer in ipairs(self.wqueue) do + if not sock_write(sock, buffer) then + break + end + end + end + self.wqueue = nil + end) + end + self.wqueue[#self.wqueue+1] = buf +end + +function Stream:recv(nbytes) + return self:read(nbytes) +end + +---comment @读取指定数量的网络数据(此函数只要缓冲区里有数据立刻返回) +---@param nbytes integer @指定的要读取的数量. +---@return string | nil @读取成功返回内容, 失败返回`nil` +function Stream:read(nbytes) + if mtype(nbytes) ~= 'integer' or nbytes < 1 then + error(strfmt("[Stream Error]: Pass invalid nbytes in (%s:%d)", debug.getinfo(2, "S").source, debug.getinfo(2, "l").currentline)) + end + return sock_read(self.tcp, nbytes) +end + +---comment @读取数据直到遇到指定分隔符, 可以选择返回的数据不包括分隔符. +---@param sp string @字符串类型的分隔符 +---@param nosp boolean @返回数据是否包括分隔符 +---@return string | nil @读取成功返回内容, 失败返回`nil` +function Stream:readline(sp, nosp) + if type(sp) ~= 'string' or sp == '' then + error(strfmt("[Stream Error]: Pass invalid readline char in (%s:%d)", debug.getinfo(2, "S").source, debug.getinfo(2, "l").currentline)) + end + return sock_readline(self.tcp, sp, nosp) +end + +---comment @读取指定数量的网络数据(读取足够字节或者连接断开才会返回). +---@param nbytes integer @指定的要读取的数量. +---@return string | nil @读取成功返回内容, 失败返回`nil` +function Stream:readbytes(nbytes) + if mtype(nbytes) ~= 'integer' or nbytes < 1 then + error(strfmt("[Stream Error]: Pass invalid nbytes in (%s:%d)", debug.getinfo(2, "S").source, debug.getinfo(2, "l").currentline)) + end + local sock, buffer, len, buffers = self.tcp, nil, nil, nil + :: CONTINUE :: + buffer, len = sock_read(sock, nbytes) + if not buffer then + return + end + -- 检查读取的字节数 + if len == nbytes then + -- 如果是一次性读取完毕直接返回 + if not buffers then + return buffer + end + -- 如果是多次读取完毕 + buffers[#buffers+1] = buffer + return tconcat(buffers) + end + if not buffers then + buffers = new_tab(3, 0) + end + -- 计算字节数并且准备继续读取 + buffers[#buffers+1] = buffer + nbytes = nbytes - len + goto CONTINUE +end + +---comment @监听网络套接字 +---@param ip string @监听指定地址 +---@param port integer @监听指定端口 +---@param cb function @连接建立成功的回调 +function Stream:listen(ip, port, cb) + self.tcp:listen(ip, port, function (fd, ...) + return cb(Stream(TCP():set_fd(fd):timeout(0)), ...) + end) +end + +---comment @监听本机套接字 +---@param path string @本机套接字所在路径 +---@param cb function @连接建立成功的回调 +function Stream:listenx(path, cb) + self.tcp:listen_ex(path, function (fd, ...) + return cb(Stream(TCP():set_fd(fd):timeout(0)), ...) + end) +end + +function Stream:run_forever() + return cf.wait() +end + +function Stream:run() + return self:run_forever() +end + +function Stream:close() + if self.tcp then + self.tcp:close() + self.tcp = nil + end + if self.wqueue then + self.wqueue = nil + end +end + +return Stream \ No newline at end of file diff --git a/lualib/system/init.lua b/lualib/system/init.lua index fb9895a9..ff846674 100644 --- a/lualib/system/init.lua +++ b/lualib/system/init.lua @@ -1,17 +1,19 @@ local sys = require "sys" - -local string = string -local fmt = string.format - +local now = sys.now local is_ipv4 = sys.ipv4 local is_ipv6 = sys.ipv6 -local now = sys.now local type = type +local pairs = pairs +local ipairs = ipairs local os_date = os.date local os_time = os.time +local modf = math.modf + +local fmt = string.format + local System = { -- 类型转换函数 toint = math.modf, @@ -26,7 +28,7 @@ function System.is_int(number) if type(number) ~= 'number' then return false end - local int, float = math.modf(number) + local int, float = modf(number) return float == 0. end @@ -35,7 +37,7 @@ function System.is_float(number) if type(number) ~= 'number' then return false end - local int, float = math.modf(number) + local int, float = modf(number) return float ~= 0. end diff --git a/lualib/template.lua b/lualib/template.lua deleted file mode 100644 index 79378113..00000000 --- a/lualib/template.lua +++ /dev/null @@ -1,478 +0,0 @@ -local setmetatable = setmetatable -local loadstring = loadstring -local loadchunk -local tostring = tostring -local setfenv = setfenv -local require = require -local capture -local concat = table.concat -local assert = assert -local prefix -local write = io.write -local pcall = pcall -local phase -local open = io.open -local load = load -local type = type -local dump = string.dump -local find = string.find -local gsub = string.gsub -local byte = string.byte -local null -local sub = string.sub -local ngx = ngx -local jit = jit -local var - -local _VERSION = _VERSION -local _ENV = _ENV -local _G = _G - -local HTML_ENTITIES = { - ["&"] = "&", - ["<"] = "<", - [">"] = ">", - ['"'] = """, - ["'"] = "'", - ["/"] = "/" -} - -local CODE_ENTITIES = { - ["{"] = "{", - ["}"] = "}", - ["&"] = "&", - ["<"] = "<", - [">"] = ">", - ['"'] = """, - ["'"] = "'", - ["/"] = "/" -} - -local VAR_PHASES - -local ok, newtab = pcall(require, "table.new") -if not ok then newtab = function() return {} end end - -local caching = true -local template = newtab(0, 12) - -template._VERSION = "1.9" -template.cache = {} - -local function enabled(val) - if val == nil then return true end - return val == true or (val == "1" or val == "true" or val == "on") -end - -local function trim(s) - return gsub(gsub(s, "^%s+", ""), "%s+$", "") -end - -local function rpos(view, s) - while s > 0 do - local c = sub(view, s, s) - if c == " " or c == "\t" or c == "\0" or c == "\x0B" then - s = s - 1 - else - break - end - end - return s -end - -local function escaped(view, s) - if s > 1 and sub(view, s - 1, s - 1) == "\\" then - if s > 2 and sub(view, s - 2, s - 2) == "\\" then - return false, 1 - else - return true, 1 - end - end - return false, 0 -end - -local function readfile(path) - local file = open(path, "rb") - if not file then return nil end - local content = file:read "*a" - file:close() - return content -end - -local function loadlua(path) - return readfile(path) or path -end - -local function loadngx(path) - local vars = VAR_PHASES[phase()] - local file, location = path, vars and var.template_location - if sub(file, 1) == "/" then file = sub(file, 2) end - if location and location ~= "" then - if sub(location, -1) == "/" then location = sub(location, 1, -2) end - local res = capture(concat{ location, '/', file}) - if res.status == 200 then return res.body end - end - local root = vars and (var.template_root or var.document_root) or prefix - if sub(root, -1) == "/" then root = sub(root, 1, -2) end - return readfile(concat{ root, "/", file }) or path -end - -do - if ngx then - VAR_PHASES = { - set = true, - rewrite = true, - access = true, - content = true, - header_filter = true, - body_filter = true, - log = true - } - template.print = ngx.print or write - template.load = loadngx - prefix, var, capture, null, phase = ngx.config.prefix(), ngx.var, ngx.location.capture, ngx.null, ngx.get_phase - if VAR_PHASES[phase()] then - caching = enabled(var.template_cache) - end - else - template.print = write - template.load = loadlua - end - if _VERSION == "Lua 5.1" then - local context = { __index = function(t, k) - return t.context[k] or t.template[k] or _G[k] - end } - if jit then - loadchunk = function(view) - return assert(load(view, nil, nil, setmetatable({ template = template }, context))) - end - else - loadchunk = function(view) - local func = assert(loadstring(view)) - setfenv(func, setmetatable({ template = template }, context)) - return func - end - end - else - local context = { __index = function(t, k) - return t.context[k] or t.template[k] or _ENV[k] - end } - loadchunk = function(view) - return assert(load(view, nil, nil, setmetatable({ template = template }, context))) - end - end -end - -function template.caching(enable) - if enable ~= nil then caching = enable == true end - return caching -end - -function template.output(s) - if s == nil or s == null then return "" end - if type(s) == "function" then return template.output(s()) end - return tostring(s) -end - -function template.escape(s, c) - if type(s) == "string" then - if c then return gsub(s, "[}{\">/<'&]", CODE_ENTITIES) end - return gsub(s, "[\">/<'&]", HTML_ENTITIES) - end - return template.output(s) -end - -function template.new(view, layout) - assert(view, "view was not provided for template.new(view, layout).") - local render, compile = template.render, template.compile - if layout then - if type(layout) == "table" then - return setmetatable({ render = function(self, context) - local context = context or self - context.blocks = context.blocks or {} - context.view = compile(view)(context) - layout.blocks = context.blocks or {} - layout.view = context.view or "" - return layout:render() - end }, { __tostring = function(self) - local context = self - context.blocks = context.blocks or {} - context.view = compile(view)(context) - layout.blocks = context.blocks or {} - layout.view = context.view - return tostring(layout) - end }) - else - return setmetatable({ render = function(self, context) - local context = context or self - context.blocks = context.blocks or {} - context.view = compile(view)(context) - return render(layout, context) - end }, { __tostring = function(self) - local context = self - context.blocks = context.blocks or {} - context.view = compile(view)(context) - return compile(layout)(context) - end }) - end - end - return setmetatable({ render = function(self, context) - return render(view, context or self) - end }, { __tostring = function(self) - return compile(view)(self) - end }) -end - -function template.precompile(view, path, strip) - local chunk = dump(template.compile(view), strip ~= false) - if path then - local file = open(path, "wb") - file:write(chunk) - file:close() - end - return chunk -end - -function template.compile(view, key, plain) - assert(view, "view was not provided for template.compile(view, key, plain).") - if key == "no-cache" then - return loadchunk(template.parse(view, plain)), false - end - key = key or view - local cache = template.cache - if cache[key] then return cache[key], true end - local func = loadchunk(template.parse(view, plain)) - if caching then cache[key] = func end - return func, false -end - -function template.parse(view, plain) - assert(view, "view was not provided for template.parse(view, plain).") - if not plain then - view = template.load(view) - if byte(view, 1, 1) == 27 then return view end - end - local j = 2 - local c = {[[ -context=... or {} -local function include(v, c) return template.compile(v)(c or context) end -local ___,blocks,layout={},blocks or {} -]] } - local i, s = 1, find(view, "{", 1, true) - while s do - local t, p = sub(view, s + 1, s + 1), s + 2 - if t == "{" then - local e = find(view, "}}", p, true) - if e then - local z, w = escaped(view, s) - if i < s - w then - c[j] = "___[#___+1]=[=[\n" - c[j+1] = sub(view, i, s - 1 - w) - c[j+2] = "]=]\n" - j=j+3 - end - if z then - i = s - else - c[j] = "___[#___+1]=template.escape(" - c[j+1] = trim(sub(view, p, e - 1)) - c[j+2] = ")\n" - j=j+3 - s, i = e + 1, e + 2 - end - end - elseif t == "*" then - local e = find(view, "*}", p, true) - if e then - local z, w = escaped(view, s) - if i < s - w then - c[j] = "___[#___+1]=[=[\n" - c[j+1] = sub(view, i, s - 1 - w) - c[j+2] = "]=]\n" - j=j+3 - end - if z then - i = s - else - c[j] = "___[#___+1]=template.output(" - c[j+1] = trim(sub(view, p, e - 1)) - c[j+2] = ")\n" - j=j+3 - s, i = e + 1, e + 2 - end - end - elseif t == "%" then - local e = find(view, "%}", p, true) - if e then - local z, w = escaped(view, s) - if z then - if i < s - w then - c[j] = "___[#___+1]=[=[\n" - c[j+1] = sub(view, i, s - 1 - w) - c[j+2] = "]=]\n" - j=j+3 - end - i = s - else - local n = e + 2 - if sub(view, n, n) == "\n" then - n = n + 1 - end - local r = rpos(view, s - 1) - if i <= r then - c[j] = "___[#___+1]=[=[\n" - c[j+1] = sub(view, i, r) - c[j+2] = "]=]\n" - j=j+3 - end - c[j] = trim(sub(view, p, e - 1)) - c[j+1] = "\n" - j=j+2 - s, i = n - 1, n - end - end - elseif t == "(" then - local e = find(view, ")}", p, true) - if e then - local z, w = escaped(view, s) - if i < s - w then - c[j] = "___[#___+1]=[=[\n" - c[j+1] = sub(view, i, s - 1 - w) - c[j+2] = "]=]\n" - j=j+3 - end - if z then - i = s - else - local f = sub(view, p, e - 1) - local x = find(f, ",", 2, true) - if x then - c[j] = "___[#___+1]=include([=[" - c[j+1] = trim(sub(f, 1, x - 1)) - c[j+2] = "]=]," - c[j+3] = trim(sub(f, x + 1)) - c[j+4] = ")\n" - j=j+5 - else - c[j] = "___[#___+1]=include([=[" - c[j+1] = trim(f) - c[j+2] = "]=])\n" - j=j+3 - end - s, i = e + 1, e + 2 - end - end - elseif t == "[" then - local e = find(view, "]}", p, true) - if e then - local z, w = escaped(view, s) - if i < s - w then - c[j] = "___[#___+1]=[=[\n" - c[j+1] = sub(view, i, s - 1 - w) - c[j+2] = "]=]\n" - j=j+3 - end - if z then - i = s - else - c[j] = "___[#___+1]=include(" - c[j+1] = trim(sub(view, p, e - 1)) - c[j+2] = ")\n" - j=j+3 - s, i = e + 1, e + 2 - end - end - elseif t == "-" then - local e = find(view, "-}", p, true) - if e then - local x, y = find(view, sub(view, s, e + 1), e + 2, true) - if x then - local z, w = escaped(view, s) - if z then - if i < s - w then - c[j] = "___[#___+1]=[=[\n" - c[j+1] = sub(view, i, s - 1 - w) - c[j+2] = "]=]\n" - j=j+3 - end - i = s - else - y = y + 1 - x = x - 1 - if sub(view, y, y) == "\n" then - y = y + 1 - end - local b = trim(sub(view, p, e - 1)) - if b == "verbatim" or b == "raw" then - if i < s - w then - c[j] = "___[#___+1]=[=[\n" - c[j+1] = sub(view, i, s - 1 - w) - c[j+2] = "]=]\n" - j=j+3 - end - c[j] = "___[#___+1]=[=[" - c[j+1] = sub(view, e + 2, x) - c[j+2] = "]=]\n" - j=j+3 - else - if sub(view, x, x) == "\n" then - x = x - 1 - end - local r = rpos(view, s - 1) - if i <= r then - c[j] = "___[#___+1]=[=[\n" - c[j+1] = sub(view, i, r) - c[j+2] = "]=]\n" - j=j+3 - end - c[j] = 'blocks["' - c[j+1] = b - c[j+2] = '"]=include[=[' - c[j+3] = sub(view, e + 2, x) - c[j+4] = "]=]\n" - j=j+5 - end - s, i = y - 1, y - end - end - end - elseif t == "#" then - local e = find(view, "#}", p, true) - if e then - local z, w = escaped(view, s) - if i < s - w then - c[j] = "___[#___+1]=[=[\n" - c[j+1] = sub(view, i, s - 1 - w) - c[j+2] = "]=]\n" - j=j+3 - end - if z then - i = s - else - e = e + 2 - if sub(view, e, e) == "\n" then - e = e + 1 - end - s, i = e - 1, e - end - end - end - s = find(view, "{", s + 1, true) - end - s = sub(view, i) - if s and s ~= "" then - c[j] = "___[#___+1]=[=[\n" - c[j+1] = s - c[j+2] = "]=]\n" - j=j+3 - end - c[j] = "return layout and include(layout,setmetatable({view=table.concat(___),blocks=blocks},{__index=context})) or table.concat(___)" - return concat(c) -end - -function template.render(view, context, key, plain) - assert(view, "view was not provided for template.render(view, context, key, plain).") - return template.print(template.compile(view, key, plain)(context)) -end - -return template \ No newline at end of file diff --git a/lualib/template/html.lua b/lualib/template/html.lua new file mode 100644 index 00000000..5d97fe05 --- /dev/null +++ b/lualib/template/html.lua @@ -0,0 +1,53 @@ +local template = require "template" +local escape = template.escape + +local setmetatable = setmetatable +local concat = table.concat +local pairs = pairs +local type = type + +local function tag(name, content, attr) + local r, a = {}, {} + content = content or attr + r[#r + 1] = "<" + r[#r + 1] = name + if attr then + for k, v in pairs(attr) do + if type(k) == "number" then + a[#a + 1] = escape(v) + else + a[#a + 1] = k .. '="' .. escape(v) .. '"' + end + end + if #a > 0 then + r[#r + 1] = " " + r[#r + 1] = concat(a, " ") + end + end + if type(content) == "string" then + r[#r + 1] = ">" + r[#r + 1] = escape(content) + r[#r + 1] = "" + else + r[#r + 1] = " />" + end + return concat(r) +end + +local html = { __index = function(_, name) + return function(attr) + if type(attr) == "table" then + return function(content) + return tag(name, content, attr) + end + else + return tag(name, attr) + end + end +end } + +template.html = setmetatable(html, html) + +return template.html \ No newline at end of file diff --git a/lualib/template/init.lua b/lualib/template/init.lua new file mode 100644 index 00000000..fa69cd1f --- /dev/null +++ b/lualib/template/init.lua @@ -0,0 +1 @@ +return require "template.new".new(false) \ No newline at end of file diff --git a/lualib/template/new.lua b/lualib/template/new.lua new file mode 100644 index 00000000..80a4f200 --- /dev/null +++ b/lualib/template/new.lua @@ -0,0 +1,612 @@ +local aio = require "aio" +local aio_open = aio.open + +local sys = require "sys" +local newtab = sys.new_tab + +local setmetatable = setmetatable +local tostring = tostring +local concat = table.concat +local assert = assert +local pcall = pcall +local load = load +local type = type +local dump = string.dump +local find = string.find +local gsub = string.gsub +local byte = string.byte +local null = null +local sub = string.sub +local print_view = io.write + +local HTML_ENTITIES = { + ["&"] = "&", + ["<"] = "<", + [">"] = ">", + ['"'] = """, + ["'"] = "'", + ["/"] = "/" +} + +local CODE_ENTITIES = { + ["{"] = "{", + ["}"] = "}", + ["&"] = "&", + ["<"] = "<", + [">"] = ">", + ['"'] = """, + ["'"] = "'", + ["/"] = "/" +} + +local ESC = byte("\27") +local NUL = byte("\0") +local HT = byte("\t") +local VT = byte("\v") +local LF = byte("\n") +local SOL = byte("/") +local BSOL = byte("\\") +local SP = byte(" ") +local AST = byte("*") +local NUM = byte("#") +local LPAR = byte("(") +local LSQB = byte("[") +local LCUB = byte("{") +local MINUS = byte("-") +local PERCNT = byte("%") + +local EMPTY = "" + +local VIEW_ENV = { __index = function(t, k) + return t.context[k] or t.template[k] or _ENV[k] + end +} + +local function enabled(val) + if val == nil then return true end + return val == true or (val == "1" or val == "true" or val == "on") +end + +local function trim(s) + return gsub(gsub(s, "^%s+", EMPTY), "%s+$", EMPTY) +end + +local function rpos(view, s) + while s > 0 do + local c = byte(view, s, s) + if c == SP or c == HT or c == VT or c == NUL then + s = s - 1 + else + break + end + end + return s +end + +local function escaped(view, s) + if s > 1 and byte(view, s - 1, s - 1) == BSOL then + if s > 2 and byte(view, s - 2, s - 2) == BSOL then + return false, 1 + else + return true, 1 + end + end + return false, 0 +end + +local function read_file(path) + local f, err = aio_open(path) + if not f then + return nil, err + end + local content = f:readall() + f:close() + f = nil + return content +end + +local function load_view(template) + return function(view, plain) + if plain == true then + return view + end + local path, root = view, template.root + if root and root ~= EMPTY then + if byte(root, -1) == SOL then + root = sub(root, 1, -2) + end + if byte(view, 1) == SOL then + path = sub(view, 2) + end + path = root .. "/" .. path + end + return plain == false and assert(read_file(path)) or read_file(path) or view + end +end + +local function load_file(func) + return function(view) return func(view, false) end +end + +local function load_string(func) + return function(view) return func(view, true) end +end + +local function loader(template) + return function(view) + return assert(load(view, nil, nil, setmetatable({ template = template }, VIEW_ENV))) + end +end + +local function visit(visitors, content, tag, name) + if not visitors then + return content + end + + for i = 1, visitors.n do + content = visitors[i](content, tag, name) + end + + return content +end + +local function new(template, safe) + template = template or newtab(0, 26) + + template._VERSION = "2.0" + template.cache = {} + template.load = load_view(template) + template.load_file = load_file(template.load) + template.load_string = load_string(template.load) + template.print = print_view + + local load_chunk = loader(template) + + local caching = true + + local visitors + function template.visit(func) + if not visitors then + visitors = { func, n = 1 } + return + end + visitors.n = visitors.n + 1 + visitors[visitors.n] = func + end + + function template.caching(enable) + if enable ~= nil then caching = enable == true end + return caching + end + + function template.output(s) + if s == nil or s == null then return EMPTY end + if type(s) == "function" then return template.output(s()) end + return tostring(s) + end + + function template.escape(s, c) + if type(s) == "string" then + if c then return gsub(s, "[}{\">/<'&]", CODE_ENTITIES) end + return gsub(s, "[\">/<'&]", HTML_ENTITIES) + end + return template.output(s) + end + + function template.new(view, layout) + + local vt = type(view) + + if vt == "boolean" then + return new(nil, view) + end + if vt == "table" then + return new(view, safe) + end + if vt == "nil" then + return new(nil, safe) + end + + local render + local process + if layout then + if type(layout) == "table" then + render = function(self, context) + context = context or self + context.blocks = context.blocks or {} + context.view = template.process(view, context) + layout.blocks = context.blocks or {} + layout.view = context.view or EMPTY + layout:render() + end + process = function(self, context) + context = context or self + context.blocks = context.blocks or {} + context.view = template.process(view, context) + layout.blocks = context.blocks or {} + layout.view = context.view + return tostring(layout) + end + else + render = function(self, context) + context = context or self + context.blocks = context.blocks or {} + context.view = template.process(view, context) + template.render(layout, context) + end + process = function(self, context) + context = context or self + context.blocks = context.blocks or {} + context.view = template.process(view, context) + return template.process(layout, context) + end + end + else + render = function(self, context) + return template.render(view, context or self) + end + process = function(self, context) + return template.process(view, context or self) + end + end + + if safe then + return setmetatable({ + render = function(...) + local ok, err = pcall(render, ...) + if not ok then + return nil, err + end + end, + process = function(...) + local ok, output = pcall(process, ...) + if not ok then + return nil, output + end + return output + end, + }, { + __tostring = function(...) + local ok, output = pcall(process, ...) + if not ok then + return "" + end + return output + end }) + end + + return setmetatable({ + render = render, + process = process + }, { + __tostring = process + }) + end + + function template.precompile(view, path, strip, plain) + local chunk = dump(template.compile(view, nil, plain), strip ~= false) + if path then + local f = aio_open(path) + f:write(chunk) + f:close() + f = nil + end + return chunk + end + + function template.precompile_string(view, path, strip) + return template.precompile(view, path, strip, true) + end + + function template.precompile_file(view, path, strip) + return template.precompile(view, path, strip, false) + end + + function template.compile(view, cache_key, plain) + assert(view, "view was not provided for template.compile(view, cache_key, plain)") + if cache_key == "no-cache" then + return load_chunk(template.parse(view, plain)), false + end + cache_key = cache_key or view + local cache = template.cache + if cache[cache_key] then return cache[cache_key], true end + local func = load_chunk(template.parse(view, plain)) + if caching then cache[cache_key] = func end + return func, false + end + + function template.compile_file(view, cache_key) + return template.compile(view, cache_key, false) + end + + function template.compile_string(view, cache_key) + return template.compile(view, cache_key, true) + end + + function template.parse(view, plain) + assert(view, "view was not provided for template.parse(view, plain)") + if plain ~= true then + view = template.load(view, plain) + if byte(view, 1, 1) == ESC then return view end + end + local j = 2 + local c = {[[ +context=... or {} +local ___,blocks,layout={},blocks or {} +local function include(v, c) return template.process(v, c or context) end +local function echo(...) for i=1,select("#", ...) do ___[#___+1] = tostring(select(i, ...)) end end +]] } + local i, s = 1, find(view, "{", 1, true) + while s do + local t, p = byte(view, s + 1, s + 1), s + 2 + if t == LCUB then + local e = find(view, "}}", p, true) + if e then + local z, w = escaped(view, s) + if i < s - w then + c[j] = "___[#___+1]=[=[\n" + c[j+1] = visit(visitors, sub(view, i, s - 1 - w)) + c[j+2] = "]=]\n" + j=j+3 + end + if z then + i = s + else + c[j] = "___[#___+1]=template.escape(" + c[j+1] = visit(visitors, trim(sub(view, p, e - 1)), "{") + c[j+2] = ")\n" + j=j+3 + s, i = e + 1, e + 2 + end + end + elseif t == AST then + local e = find(view, "*}", p, true) + if e then + local z, w = escaped(view, s) + if i < s - w then + c[j] = "___[#___+1]=[=[\n" + c[j+1] = visit(visitors, sub(view, i, s - 1 - w)) + c[j+2] = "]=]\n" + j=j+3 + end + if z then + i = s + else + c[j] = "___[#___+1]=template.output(" + c[j+1] = visit(visitors, trim(sub(view, p, e - 1)), "*") + c[j+2] = ")\n" + j=j+3 + s, i = e + 1, e + 2 + end + end + elseif t == PERCNT then + local e = find(view, "%}", p, true) + if e then + local z, w = escaped(view, s) + if z then + if i < s - w then + c[j] = "___[#___+1]=[=[\n" + c[j+1] = visit(visitors, sub(view, i, s - 1 - w)) + c[j+2] = "]=]\n" + j=j+3 + end + i = s + else + local n = e + 2 + if byte(view, n, n) == LF then + n = n + 1 + end + local r = rpos(view, s - 1) + if i <= r then + c[j] = "___[#___+1]=[=[\n" + c[j+1] = visit(visitors, sub(view, i, r)) + c[j+2] = "]=]\n" + j=j+3 + end + c[j] = visit(visitors, trim(sub(view, p, e - 1)), "%") + c[j+1] = "\n" + j=j+2 + s, i = n - 1, n + end + end + elseif t == LPAR then + local e = find(view, ")}", p, true) + if e then + local z, w = escaped(view, s) + if i < s - w then + c[j] = "___[#___+1]=[=[\n" + c[j+1] = visit(visitors, sub(view, i, s - 1 - w)) + c[j+2] = "]=]\n" + j=j+3 + end + if z then + i = s + else + local f = visit(visitors, sub(view, p, e - 1), "(") + local x = find(f, ",", 2, true) + if x then + c[j] = "___[#___+1]=include([=[" + c[j+1] = trim(sub(f, 1, x - 1)) + c[j+2] = "]=]," + c[j+3] = trim(sub(f, x + 1)) + c[j+4] = ")\n" + j=j+5 + else + c[j] = "___[#___+1]=include([=[" + c[j+1] = trim(f) + c[j+2] = "]=])\n" + j=j+3 + end + s, i = e + 1, e + 2 + end + end + elseif t == LSQB then + local e = find(view, "]}", p, true) + if e then + local z, w = escaped(view, s) + if i < s - w then + c[j] = "___[#___+1]=[=[\n" + c[j+1] = visit(visitors, sub(view, i, s - 1 - w)) + c[j+2] = "]=]\n" + j=j+3 + end + if z then + i = s + else + c[j] = "___[#___+1]=include(" + c[j+1] = visit(visitors, trim(sub(view, p, e - 1)), "[") + c[j+2] = ")\n" + j=j+3 + s, i = e + 1, e + 2 + end + end + elseif t == MINUS then + local e = find(view, "-}", p, true) + if e then + local x, y = find(view, sub(view, s, e + 1), e + 2, true) + if x then + local z, w = escaped(view, s) + if z then + if i < s - w then + c[j] = "___[#___+1]=[=[\n" + c[j+1] = visit(visitors, sub(view, i, s - 1 - w)) + c[j+2] = "]=]\n" + j=j+3 + end + i = s + else + y = y + 1 + x = x - 1 + if byte(view, y, y) == LF then + y = y + 1 + end + local b = trim(sub(view, p, e - 1)) + if b == "verbatim" or b == "raw" then + if i < s - w then + c[j] = "___[#___+1]=[=[\n" + c[j+1] = visit(visitors, sub(view, i, s - 1 - w)) + c[j+2] = "]=]\n" + j=j+3 + end + c[j] = "___[#___+1]=[=[" + c[j+1] = visit(visitors, sub(view, e + 2, x)) + c[j+2] = "]=]\n" + j=j+3 + else + if byte(view, x, x) == LF then + x = x - 1 + end + local r = rpos(view, s - 1) + if i <= r then + c[j] = "___[#___+1]=[=[\n" + c[j+1] = visit(visitors, sub(view, i, r)) + c[j+2] = "]=]\n" + j=j+3 + end + c[j] = 'blocks["' + c[j+1] = b + c[j+2] = '"]=include[=[' + c[j+3] = visit(visitors, sub(view, e + 2, x), "-", b) + c[j+4] = "]=]\n" + j=j+5 + end + s, i = y - 1, y + end + end + end + elseif t == NUM then + local e = find(view, "#}", p, true) + if e then + local z, w = escaped(view, s) + if i < s - w then + c[j] = "___[#___+1]=[=[\n" + c[j+1] = visit(visitors, sub(view, i, s - 1 - w)) + c[j+2] = "]=]\n" + j=j+3 + end + if z then + i = s + else + e = e + 2 + if byte(view, e, e) == LF then + e = e + 1 + end + s, i = e - 1, e + end + end + end + s = find(view, "{", s + 1, true) + end + s = sub(view, i) + if s and s ~= EMPTY then + c[j] = "___[#___+1]=[=[\n" + c[j+1] = visit(visitors, s) + c[j+2] = "]=]\n" + j=j+3 + end + c[j] = "return layout and include(layout,setmetatable({view=table.concat(___),blocks=blocks},{__index=context})) or table.concat(___)" -- luacheck: ignore + return concat(c) + end + + function template.parse_file(view) + return template.parse(view, false) + end + + function template.parse_string(view) + return template.parse(view, true) + end + + function template.process(view, context, cache_key, plain) + assert(view, "view was not provided for template.process(view, context, cache_key, plain)") + return template.compile(view, cache_key, plain)(context) + end + + function template.process_file(view, context, cache_key) + assert(view, "view was not provided for template.process_file(view, context, cache_key)") + return template.compile(view, cache_key, false)(context) + end + + function template.process_string(view, context, cache_key) + assert(view, "view was not provided for template.process_string(view, context, cache_key)") + return template.compile(view, cache_key, true)(context) + end + + function template.render(view, context, cache_key, plain) + assert(view, "view was not provided for template.render(view, context, cache_key, plain)") + template.print(template.process(view, context, cache_key, plain)) + end + + function template.render_file(view, context, cache_key) + assert(view, "view was not provided for template.render_file(view, context, cache_key)") + template.render(view, context, cache_key, false) + end + + function template.render_string(view, context, cache_key) + assert(view, "view was not provided for template.render_string(view, context, cache_key)") + template.render(view, context, cache_key, true) + end + + if safe then + return setmetatable({}, { + __index = function(_, k) + if type(template[k]) == "function" then + return function(...) + local ok, a, b = pcall(template[k], ...) + if not ok then + return nil, a + end + return a, b + end + end + return template[k] + end, + __new_index = function(_, k, v) + template[k] = v + end, + }) + end + + return template +end + +return new() \ No newline at end of file diff --git a/lualib/uitls/init.lua b/lualib/uitls/init.lua deleted file mode 100644 index 0857f3ee..00000000 --- a/lualib/uitls/init.lua +++ /dev/null @@ -1,74 +0,0 @@ --- 格式化输出(美化) -var_dump = function (data, showMetatable, lastCount) - if type(data) ~= "table" then - --Value - if type(data) == "string" then - io.write('"', data, '"') - else - io.write(tostring(data)) - end - else - --Format - local count = lastCount or 0 - count = count + 1 - io.write("{\n") - --Metatable - if showMetatable then - for i = 1,count do io.write(" ") end - local mt = getmetatable(data) - io.write("\"__metatable\" = ") - var_dump(mt, showMetatable, count) -- 如果不想看到元表的元表,可将showMetatable处填nil - io.write(",\n") --如果不想在元表后加逗号,可以删除这里的逗号 - end - --Key - for key,value in pairs(data) do - for i = 1,count do io.write(" ") end - if type(key) == "string" then - -- io.write("\"", key, "\" = ") - io.write('["', key, '"] = ') - elseif type(key) == "number" then - io.write("[", key, "] = ") - else - io.write(tostring(key)) - end - var_dump(value, showMetatable, count) -- 如果不想看到子table的元表,可将showMetatable处填nil - io.write(",\n") --如果不想在table的每一个item后加逗号,可以删除这里的逗号 - end - --Format - for i = 1, lastCount or 0 do io.write(" ") end - io.write("}") - end - --Format - if not lastCount then - io.write("\n") - end -end - --- local co = require "internal.Co" --- local tcp = require "internal.TCP" --- local Timer = require "internal.Timer" --- local DB = require "DB" --- local Cache = require "Cache" --- co.spwan(function ( ... ) --- while 1 do --- local self = co.self() --- local ti = Timer.timeout(0.1, function() --- local co_count, task_count = co.count() --- local tcp_count = tcp.count() --- local time_count = Timer.count() --- local db_count = DB.count() --- local cache_count = Cache.count() --- print("=======================") --- print("co 数量为:", co_count) --- print("tcp 数量为:", tcp_count) --- print("task 数量为:", task_count) --- print("timer 数量为:", time_count) --- print("db 数量为:", db_count) --- print("cache 数量为:", cache_count) --- print("当前内存为:", collectgarbage('count')) --- print("=======================") --- co.wakeup(self) --- end) --- co.wait() --- end --- end) diff --git a/lualib/url/init.lua b/lualib/url/init.lua new file mode 100644 index 00000000..a519bea2 --- /dev/null +++ b/lualib/url/init.lua @@ -0,0 +1,126 @@ +local type = type +local assert = assert +local setmetatable = setmetatable + +local strsub = string.sub +local strbyte = string.byte +local strfind = string.find +local strfmt = string.format +local strmatch = string.match + +local tconcat = table.concat + +-- C版实现 +local encode = require("crypt").urlencode +local decode = require("crypt").urldecode + +--[[ +经过测试: 100万此编码/解码两者性能相差30倍, 正好是lua与C的性能差距. +]] + +local url = {} + +-- urlencode编码 +function url.encode(s) + -- return spliter(spliter(s, "([^%w%.%- ])", function(c) return fmt("%%%02X", byte(c)) end), " ", "+") + return encode(s) +end + +-- urldecode解码 +function url.decode(s) + -- return spliter(s, '%%(%x%x)', function(h) return char(tonumber(h, 16)) end) + return decode(s) +end + +local meta = {} + +function meta.__tostring(t) + return strfmt( + "Url(sheme='%s', netloc='%s', path='%s', query='%s', fragment='%s')", + t.sheme or '', t.netloc or '', t.path or '', t.query or '', t.fragment or '' + ) +end + +local function parse_other(other, t, dec) + -- find '#' or '?' + local pos = strfind(other, "#") + if pos then + -- Begin with '#' + t.fragment = strsub(other, pos + 1) + if pos > 1 then + t.query = strsub(other, 2, pos - 1) + end + else + -- got query string. + pos = strfind(other, "?") + if pos then + t.query = strsub(other, pos + 1) + end + end + if dec and t.query and t.query ~= '' then + t.query = decode(t.query) + end +end + +local function parse_noloc(str, dec) + local t, other = {}, nil + t.path, other = strmatch(str, "([^%?#]*)([%?#]?.*)") + parse_other(other, t, dec) + return setmetatable(t, meta) +end + +local function parse_nosheme(str, dec) + local t, other = {}, nil + -- got url split string. + t.path, other = strmatch(str, "([^%?#]*)([%?#]?.*)") + if strbyte(t.path) == 47 then + return parse_noloc(str) + end + if strfind(t.path, '/') then + t.netloc, t.path, other = strmatch(str, "([^/]*)([^%?#]*)([%?#]?.*)") + end + parse_other(other, t, dec) + return setmetatable(t, meta) +end + +---comment split url to Url Table(Class). +---@param str string @Url buffer. +---@param dec boolean @Url decode. +---@return table +function url.split(str, dec) + assert(type(str) == 'string', 'Invalid Url type.') + local t, other = {}, nil + -- got url split string. + t.sheme, t.netloc, t.path, other = strmatch(str, '([^:]*)[:]?//([^/]*)([/]?[^%?#]*)([%?#]?.*)') + if not t.sheme then + return parse_nosheme(str, dec) + end + parse_other(other, t, dec) + return setmetatable(t, meta) +end + +---comment Use `Url` Table(Class) to Build url `String`. +---@param tab table @Split Class. +---@return string +function url.join(tab) + assert(type(tab) == 'table', 'Invalid Url Class.') + local urls = {} + if tab.sheme then + urls[1] = (tab.sheme ~= '' and (tab.sheme .. ':') or '' ) .. '//' + end + if tab.netloc then + urls[#urls+1] = tab.netloc + end + if tab.path then + urls[#urls+1] = tab.path + end + if tab.query then + urls[#urls+1] = '?' .. tab.query + end + if tab.fragment then + urls[#urls+1] = '#' .. tab.fragment + end + return tconcat(urls) +end + +return url diff --git a/lualib/utils/init.lua b/lualib/utils/init.lua new file mode 100644 index 00000000..3b8e5a66 --- /dev/null +++ b/lualib/utils/init.lua @@ -0,0 +1,133 @@ +local type = type +local pairs = pairs +local print = print +local tostring = tostring + +local getmetatable = getmetatable +local setmetatable = setmetatable + +local mtype = math.type or function (v) + return type(v) == 'number' and + (v % 1 == 0 and 'integer' or 'float') or 'nil' +end + +local debug_getinfo = debug.getinfo + +local strrep = string.rep +local strfmt = string.format + +local tconcat = table.concat +local tinsert = table.insert + +local r = debug.getregistry() +local l, g = r._LOADED, _G + +local V = tonumber(_VERSION:sub(4)) + +local function space(level) + return strrep(' ', level) +end + +local function tokey(key) + if type(key) ~= 'string' then + return strfmt("%s", key) + end + return strfmt("%q", key) +end + +local array_mt = r['Lua.Array'] +local function isarray(tab) + if #tab > 0 then + return true + end + return array_mt and getmetatable(tab) == array_mt +end + +---comment 格式化打印变量`tab`. +---@param tab any @指定指定变量 +---@param meta boolean? @是否跟进元表 +---@param level integer? @指定打印层级 +local function var_dump(tab, meta, level) + if type(tab) ~= 'table' then + return strfmt("%s\n", tostring(tab)) + end + local ptab = {} + -- 拆分 + local M, I + if meta then + M = getmetatable(tab) + if M then + setmetatable(tab, nil) + tab.__metatable__ = M + end + end + if tab.__index == tab then + I = tab.__index + tab.__index = nil + end + local n = 0 + local olevel = level + level = level + 1 + for k, v in pairs(tab) do + if type(k) ~= 'number' and type(k) ~= 'string' then + k = tostring(k) + end + if type(v) == "number" then + if mtype(v) == 'float' then + tinsert(ptab, strfmt('%s[%s] = Number(%s),\n', space(level), tokey(k), v)) + else + tinsert(ptab, strfmt('%s[%s] = Integer(%d),\n', space(level), tokey(k), v)) + end + elseif type(v) == 'boolean' then + tinsert(ptab, strfmt('%s[%s] = Boolean(%s),\n', space(level), tokey(k), v)) + elseif type(v) == 'table' then + if v == g or v == l then + tinsert(ptab, strfmt('%s[%s] = %s,\n', space(level), tokey(k), tostring(v))) + else + tinsert(ptab, strfmt('%s[%s] = %s', space(level), tokey(k), var_dump(v, meta, level))) + end + elseif type(v) == 'string' then + tinsert(ptab, strfmt('%s[%s] = String(%q),\n', space(level), tokey(k), v)) + elseif type(v) == 'function' then + local info = debug_getinfo(v) + if info.linedefined > 0 then + if V > 5.3 then + tinsert(ptab, strfmt('%s[%s] = LuaFunction(%p%s),\n', space(level), tokey(k), v, info.source .. ':' .. info.linedefined)) + else + tinsert(ptab, strfmt('%s[%s] = %s(%s),\n', space(level), tokey(k), v, info.source .. ':' .. info.linedefined)) + end + else + if V > 5.3 then + tinsert(ptab, strfmt('%s[%s] = LuaCFunction(%p),\n', space(level), tokey(k), tostring(v))) + else + tinsert(ptab, strfmt('%s[%s] = c%s,\n', space(level), tokey(k), tostring(v))) + end + end + else + tinsert(ptab, strfmt('%s[%s] = %s,\n', space(level), tokey(k), tostring(v))) + end + n = n + 1 + end + -- 还原 + if meta then + if M then + setmetatable(tab, M) + tab.__metatable__ = nil + end + end + if I then + tab.__index = I + end + local left, right = "{\n", "%s}%s\n" + if n == #tab and isarray(tab) then + left, right = "[\n", "%s]%s\n" + end + return left .. tconcat(ptab) .. strfmt(right, space(olevel), olevel == 0 and "" or ",") +end + +---comment Dump表结构 +---@param tab any @格式化打印当前表结构 +---@param meta? boolean @将元表结构也打印出来 +_G.var_dump = function (tab, meta) + print(var_dump(tab, meta, 0)) +end \ No newline at end of file diff --git a/lualib/utils/random.lua b/lualib/utils/random.lua new file mode 100644 index 00000000..7829c461 --- /dev/null +++ b/lualib/utils/random.lua @@ -0,0 +1,173 @@ +local type = type +local assert = assert + +local ssub = string.sub +local char = string.char + +local mlog = math.log +local mexp = math.exp +local msqrt = math.sqrt +local mcos = math.cos +local msin = math.sin +local mrandom = math.random +local mrandomseed = math.randomseed + +local NV_MAGIC = 4 * mexp(-0.5) / msqrt(2.0) + +local PI2 = 2 * math.pi +local gauss_next = nil + +local random = {} + +---comment @同`math.randomseed` +function random.randomseed(...) + return mrandomseed(...) +end + +---comment @同`math.random` +---@return number +function random.random(...) + return mrandom(...) +end + +---comment 生成指定数量的字符数组 +---@param num integer @样品数量 +---@return table +function random.generatechar(num) + local list = {} + for i = 1, num do + list[i] = char(mrandom(0, 255)) + end + return list +end + +---comment 生成指定数量的随机整数数组 +---@param x integer @随机数的最小值, 结果包含该值. +---@param y integer @随机数的最大值, 结果包含该值. +---@param num integer @样品数量 +---@return table +function random.generateint (x, y, num) + local list = {} + for i = 1, num do + list[i] = mrandom(x, y) + end + return list +end + +---comment 生成指定数量的随机实数数组 +---@param x integer @随机数的最小值, 结果包含该值. +---@param y integer @随机数的最大值, 结果包含该值. +---@param num integer @样品数量 +---@return table +function random.generatefloat (x, y, num) + local list = {} + for i = 1, num do + list[i] = random.uniform(x, y) + end + return list +end + +---comment 将随机生成一个实数,它在`x`, `y`范围内。 +---@param x integer @随机数的最小值, 结果包含该值. +---@param y integer @随机数的最大值, 结果包含该值. +---@return number +function random.uniform(x, y) + return mrandom(x or 0, y or 4294967296) + mrandom() +end + +---comment 返回给定`sequence`内的随机项。 +---@param sequence string | table @可以是`数组`或`字符串`. +---@return any +function random.choice(sequence) + local tp = type(sequence) + if tp ~= 'string' and tp ~= 'table' then + return + end + local len = #sequence + if len < 1 then + return + end + local rv = mrandom(1, len) + if tp == 'table' then + return sequence[rv] + end + return ssub(sequence, rv, rv) +end + +---comment 打乱给定`sequence`内的顺序 +---@param sequence table @数组结构 +function random.shuffle(sequence) + local len = #assert(sequence, "Invalid `sequence`") + for i = 1, len, 1 do + local j = mrandom(1, len) + sequence[i], sequence[j] = sequence[j], sequence[i] + end + return sequence +end + +---comment 根据给定`sequence`选取`num`个样品 +---@param sequence table @样品数组 +---@param num integer @样品数量 +function random.sample(sequence, num) + local len = #assert(sequence, "Invalid `sequence`") + assert(num and num <= len, "Invalid `num` or `sample` larger than population.") + local idx_list = {} + for i = 1, len do + idx_list[i] = i + end + random.shuffle(idx_list) + local list = {} + for i = 1, num do + list[i] = sequence[idx_list[i]] + end + return list +end + +---comment 正态分布 +---@param mean number @平均值 +---@param sigma number @标准差 +---@return number +function random.normalvariate(mean, sigma) + local z, zz, u1, u2 + while true do + u1 = mrandom() + u2 = 1.0 - mrandom() + z = NV_MAGIC * (u1 - 0.5) / u2 + zz = z * z / 4.0 + if zz <= -mlog(u2) then + break + end + end + return mean + z * sigma +end + +---comment 对数正态分布 +---@param mean number @平均值 +---@param sigma number @标准差 +---@return number +function random.lognormvariate(mean, sigma) + return mexp(random.normalvariate(mean, sigma)) +end + +---comment 指数分布 +---@param lambd number @lambd 是1.0除以所需的平均值. +function random.expovariate(lambd) + return -mlog(1.0 - mrandom()) / lambd +end + +---comment 高斯分布 +---@param mean number @平均值 +---@param sigma number @标准差 +function random.gauss(mean, sigma) + local z = gauss_next + gauss_next = nil + if not z then + local x2pi = mrandom() * PI2 + local g2rad = msqrt(-2.0 * mlog(1.0 - mrandom())) + z = mcos(x2pi) * g2rad + gauss_next = msin(x2pi) * g2rad + end + return mean + z * sigma +end + +return random \ No newline at end of file diff --git a/lualib/utils/set.lua b/lualib/utils/set.lua new file mode 100644 index 00000000..f1f909fe --- /dev/null +++ b/lualib/utils/set.lua @@ -0,0 +1,136 @@ +local type = type +local pairs = pairs +local assert = assert +local tostring = tostring +local setmetatable = setmetatable + +local tconcat = table.concat + +---@class Set @集合 +local MetaSet = { map = {} } + +MetaSet.__index = MetaSet + +---comment 实例化`Set` +---@return Set +function MetaSet:new() + return setmetatable({ count = 0, map = {} }, MetaSet) +end + +---comment 插入元素 +---@param value any @待插入的元素 +---@return boolean @已存在返回`false`, 否则返回`true` +function MetaSet:insert(value) + if not self.map[value] then + self.map[value] = true + self.count = self.count + 1 + return true + end + return false +end + +---comment 删除元素 +---@param value any @待删除的元素 +---@return boolean @删除成功返回`true`, 否则返回`false` +function MetaSet:remove(value) + if self.map[value] then + self.map[value] = nil + self.count = self.count - 1 + return true + end + return false +end + +---comment 返回集合内的元素数量 +---@return integer +function MetaSet:len() + return self.count +end + +---comment 是否为空 +---@return boolean +function MetaSet:is_empty() + return self.count == 0 +end + +---comment 美化打印输出 +---@return string +function MetaSet:__tostring() + local tab = {} + for element in pairs(self.map) do + tab[#tab+1] = tostring(element) + end + return "Set([" .. tconcat(tab, ', ') .. "])" +end + +---comment 求差集 +---@param t1 Set @集合1 +---@param t2 Set @集合2 +---@return Set @新集合 +function MetaSet.__sub(t1, t2) + assert(type(t1) == 'table' and type(t2) == 'table', "[Set ERROR]: Invalid Set OP.") + local t1_map, t2_map = t1.map, t2.map + assert(type(t1_map) == 'table' and type(t2_map) == 'table', "[Set ERROR]: Invalid Set OP.") + local Set = MetaSet.new() + for k in pairs(t1_map) do + if not t2_map[k] then + Set:insert(k) + end + end + return Set +end + +---comment 求交集 +---@param t1 Set @集合1 +---@param t2 Set @集合2 +---@return Set @新集合 +function MetaSet.__band (t1, t2) + assert(type(t1) == 'table' and type(t2) == 'table', "[Set ERROR]: Invalid Set OP.") + local t1_map, t2_map = t1.map, t2.map + assert(type(t1_map) == 'table' and type(t2_map) == 'table', "[Set ERROR]: Invalid Set OP.") + local Set = MetaSet.new() + for k in pairs(t1_map) do + if t2_map[k] then + Set:insert(k) + end + end + return Set +end + +---comment 求并集 +---@param t1 Set @集合1 +---@param t2 Set @集合2 +---@return Set @新集合 +function MetaSet.__bor (t1, t2) + assert(type(t1) == 'table' and type(t2) == 'table', "[Set ERROR]: Invalid Set OP.") + local t1_map, t2_map = t1.map, t2.map + assert(type(t1_map) == 'table' and type(t2_map) == 'table', "[Set ERROR]: Invalid Set OP.") + local Set = MetaSet.new() + for k in pairs(t1_map) do + Set:insert(k) + end + for k in pairs(t2_map) do + Set:insert(k) + end + return Set +end + +---comment 求并集 +---@param t1 Set @集合1 +---@param t2 Set @集合2 +---@return Set @新集合 +function MetaSet.__add (t1, t2) + assert(type(t1) == 'table' and type(t2) == 'table', "[Set ERROR]: Invalid Set OP.") + local t1_map, t2_map = t1.map, t2.map + assert(type(t1_map) == 'table' and type(t2_map) == 'table', "[Set ERROR]: Invalid Set OP.") + local Set = MetaSet.new() + for k in pairs(t1_map) do + Set:insert(k) + end + for k in pairs(t2_map) do + Set:insert(k) + end + return Set +end + +return MetaSet \ No newline at end of file diff --git a/lualib/utils/string.lua b/lualib/utils/string.lua new file mode 100644 index 00000000..0e5f65b9 --- /dev/null +++ b/lualib/utils/string.lua @@ -0,0 +1,216 @@ +local type = type +local select = select +local assert = assert +local tonumber = tonumber + +local string = string +local ssub = string.sub +local srep = string.rep +local sfind = string.find +local sbyte = string.byte +local sgsub = string.gsub +local sgmatch = string.gmatch + +local mtype = math.type +local tconcat = table.concat + +---comment 计算`pattern`在`text`中pos位置开始出现的总次数. +---@param text string @实际内容 +---@param pattern string @匹配内容 +---@param pos integer @字符串 +---@return string @返回拼接好的字符串 +function string.count (text, pattern, pos) + if mtype(pos) ~= 'integer' or pos < 1 then + pos = 1 + end + local count = 0 + while true do + pos = sfind(text, pattern, pos) + if not pos then + break + end + count = count + 1 + pos = pos + 1 + end + return count +end + +---comment 以`text`为中心拼接`count`个`fill`在两侧. +---@param text string @实际内容 +---@param fill string @填充字符 +---@param count integer @填充数量 +---@return string @返回拼接好的字符串 +function string.center (text, fill, count) + assert(type(text) == 'string' and type(fill) == 'string' , "Invalid string `text` or `fill`.") + assert(type(count) == 'number' and tonumber(count) or count > 0, "Invalid fill `count`") + local fill_text = srep(fill, count) + return tconcat{ fill_text, text, fill_text } +end + +---comment 判断指定字符串内容是否全部空格 +---@param text string @判断内容 +---@return boolean @如果`text`全是空格返回`true`, 否则返回`false`. +function string.allspace (text) + return sfind(text or '', "^[ ]+$") and true or false +end + +---comment 连接`1`个或`N`个字符串, 非可转换字符串的对象会抛出异常 +---@return string +function string.join (...) + if select("#", ...) <= 1 then + return (...) or '' + end + return tconcat{...} +end + +---comment 根据`sep`分割`text`字符串. +---@param text string @待分割的字符串 +---@param sep string @分割用的分隔符 +---@return table @分割后的数组 +function string.split(text, sep) + assert(type(text) == 'string', "Invalid string `text`.") + if not sep or type(sep) ~= 'string' or sep == '' then + sep = '%,' + end + local index = 1 + local list = {} + for sub in sgmatch(text, "([^" .. sep .. "]*)") do + list[index] = sub + index = index + 1 + end + if index == 1 then + list[index] = text + end + return list +end + +local function strip(text, sep, left, right) + if left then + text = sgsub(text, "^[" .. sep .."]+", "", 1) + end + if right then + text = sgsub(text, "[" .. sep .."]+$", "", 1) + end + return text +end + +---comment 移除字符串头、尾指定的字符 +---@param text string @待分割的字符串 +---@param sep string @移除用的分隔符 +function string.strip (text, sep) + assert(type(text) == 'string', "Invalid string `text`.") + if not sep or type(sep) ~= 'string' or sep == '' then + sep = ' ' + end + return strip(text, sep, true, true) +end + +---comment 移除字符串头部指定的字符 +---@param text string @待分割的字符串 +---@param sep string @移除用的分隔符 +function string.lstrip (text, sep) + assert(type(text) == 'string', "Invalid string `text`.") + if not sep or type(sep) ~= 'string' or sep == '' then + sep = ' ' + end + return strip(text, sep, true, false) +end + +---comment 移除字符串尾部指定的字符 +---@param text string @待分割的字符串 +---@param sep string @移除用的分隔符 +function string.rstrip (text, sep) + assert(type(text) == 'string', "Invalid string `text`.") + if not sep or type(sep) ~= 'string' or sep == '' then + sep = ' ' + end + return strip(text, sep, false, true) +end + +---comment 判断起始位置是否指定内容 +---@param text string @待匹配内容 +---@param sstring string @其实内容 +---@param start integer @起始位置 +function string.startswith(text, sstring, start) + assert(type(text) == 'string', "Invalid string `text`.") + assert(type(sstring) == 'string', "Invalid start string.") + return sfind(text, '^' .. sstring, start) and true or false +end + +---comment 判断结束位置是否指定内容 +---@param text string @待匹配内容 +---@param estring string @结束内容 +---@param over integer @结束位置 +function string.endswith(text, estring, over) + assert(type(text) == 'string', "Invalid string `text`.") + assert(type(estring) == 'string', "Invalid end string.") + return sfind(text, estring .. '$', over) and true or false +end + +---comment 将`text`里的`s1`替换为`s2`(最多`count`此) +---@param text string @原始内容 +---@param s1 string @匹配字符串 +---@param s2 string @替换字符串 +---@param count number @替换次数 +---@return string @替换后内容 +function string.replace (text, s1, s2, count) + assert(type(text) == 'string' and text ~= '', "Invalid text string.") + assert(type(s1) == 'string' and s1 ~= '', "Invalid s1 string.") + assert(type(s2) == 'string' and s2 ~= '', "Invalid s2 string.") + local s = sgsub(text, s1, s2, count) + return s +end + +---comment 字符串转换为字节数组 +---@param text string @待转换的字符串 +---@return table @转换后的字节数组 +function string.tobytes(text) + assert(type(text) == 'string' and text ~= '', "Invalid text string.") + local list = {} + for idx = 1, #text do + list[#list+1] = sbyte(text, idx) + end + return list +end + +---comment 向指定位置的字符串后插入字符串. +---@param text string @原始字符串 +---@param pos integer @待插入的位置 +---@param str string @待插入的字符串 +---@return string @返回插入后的字符串内容 +function string.insert(text, pos, str) + assert(type(text) == 'string' and text ~= '', "Invalid text string.") + assert(type(pos) == 'number', "Invalid pos integer.") + assert(type(str) == 'string' and str ~= '', "Invalid text string.") + return tconcat{ssub(text, 1, pos), str, ssub(text, pos + 1, -1)} +end + +local _, liconv = pcall(require, "liconv") + +---comment 替换iconv函数, 错误的实现会出现运行时错误. +---@param module table +---@param encode string @需保证`module[encode]`行为与`liconv.to`行为一致 +---@param decode string @需保证`module[decode]`行为与`liconv.from`行为一致 +function string.iconv(module, encode, decode) + liconv = { to = module[encode], from = module[decode] } +end + +---comment 使用iconv进行编码转换 +---@param text string @文本内容 +---@param encoding string @目标编码 +---@return string @转换后的文本 +function string.encode (text, encoding) + assert(type(text) == 'string', "Invalid string `text`.") + assert(type(encoding) == 'string', "Invalid string encoding.") + return assert(type(liconv) == 'table' and liconv, "Lua iconv is not supported.").to(encoding, text) +end + +---comment 使用iconv进行编码转换 +---@param text string @文本内容 +---@param decoding string @原始编码 +---@return string @转换后的文本 +function string.decode (text, decoding) + assert(type(text) == 'string', "Invalid string `text`.") + assert(type(decoding) == 'string', "Invalid string encoding.") + return assert(type(liconv) == 'table' and liconv, "Lua iconv is not supported.").from(decoding, text) +end \ No newline at end of file diff --git a/lualib/utils/table.lua b/lualib/utils/table.lua new file mode 100644 index 00000000..faf3ceab --- /dev/null +++ b/lualib/utils/table.lua @@ -0,0 +1,373 @@ +local type = type +local pairs = pairs +local ipairs = ipairs +local rawlen = rawlen +local assert = assert +local select = select + +local ceil = math.ceil +local floor = math.floor +local sformat = string.format + +local tsort = table.sort +local tconcat = table.concat +local tunpack = table.unpack + +---comment 获取`table`长度 +---@param tab table +---@return integer +function table.len (tab) + assert(type(tab) == 'table', "Invalid table.") + return #tab +end + +---comment 获取`table`长度(跳过元方法) +---@param tab table +---@return integer +function table.rawlen (tab) + assert(type(tab) == 'table', "Invalid table.") + if rawlen then + return rawlen(tab) + end + return #tab +end + +---comment 返回数组内最大`value` +---@param tab table +---@return number +function table.max(tab) + assert(type(tab) == 'table', "Invalid table.") + local s, e = 2, #tab + local max_value = nil + if e < 1 then + return max_value + end + max_value = tab[1] + while s < e do + if max_value < tab[s] then + max_value = tab[s] + end + s = s + 1 + if max_value < tab[e] then + max_value = tab[e] + end + e = e - 1 + end + return max_value +end + +---comment 返回数组内最小`value` +---@param tab table +---@return number +function table.min(tab) + assert(type(tab) == 'table', "Invalid table.") + local s, e = 2, #tab + local min_value = nil + if e < 1 then + return min_value + end + min_value = tab[1] + while s < e do + if min_value > tab[s] then + min_value = tab[s] + end + s = s + 1 + if min_value > tab[e] then + min_value = tab[e] + end + e = e - 1 + end + return min_value +end + +---comment 获取`table`所有`key` +---@param tab table +---@return table @返回`keys`数组 +function table.keys (tab) + assert(type(tab) == 'table', "Invalid table.") + local list = {} + for k, _ in pairs(tab) do + list[#list+1] = k + end + return list +end + +---comment 获取`table`所有`value` +---@param tab table +---@return table @返回`value`数组 +function table.values (tab) + assert(type(tab) == 'table', "Invalid table.") + local list = {} + for _, v in pairs(tab) do + list[#list+1] = v + end + return list +end + +---comment 向左旋转数组 +---@param tab table +---@return table @返回旋转完成后的数组 +function table.lrotate(tab) + assert(type(tab) == 'table', "Invalid table.") + local last = tab[1] + for i = 2, #tab do + tab[i-1] = tab[i] + end + tab[#tab] = last + return tab +end + +---comment 向右旋转数组 +---@param tab table +---@return table @返回旋转完成后的数组 +function table.rrotate(tab) + assert(type(tab) == 'table', "Invalid table.") + local last = tab[#tab] + for i = #tab - 1, 1, -1 do + tab[i+1] = tab[i] + end + tab[1] = last + return tab +end + +---comment 反转数组 +---@param tab table @待反转的数组 +---@return table @返回`tab` +function table.reverse (tab) + assert(type(tab) == 'table', "Invalid table.") + local s, e = 1, #tab + while s < e do + tab[s], tab[e] = tab[e], tab[s] + s = s + 1 + e = e - 1 + end + return tab +end + +local sorts = { + [1] = function (list1, list2) + return list1[1] < list2[1] + end, + [2] = function (list1, list2) + return list2[1] < list1[1] + end, + [3] = function (list1, list2) + return list1[2] < list2[2] + end, + [4] = function (list1, list2) + return list2[2] < list1[2] + end, +} + +---comment 格式化输出表内容 +---@param tab table @原始表 +---@param fmt string | nil @可自定义`key`、`value`格式内容 +---@param sep string | nil @如果表内字段多, 多个`format`字符串连接时候可能会需要用到. +---@param sort integer | nil @默认为(1.key升序),可选:(2.key降序)、(3.value升序)、(4.value降序) +---@return string @最终的输出内容 +function table.format (tab, fmt, sep, sort) + assert(type(tab) == 'table', "Invalid table.") + local list = {} + for k, v in pairs(tab) do + list[#list+1] = {k, v} + end + -- 根据key进行升序排列 + tsort(list, sorts[tonumber(sort) or 1] or sorts[1]) + -- 开始合并数据 + for idx, item in ipairs(list) do + list[idx] = sformat(fmt or "%s=%s", item[1], item[2]) + end + return tconcat(list, sep) +end + +---comment 合并2个表 +---@param table1 table | nil @`table1`和`table2`只能有一个为空 +---@param table2 table | nil @`table1`和`table2`只能有一个为空 +---@param new_tab table | nil @可以外部传入或者内部创建 +---@return table +local function table_merge(table1, table2, new_tab) + local tab = new_tab or {} + if type(table1) == 'table' then + for k, v in pairs(table1) do + tab[k] = type(v) ~= 'table' and v or table_merge(v, {}) + end + end + if type(table2) == 'table' then + for k, v in pairs(table2) do + tab[k] = type(v) ~= 'table' and v or table_merge(v, {}) + end + end + return tab +end + +---comment 创建新表来合并2个字典表(不存在引用问题) +---@param table1 table +---@param table2 table +---@return table @始终返回新表 +function table.new (table1, table2) + assert(table1 ~= table2, "You cannot merge two tables of the same type.") + return table_merge(table1, table2, {}) +end + +---comment 合并表`table2`内容到表`table1`内(不存在引用问题) +---@param table1 table +---@param table2 table +---@return table @返回`table1` +function table.lmerge (table1, table2) + assert(table1 ~= table2, "You cannot merge two tables of the same type.") + return table_merge(nil, table2, table1) +end + +---comment 合并表`table1`内容到表`table2`内(不存在引用问题) +---@param table1 table +---@param table2 table +---@return table @返回`table2` +function table.rmerge (table1, table2) + assert(table1 ~= table2, "You cannot merge two tables of the same type.") + return table_merge(nil, table1, table2) +end + +---comment 数组转哈希表 +---@param tab table @待转换的数组 +---@return table @返回转换后的哈希表 +function table.tohash(tab) + assert(type(tab) == 'table', "Invalid table.") + local len = #tab + assert(ceil(len / 2) == floor(len / 2), "Invalid key value amount.") + local hashtab = {} + for idx = 1, len, 2 do + hashtab[tab[idx]] = tab[idx+1] + end + return hashtab +end + +---comment 哈希表转数组(列表) +---@param tab table @待转换的哈希表 +---@return table @返回转换后的数组(列表) +function table.tolist(tab) + assert(type(tab) == 'table', "Invalid table.") + local idx = 1 + local list = {} + for k, v in pairs(tab) do + list[idx] = k + idx = idx + 1 + list[idx] = v + idx = idx + 1 + end + return list +end + +---comment 将一个或者多可`key`->`value`构建为哈希表或者数组 +---@return table @返回构建好的table +function table.wrap(...) + local len = select("#", ...) + assert(ceil(len / 2) == floor(len / 2), "Invalid key value amount.") + local list = {...} + local tab = {} + for idx = 1, len, 2 do + tab[list[idx]] = list[idx+1] + end + return tab +end + +---comment 求一个序列或者多个序列进行函数映射之后的值 +---@param func function @回调函数, 返回值不能为`nil` +---@param list1 table @多个数组(至少一个) +---@return table @返回新数组 +function table.map(func, list1, ...) + assert(type(func) == 'function', "Invalid `function`.") + assert(type(list1) == 'table', "Invalid `table`.") + local newlist = {} + local lists = {list1, ...} + local count = #lists + local len + for i = 1, count do + local l = #(lists[i]) + if not len or len <= l then + len = l + end + end + local args = {} + for index = 1, len do + for idx, list in ipairs(lists) do + args[idx] = list[index] + end + local o = func(tunpack(args)) + if o ~= nil then + newlist[#newlist+1] = o + end + end + return newlist +end + +---comment 过滤不符合函数条件的元素并返回新的结果数组 +---@param func function @回调函数, 返回值必须是`boolean` +---@param list table @原始数组 +---@return table @返回新数组 +function table.filter(func, list) + assert(type(func) == 'function', "Invalid `function`.") + assert(type(list) == 'table', "Invalid `table`.") + local newlist = {} + for i = 1, #list do + local ok = func(list[i]) + assert(type(ok) == 'boolean', "callback must return `true` or `false`.") + if ok then + newlist[#newlist+1] = list[i] + end + end + return newlist +end + +---comment 过滤不符合函数条件的元素并返回新的结果数组 +---@param func function @回调函数, 返回值必须是`boolean` +---@param list table @原始数组 +---@return number @返回计算结果 +function table.reduce(func, list) + assert(type(func) == 'function', "Invalid `function`.") + assert(type(list) == 'table', "Invalid `table`.") + local len = #list + if len <= 1 then + if len == 1 then + return list[1] + end + error("can't passed empty array.") + end + local args = {list[1], nil} + for i = 2, len do + args[2] = list[i] + local result = func(args[1], args[2]) + if type(result) ~= 'number' then + error("return invalid result.") + end + args[1] = result + end + return args[1] +end + +---comment 检查`key`是否包含在多个参数集合中. +---@param key any @指定的`key` +---@param ... any @`1`个或`N`个参数组成的集合 +---@return boolean @包含返回`true`, 不包含返回`false` +function table.on(key, ...) + if not key then + return false + end + local tab = {...} + for i = 1, #tab do + if key == tab[i] then + return true + end + end + return false +end + +---comment 将就表的内容转移到新表内, 并且可以直接替换为新表引用(可用于释放大表内存) +---@param tab table @之前的表 +---@return table @新建的表 +function table.replace(tab) + local t = {} + for key, value in pairs(tab) do + t[key] = value + end + return t +end diff --git a/lualib/webhook/dingtalk.lua b/lualib/webhook/dingtalk.lua new file mode 100644 index 00000000..8b24036a --- /dev/null +++ b/lualib/webhook/dingtalk.lua @@ -0,0 +1,111 @@ +local httpc = require "httpc" +local json = require "json" + +local class = require "class" + +local type = type +local ipairs = ipairs + +local function check_error (code, response) + if code ~= 200 then + return nil, "请求失败." .. (response or "") + end + local r = json.decode(response) + if type(r) ~= 'table' then + return nil, "未知的回应." + end + if r.errcode ~= 0 then + return nil, r.errmsg + end + return true +end + +local dingtalk = { __VERSION__ = 0.1, robot = "https://oapi.dingtalk.com/robot/send?access_token=" } + +-- 发送text消息 +function dingtalk.send_text (opt) + assert(type(opt) == 'table', "dingtalk error: 无效的参数.") + assert(type(opt.token) == 'string' and opt.token ~= '', "dingtalk error: 无效的Token参数.") + assert(type(opt.content) == 'string' and opt.content ~= '', "dingtalk error: 无效的content参数.") + local allmobiles = {} + if type(opt.mobiles) == 'table' then + for _, phone in ipairs(opt.mobiles) do + allmobiles[#allmobiles+1] = tostring(phone) + end + end + local code, response = httpc.json(dingtalk.robot..opt.token, nil, json.encode({ msgtype = "text", text = { content = opt.content }, at = { atMobiles = allmobiles, isAtAll = opt.atall == true} })) + return check_error(code, response) +end + +-- 发送link消息 +function dingtalk.send_link (opt) + assert(type(opt) == 'table', "dingtalk error: 无效的参数.") + assert(type(opt.token) == 'string' and opt.token ~= '', "dingtalk error: 无效的Token参数.") + assert(type(opt.msg_link) == 'string' and opt.msg_link ~= '', "dingtalk error: 无效的msg_link(必须为非空字符串).") + assert(type(opt.msg_title) == 'string' and opt.msg_title ~= '', "dingtalk error: 无效的msg_title(必须为非空字符串).") + assert(type(opt.msg_describe) == 'string' and opt.msg_describe ~= '', "dingtalk error: 无效的msg_describe(必须为非空字符串).") + local code, response = httpc.json(dingtalk.robot..opt.token, nil, json.encode({ msgtype = "link", link = { title = opt.msg_title, text = opt.msg_describe, messageUrl = opt.msg_link, picUrl = opt.msg_pic } })) + return check_error(code, response) +end + +-- 发送markdown消息 +function dingtalk.send_markdown (opt) + assert(type(opt) == 'table', "dingtalk error: 无效的参数.") + assert(type(opt.token) == 'string' and opt.token ~= '', "dingtalk error: 无效的Token参数.") + assert(type(opt.msg_title) == 'string' and opt.msg_title ~= '', "dingtalk error: 无效的msg_title(必须为非空字符串).") + assert(type(opt.msg_content) == 'string' and opt.msg_content ~= '', "dingtalk error: 无效的msg_content(必须为非空字符串).") + + local allmobiles = {} + if type(opt.mobiles) == 'table' then + for _, phone in ipairs(opt.mobiles) do + allmobiles[#allmobiles+1] = tostring(phone) + end + end + local code, response = httpc.json(dingtalk.robot..opt.token, nil, json.encode({ + msgtype = "markdown", markdown = { title = opt.msg_title, text = opt.msg_content }, at = { atMobiles = opt.mobiles, isAtAll = opt.atall == true } + })) + return check_error(code, response) +end + +-- 发送actionCard消息 +function dingtalk.send_actioncard (opt) + assert(type(opt) == 'table', "dingtalk error: 无效的参数.") + assert(type(opt.token) == 'string' and opt.token ~= '', "dingtalk error: 无效的Token参数.") + assert(type(opt.msg_title) == 'string' and opt.msg_title ~= '', "dingtalk error: 无效的msg_title(必须为非空字符串).") + assert(type(opt.msg_describe) == 'string' and opt.msg_describe ~= '', "dingtalk error: 无效的msg_describe(必须为非空字符串).") + + local btns, single_title, single_url = nil, nil, nil + if type(opt.single) == 'table' and (type(opt.single.title) == 'string' and opt.single.title ~= '') and (type(opt.single.url) == 'string' and opt.single.url ~= '') then + single_title, single_url = opt.single.title, opt.single.url + end + + if not single_title and not single_url and type(opt.btns) == 'table' and #opt.btns >= 1 then + btns = {} + for index, btn in ipairs(opt.btns) do + assert(type(btn.title) == 'string' and btn.title ~= '', 'dingtalk error: btns第'..index..'个参数title无效.') + assert(type(btn.url) == 'string' and btn.url ~= '', 'dingtalk error: btns第'..index..'个参数url无效.') + btns[#btns+1] = {title = btn.title, actionURL = btn.url} + end + end + + local code, response = httpc.json(dingtalk.robot..opt.token, nil, json.encode({ msgtype = "actionCard", actionCard = { title = opt.msg_title, text = opt.msg_describe, singleURL = single_url, singleTitle = single_title, btns = btns, hideAvatar = opt.hide_avatar and '1' or '0', btnOrientation = opt.btn_orientation and '1' or '0' }})) + return check_error(code, response) +end + +-- 发送FeedCard消息 +function dingtalk.send_feedcard (opt) + assert(type(opt) == 'table', "dingtalk error: 无效的参数.") + assert(type(opt.token) == 'string' and opt.token ~= '', "dingtalk error: 无效的Token参数.") + assert(type(opt.msg_links) == 'table' and #opt.msg_links >= 1, "dingtalk error: 无效的msg_links参数(内部至少有一条消息).") + local links = {} + for index, link in ipairs(opt.msg_links) do + assert(type(link) == 'table', "dingtalk error: 无效的消息"..index) + assert(type(link.msg_title) == 'string' and link.msg_title ~= '', "dingtalk error: 第"..index..'个消息的msg_title无效.') + assert(type(link.msg_link) == 'string' and link.msg_link ~= '', "dingtalk error: 第"..index..'个消息的msg_link无效.') + links[#links+1] = { title = link.msg_title, messageURL = link.msg_link, picURL = link.msg_pic } + end + local code, response = httpc.json(dingtalk.robot..opt.token, nil, json.encode({ msgtype = "feedCard", feedCard = { links = links } })) + return check_error(code, response) +end + +return dingtalk diff --git a/lualib/XmlParser.lua b/lualib/xml2lua/XmlParser.lua similarity index 85% rename from lualib/XmlParser.lua rename to lualib/xml2lua/XmlParser.lua index 04c55c65..b5dc250f 100644 --- a/lualib/XmlParser.lua +++ b/lualib/xml2lua/XmlParser.lua @@ -1,15 +1,4 @@ ---- this code fork from github.com/manoelcampos/xml2lua - -local tonumber = tonumber -local getmetatable = getmetatable -local setmetatable = setmetatable -local error = error -local pairs = pairs -local ipairs = ipairs -local table = table -local string = string - ---- @module Class providing the actual XML parser. +-- @module Class providing the actual XML parser. -- Available options are: -- * stripWS -- Strip non-significant whitespace (leading/trailing) @@ -30,7 +19,7 @@ local string = string ---Converts the decimal code of a character to its corresponding char --if it's a graphical char, otherwise, returns the HTML ISO code --for that decimal value in the format &#code ---@param code the decimal value to convert to its respective character +--@param code number @the decimal value to convert to its respective character local function decimalToHtmlChar(code) local n = tonumber(code) if n >= 0 and n < 256 then @@ -43,7 +32,7 @@ end ---Converts the hexadecimal code of a character to its corresponding char --if it's a graphical char, otherwise, returns the HTML ISO code --for that hexadecimal value in the format ode ---@param code the hexadecimal value to convert to its respective character +--@param code number @the hexadecimal value to convert to its respective character local function hexadecimalToHtmlChar(code) local n = tonumber(code, 16) if n >= 0 and n < 256 then @@ -67,7 +56,8 @@ local XmlParser = { _WS = '^%s*$', _DTD1 = '', _DTD2 = '', - _DTD3 = '', + --_DTD3 = '', + _DTD3 = '', _DTD4 = '', _DTD5 = '', @@ -104,11 +94,11 @@ local XmlParser = { } --- Instantiates a XmlParser object. ---@param _handler Handler module to be used to convert the XML string +--@param _handler table @Handler module to be used to convert the XML string -- to another formats. See the available handlers at the handler directory. -- Usually you get an instance to a handler module using, for instance: -- local handler = require("xmlhandler/tree"). ---@param _options Options for this XmlParser instance. +--@param _options table @Options for this XmlParser instance. --@see XmlParser.options function XmlParser.new(_handler, _options) local obj = { @@ -123,18 +113,18 @@ function XmlParser.new(_handler, _options) end ---Checks if a function/field exists in a table or in its metatable ---@param table the table to check if it has a given function ---@param elementName the name of the function/field to check if exists ---@return true if the function/field exists, false otherwise +--@param table table @the table to check if it has a given function +--@param elementName string @the name of the function/field to check if exists +--@return boolean @true if the function/field exists, false otherwise local function fexists(table, elementName) if table == nil then return false end - if table[elementName] ~= nil then - return true - else + if table[elementName] == nil then return fexists(getmetatable(table), elementName) + else + return true end end @@ -164,10 +154,10 @@ local function parseEntities(self, s) end --- Parses a string representing a tag. ---@param s String containing tag text ---@return a {name, attrs} table --- where name is the name of the tag and attrs --- is a table containing the atributtes of the tag +--- where name is the name of the tag and attrs +--- is a table containing the atributtes of the tag +--@param s string @String containing tag text +--@return table @{name, attrs} table local function parseTag(self, s) local tag = { name = string.gsub(s, self._TAG, '%1'), @@ -178,7 +168,7 @@ local function parseTag(self, s) tag.attrs[k] = parseEntities(self, v) tag.attrs._ = 1 end - + string.gsub(s, self._ATTR1, parseFunction) string.gsub(s, self._ATTR2, parseFunction) @@ -211,7 +201,7 @@ local function parseXmlDeclaration(self, xml, f) end if fexists(self.handler, 'decl') then - self.handler:decl(tag, f.match, f.endMatch) + self.handler:decl(tag, f.match, f.endMatch) end return tag @@ -256,44 +246,27 @@ end local function _parseDtd(self, xml, pos) -- match,endMatch,root,type,name,uri,internal - local m,e,r,t,n,u,i - - m,e,r,t,u,i = string.find(xml, self._DTD1,pos) - if m then - return m, e, {_root=r,_type=t,_uri=u,_internal=i} - end + local dtdPatterns = {self._DTD1, self._DTD2, self._DTD3, self._DTD4, self._DTD5} - m,e,r,t,n,u,i = string.find(xml, self._DTD2,pos) - if m then - return m, e, {_root=r,_type=t,_name=n,_uri=u,_internal=i} - end - - m,e,r,i = string.find(xml, self._DTD3,pos) - if m then - return m, e, {_root=r,_internal=i} - end - - m,e,r,t,u = string.find(s,self._DTD4,pos) - if m then - return m,e,{_root=r,_type=t,_uri=u} - end - - m,e,r,t,n,u = string.find(s,self._DTD5,pos) - if m then - return m,e,{_root=r,_type=t,_name=n,_uri=u} + for i, dtd in pairs(dtdPatterns) do + local m,e,r,t,n,u,i = string.find(xml, dtd, pos) + if m then + return m, e, {_root=r, _type=t, _name=n, _uri=u, _internal=i} + end end return nil end local function parseDtd(self, xml, f) - f.match, f.endMatch, attrs = self:_parseDtd(xml, f.pos) + f.match, f.endMatch = _parseDtd(self, xml, f.pos) if not f.match then err(self, self._errstr.dtdErr, f.pos) end if fexists(self.handler, 'dtd') then - self.handler:dtd(attrs._root, attrs, f.match, f.endMatch) + local tag = {name="DOCTYPE", value=string.sub(xml, f.match+10, f.endMatch-1)} + self.handler:dtd(tag, f.match, f.endMatch) end end @@ -388,7 +361,7 @@ local function parseTagType(self, xml, f) end --- Get next tag (first pass - fix exceptions below). ---@return true if the next tag could be got, false otherwise +--@return boolean @true if the next tag could be got, false otherwise local function getNextTag(self, xml, f) f.match, f.endMatch, f.text, f.endt1, f.tagstr, f.endt2 = string.find(xml, self._XML, f.pos) if not f.match then @@ -413,8 +386,8 @@ local function getNextTag(self, xml, f) end --Main function which starts the XML parsing process ---@param xml the XML string to parse ---@param parseAttributes indicates if tag attributes should be parsed or not. +--@param xml string @the XML string to parse +--@param parseAttributes boolean @indicates if tag attributes should be parsed or not. -- If omitted, the default value is true. function XmlParser:parse(xml, parseAttributes) if type(self) ~= "table" or getmetatable(self) ~= XmlParser then @@ -461,4 +434,4 @@ function XmlParser:parse(xml, parseAttributes) end XmlParser.__index = XmlParser -return XmlParser \ No newline at end of file +return XmlParser diff --git a/lualib/xml2lua/init.lua b/lualib/xml2lua/init.lua new file mode 100644 index 00000000..397e1f26 --- /dev/null +++ b/lualib/xml2lua/init.lua @@ -0,0 +1,38 @@ +-- 对xml2lua的简单封装, 简化xml2lua调用流程 +local xml2lua = require "xml2lua.xml2lua" +local xml2lua_handler = require "xml2lua.xmlhandler.tree" + +local type = type +local pcall = pcall + +local xml = {} + +-- xml字符串解析 +function xml.parser(xml_data) + local cls = xml2lua.parser(xml2lua_handler:new()) + -- cls:parse(xml_data) + local ok, err = pcall(cls.parse, cls, xml_data) + if not ok then + return err + end + return cls.handler.root +end + +-- xml文件读取 +function xml.load(xml_path) + if type(xml_path) ~= 'string' or xml_path == '' then + return nil, '无效的xml文件路径.' + end + local xfile, error = xml2lua.loadFile(xml_path) + if xfile then + return xml.parser(xfile) + end + return xfile, error +end + +-- 将table序列化为xml +function xml.toxml( ... ) + return xml2lua.toXml(...) +end + +return xml diff --git a/lualib/xml2lua.lua b/lualib/xml2lua/xml2lua.lua old mode 100644 new mode 100755 similarity index 54% rename from lualib/xml2lua.lua rename to lualib/xml2lua/xml2lua.lua index 0725fbed..d7222be3 --- a/lualib/xml2lua.lua +++ b/lualib/xml2lua/xml2lua.lua @@ -1,6 +1,4 @@ ---- this code fork from github.com/manoelcampos/xml2lua - ---- @module Module providing a non-validating XML stream parser in Lua. +-- @module Module providing a non-validating XML stream parser in Lua. -- -- Features: -- ========= @@ -51,21 +49,12 @@ -- --@author Paul Chakravarti (paulc@passtheaardvark.com) --@author Manoel Campos da Silva Filho -local class = require "class" -local XmlParser = require("XmlParser") - -local tonumber = tonumber -local getmetatable = getmetatable -local setmetatable = setmetatable -local error = error -local pairs = pairs -local ipairs = ipairs -local table = table -local string = string +local xml2lua = {} +local XmlParser = require("xml2lua.XmlParser") ---Recursivelly prints a table in an easy-to-ready format ---@param tb The table to be printed ---@param level the indentation level to start with +--@param tb table @The table to be printed +--@param level integer @the indentation level to start with local function printableInternal(tb, level) if tb == nil then return @@ -84,16 +73,13 @@ local function printableInternal(tb, level) end ---Instantiates a XmlParser object to parse a XML string ---@param handler Handler module to be used to convert the XML string +--@param handler table Handler module to be used to convert the XML string --to another formats. See the available handlers at the handler directory. -- Usually you get an instance to a handler module using, for instance: -- local handler = require("xmlhandler/tree"). ---@return a XmlParser object used to parse the XML +--@return string @XmlParser object used to parse the XML --@see XmlParser - -local xml2lua = {} - -function xml2lua.parser(handler) +function xml2lua.parser(handler) if handler == xml2lua then error("You must call xml2lua.parse(handler) instead of xml2lua:parse(handler)") end @@ -111,15 +97,15 @@ function xml2lua.parser(handler) end ---Recursivelly prints a table in an easy-to-ready format ---@param tb The table to be printed +--@param tb table The table to be printed function xml2lua.printable(tb) printableInternal(tb) end ---Handler to generate a string prepresentation of a table --Convenience function for printHandler (Does not support recursive tables). ---@param t Table to be parsed ---@return a string representation of the table +--@param t table to be parsed +--@return string @representation of the table function xml2lua.toString(t) local sep = '' local res = '' @@ -140,16 +126,86 @@ function xml2lua.toString(t) end --- Loads an XML file from a specified path --- @param xmlFilePath the path for the XML file to load --- @return the XML loaded file content +-- @param xmlFilePath string @the path for the XML file to load +-- @return string @the XML loaded file content function xml2lua.loadFile(xmlFilePath) local f, e = io.open(xmlFilePath, "r") if f then --Gets the entire file content and stores into a string - return f:read("*a") + local content = f:read("*a") + f:close() + return content end error(e) end +---comment Gets an _attr element from a table that represents the attributes of an XML tag, +--and generates a XML String representing the attibutes to be inserted +--into the openning tag of the XML +--@param attrTable table @from where the _attr field will be got +--@return string @a XML String representation of the tag attributes +local function attrToXml(attrTable) + local s = "" + for k, v in pairs(attrTable or {}) do + s = s .. " " .. k .. "=" .. '"' .. v .. '"' + end + return s +end + +---Gets the first key of a given table +local function getFirstKey(tb) + if type(tb) == "table" then + for k, _ in pairs(tb) do + return k + end + return nil + end + + return tb +end + +---Converts a Lua table to a XML String representation. +--@param tb table @Table to be converted to XML +--@param tableName string @Name of the table variable given to this function, to be used as the root tag. +--@param level integer @Only used internally, when the function is called recursively to print indentation +--@return string @String representing the table content in XML +function xml2lua.toXml(tb, tableName, level) + local level = level or 1 + local firstLevel = level + local spaces = string.rep(' ', level*2) + local xmltb = level == 1 and {'<'..tableName..'>'} or {} + + for k, v in pairs(tb) do + if type(v) == "table" then + --If the keys of the table are a number, it represents an array + if type(k) == "number" then + local attrs = attrToXml(v._attr) + v._attr = nil + table.insert(xmltb, + spaces..'<'..tableName..attrs..'>\n'..xml2lua.toXml(v, tableName, level+1).. + '\n'..spaces..'') + else + level = level + 1 + if type(getFirstKey(v)) == "number" then + table.insert(xmltb, spaces..xml2lua.toXml(v, k, level)) + else + local attrs = attrToXml(v._attr) + v._attr = nil + table.insert(xmltb, + spaces..'<'..k..attrs..'>\n'.. xml2lua.toXml(v, k, level+1).. + '\n'..spaces..'') + end + end + else + table.insert(xmltb, spaces..'<'..k..'>'..tostring(v)..'') + end + end + + if firstLevel == 1 then + table.insert(xmltb, '\n') + end + return table.concat(xmltb, "\n") +end + return xml2lua \ No newline at end of file diff --git a/lualib/xmlhandler/README.md b/lualib/xml2lua/xmlhandler/README.md old mode 100644 new mode 100755 similarity index 99% rename from lualib/xmlhandler/README.md rename to lualib/xml2lua/xmlhandler/README.md index a1a5e326..e70323f2 --- a/lualib/xmlhandler/README.md +++ b/lualib/xml2lua/xmlhandler/README.md @@ -12,4 +12,4 @@ Then, you have to use one the handler instance when getting an instance of the X Notice the module `xml2lua` should have been loaded before using `require("xml2lua")`. This way, the handler is called internally when the `parser:parse(xml)` function is called. -Check the documentation on the root directory for complete examples. +Check the documentation on the root directory for complete examples. \ No newline at end of file diff --git a/lualib/xmlhandler/dom.lua b/lualib/xml2lua/xmlhandler/dom.lua old mode 100644 new mode 100755 similarity index 77% rename from lualib/xmlhandler/dom.lua rename to lualib/xml2lua/xmlhandler/dom.lua index 7ed27fc7..236bbb45 --- a/lualib/xmlhandler/dom.lua +++ b/lualib/xml2lua/xmlhandler/dom.lua @@ -1,4 +1,12 @@ ----- @module Handler to generate a DOM-like node tree structure with +local function init() + return { + options = {commentNode=1, piNode=1, dtdNode=1, declNode=1}, + current = { _children = {n=0}, _type = "ROOT" }, + _stack = {} + } +end + +-- @module Handler to generate a DOM-like node tree structure with -- a single ROOT node parent - each node is a table comprising -- the fields below. -- @@ -26,14 +34,24 @@ -- --@author Paul Chakravarti (paulc@passtheaardvark.com) --@author Manoel Campos da Silva Filho -local dom = { - options = {commentNode=1, piNode=1, dtdNode=1, declNode=1}, - current = { _children = {n=0}, _type = "ROOT" }, - _stack = {} -} +local dom = init() + +---Instantiates a new handler object. +--Each instance can handle a single XML. +--By using such a constructor, you can parse +--multiple XML files in the same application. +--@return table @the handler instance +function dom:new() + local obj = init() + + obj.__index = self + setmetatable(obj, self) + + return obj +end ---Parses a start tag. --- @param tag a {name, attrs} table +-- @param tag table @a {name, attrs} table -- where name is the name of the tag and attrs -- is a table containing the atributtes of the tag function dom:starttag(tag) @@ -54,7 +72,8 @@ function dom:starttag(tag) end ---Parses an end tag. --- @param tag a {name, attrs} table +-- @param tag table @a {name, attrs} table +-- @param s string @a {name, attrs} table -- where name is the name of the tag and attrs -- is a table containing the atributtes of the tag function dom:endtag(tag, s) @@ -66,10 +85,11 @@ function dom:endtag(tag, s) end table.remove(self._stack) + self.current = self._stack[#self._stack] end ---Parses a tag content. --- @param text text to process +-- @param text string @text to process function dom:text(text) local node = { _type = "TEXT", _text = text @@ -78,7 +98,7 @@ function dom:text(text) end ---Parses a comment tag. --- @param text comment text +-- @param text string @comment text function dom:comment(text) if self.options.commentNode then local node = { _type = "COMMENT", @@ -89,7 +109,7 @@ function dom:comment(text) end --- Parses a XML processing instruction (PI) tag --- @param tag a {name, attrs} table +-- @param tag table @a {name, attrs} table -- where name is the name of the tag and attrs -- is a table containing the atributtes of the tag function dom:pi(tag) @@ -103,7 +123,7 @@ function dom:pi(tag) end ---Parse the XML declaration line (the line that indicates the XML version). --- @param tag a {name, attrs} table +-- @param tag table @a {name, attrs} table -- where name is the name of the tag and attrs -- is a table containing the atributtes of the tag function dom:decl(tag) @@ -117,7 +137,7 @@ function dom:decl(tag) end ---Parses a DTD tag. --- @param tag a {name, attrs} table +-- @param tag table @a {name, attrs} table -- where name is the name of the tag and attrs -- is a table containing the atributtes of the tag function dom:dtd(tag) @@ -132,4 +152,5 @@ end ---Parses CDATA tag content. dom.cdata = dom.text -return dom \ No newline at end of file +dom.__index = dom +return dom diff --git a/lualib/xml2lua/xmlhandler/print.lua b/lualib/xml2lua/xmlhandler/print.lua new file mode 100755 index 00000000..83c4a146 --- /dev/null +++ b/lualib/xml2lua/xmlhandler/print.lua @@ -0,0 +1,92 @@ +-- @module Handler to generate a simple event trace which +-- outputs messages to the terminal during the XML +-- parsing, usually for debugging purposes. +-- +-- License: +-- ======== +-- +-- This code is freely distributable under the terms of the [MIT license](LICENSE). +-- +--@author Paul Chakravarti (paulc@passtheaardvark.com) +--@author Manoel Campos da Silva Filho +local print = {} + +---Parses a start tag. +-- @param tag table @a {name, attrs} table +-- where name is the name of the tag and attrs +-- is a table containing the atributtes of the tag +function print:starttag(tag) + io.write("Start : "..tag.name.."\n") + if tag.attrs then + for k,v in pairs(tag.attrs) do + io.write(string.format(" + %s='%s'\n", k, v)) + end + end +end + +---Parses an end tag. +-- @param tag table @a {name, attrs} table +-- where name is the name of the tag and attrs +-- is a table containing the atributtes of the tag +function print:endtag(tag) + io.write("End : "..tag.name.."\n") +end + +---Parses a tag content. +-- @param text string @text to process +function print:text(text) + io.write("Text : "..text.."\n") +end + +---Parses CDATA tag content. +-- @param text string @CDATA content to be processed +function print:cdata(text) + io.write("CDATA : "..text.."\n") +end + +---Parses a comment tag. +-- @param text string @comment text +function print:comment(text) + io.write("Comment : "..text.."\n") +end + +---Parses a DTD tag. +-- @param tag table @a {name, attrs} table +-- where name is the name of the tag and attrs +-- is a table containing the atributtes of the tag +function print:dtd(tag) + io.write("DTD : "..tag.name.."\n") + if tag.attrs then + for k,v in pairs(tag.attrs) do + io.write(string.format(" + %s='%s'\n", k, v)) + end + end +end + +--- Parse a XML processing instructions (PI) tag. +-- @param tag table @a {name, attrs} table +-- where name is the name of the tag and attrs +-- is a table containing the atributtes of the tag +function print:pi(tag) + io.write("PI : "..tag.name.."\n") + if tag.attrs then + for k,v in pairs(tag.attrs) do + io. write(string.format(" + %s='%s'\n",k,v)) + end + end +end + +---Parse the XML declaration line (the line that indicates the XML version). +-- @param tag table @a {name, attrs} table +-- where name is the name of the tag and attrs +-- is a table containing the atributtes of the tag +function print:decl(tag) + io.write("XML Decl : "..tag.name.."\n") + if tag.attrs then + for k,v in pairs(tag.attrs) do + io.write(string.format(" + %s='%s'\n", k, v)) + end + end +end + +return print \ No newline at end of file diff --git a/lualib/xmlhandler/tree.lua b/lualib/xml2lua/xmlhandler/tree.lua old mode 100644 new mode 100755 similarity index 87% rename from lualib/xmlhandler/tree.lua rename to lualib/xml2lua/xmlhandler/tree.lua index 7703799d..69320976 --- a/lualib/xmlhandler/tree.lua +++ b/lualib/xml2lua/xmlhandler/tree.lua @@ -3,12 +3,11 @@ local function init() root = {}, options = {noreduce = {}} } - - obj._stack = {obj.root, n=1} + obj._stack = {obj.root, n = 1} return obj end ---- @module XML Tree Handler. +-- @module XML Tree Handler. -- Generates a lua table from an XML content string. -- It is a simplified handler which attempts -- to generate a more 'natural' table based structure which @@ -61,7 +60,7 @@ local tree = init() --Each instance can handle a single XML. --By using such a constructor, you can parse --multiple XML files in the same application. ---@return the handler instance +--@return table @the handler instance function tree:new() local obj = init() @@ -73,11 +72,10 @@ end --Gets the first key of a table --@param tb table to get its first key ---@return the table's first key, nil if the table is empty ---or the given parameter if it isn't a table +--@return table @the table's first key, nil if the table is empty or the given parameter if it isn't a table local function getFirstKey(tb) if type(tb) == "table" then - for k, v in pairs(tb) do + for k, _ in pairs(tb) do return k end @@ -87,8 +85,10 @@ local function getFirstKey(tb) return tb end ---- Recursively removes redundant vectors for nodes --- with single child elements +--- Recursively removes redundant vectors for nodes with single child elements +---@param node table +---@param key string +---@param parent table function tree:reduce(node, key, parent) for k,v in pairs(node) do if type(v) == 'table' then @@ -104,7 +104,7 @@ function tree:reduce(node, key, parent) end ---Parses a start tag. --- @param tag a {name, attrs} table +-- @param tag table @a {name, attrs} table -- where name is the name of the tag and attrs -- is a table containing the atributtes of the tag function tree:starttag(tag) @@ -115,7 +115,7 @@ function tree:starttag(tag) --Table in the stack representing the tag being processed local current = self._stack[#self._stack] - + if current[tag.name] then table.insert(current[tag.name], node) else @@ -126,7 +126,7 @@ function tree:starttag(tag) end ---Parses an end tag. --- @param tag a {name, attrs} table +-- @param tag table @a {name, attrs} table -- where name is the name of the tag and attrs -- is a table containing the atributtes of the tag function tree:endtag(tag, s) @@ -142,18 +142,18 @@ function tree:endtag(tag, s) self:reduce(prev, nil, nil) end - local firstKey = getFirstKey(current) + getFirstKey(current) table.remove(self._stack) end ---Parses a tag content. --- @param t text to process -function tree:text(t) +-- @param text string text to process +function tree:text(text) local current = self._stack[#self._stack] - table.insert(current, t) + table.insert(current, text) end ---Parses CDATA tag content. tree.cdata = tree.text tree.__index = tree -return tree \ No newline at end of file +return tree diff --git a/lualib/xmlhandler/print.lua b/lualib/xmlhandler/print.lua deleted file mode 100644 index e8c03041..00000000 --- a/lualib/xmlhandler/print.lua +++ /dev/null @@ -1,108 +0,0 @@ ----@module Handler to generate a simple event trace which ---outputs messages to the terminal during the XML ---parsing, usually for debugging purposes. --- --- License: --- ======== --- --- This code is freely distributable under the terms of the [MIT license](LICENSE). --- ---@author Paul Chakravarti (paulc@passtheaardvark.com) ---@author Manoel Campos da Silva Filho -local print = {} - ----Parses a start tag. --- @param tag a {name, attrs} table --- where name is the name of the tag and attrs --- is a table containing the atributtes of the tag --- @param s position where the tag starts --- @param e position where the tag ends -function print:starttag(tag, s, e) - io.write("Start : "..tag.name.."\n") - if tag.attrs then - for k,v in pairs(tag.attrs) do - io.write(string.format(" + %s='%s'\n", k, v)) - end - end -end - ----Parses an end tag. --- @param tag a {name, attrs} table --- where name is the name of the tag and attrs --- is a table containing the atributtes of the tag --- @param s position where the tag starts --- @param e position where the tag ends -function print:endtag(tag, s, e) - io.write("End : "..tag.name.."\n") -end - ----Parses a tag content. --- @param text text to process --- @param s position where the tag starts --- @param e position where the tag ends -function print:text(text, s, e) - io.write("Text : "..text.."\n") -end - ----Parses CDATA tag content. --- @param text CDATA content to be processed --- @param s position where the tag starts --- @param e position where the tag ends -function print:cdata(text, s, e) - io.write("CDATA : "..text.."\n") -end - ----Parses a comment tag. --- @param text comment text --- @param s position where the tag starts --- @param e position where the tag ends -function print:comment(text, s, e) - io.write("Comment : "..text.."\n") -end - ----Parses a DTD tag. --- @param tag a {name, attrs} table --- where name is the name of the tag and attrs --- is a table containing the atributtes of the tag --- @param s position where the tag starts --- @param e position where the tag ends -function print:dtd(tag, s, e) - io.write("DTD : "..tag.name.."\n") - if tag.attrs then - for k,v in pairs(tag.attrs) do - io.write(string.format(" + %s='%s'\n", k, v)) - end - end -end - ---- Parse a XML processing instructions (PI) tag. --- @param tag a {name, attrs} table --- where name is the name of the tag and attrs --- is a table containing the atributtes of the tag --- @param s position where the tag starts --- @param e position where the tag ends -function print:pi(tag, s, e) - io.write("PI : "..tag.name.."\n") - if tag.attrs then - for k,v in pairs(tag.attrs) do - io. write(string.format(" + %s='%s'\n",k,v)) - end - end -end - ----Parse the XML declaration line (the line that indicates the XML version). --- @param tag a {name, attrs} table --- where name is the name of the tag and attrs --- is a table containing the atributtes of the tag --- @param s position where the tag starts --- @param e position where the tag ends -function print:decl(tag, s, e) - io.write("XML Decl : "..tag.name.."\n") - if tag.attrs then - for k,v in pairs(tag.attrs) do - io.write(string.format(" + %s='%s'\n", k, v)) - end - end -end - -return print \ No newline at end of file diff --git a/private1024.pem b/private1024.pem new file mode 100644 index 00000000..1939c3c3 --- /dev/null +++ b/private1024.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDc84CClRm5VqlzhvG7Izfff/VBhycuhxYEubSQp6NXT8virjlp +e/g0dW94d1UENsEZYiLxepVwkHKNjxlbKMiLTjk+hs+S2g6U7bIbFLClZAdD9Bf+ +wuKD5YUUX7cRLzFEIFCNLn9E7HZ6/8iRkMtMmZf6Dx0LG/WmZ3WN1CnqwwIDAQAB +AoGBAIOC7BrNZGJMks+QopEghUEiiHhYWZn4DcMCRddT8IUnmdNyn/mJMFMJEzBA +1vmLHaReJS5WKFy3nXdklVMNE0+7khDciu/sDTSgxtZJRWvBwdYmgkzNz8jjkOzH +ZCQuqUu12AS5aFieLRB6i/FeGZiJGEoT+/KBMlVNi11xJXsBAkEA/uu88SwnjnbY +nbMwiDyWhK3/QE3vr82DBg0Z9mrmCnw7iXjB5pKUJLQbAO0NiCbi/HEkwvRkQpgx +qzh8MB4gnQJBAN3i81AnOPPUAOS2Gh1jHpNyT/zr9BZ27aKbXL7eraUky3uEdNps +rWCboyXxR0aF0BSJM4K4rL+Alveqnv/M6t8CQEuTzJKcCqY8KgCnLY5WmDGB/Jku +Ag/XGC9lFvttugIFzwj02lfnwTAYjaD6pvZkwQsi6Ek8d7UetisTNg52ACkCQDAA +gybZ9WY6fR79jlTBNsIrPsa2vQ2HGQ3OkpfwUJyjgynrk+QVEsUNppP0yLinBkcL +D4u+LBEZ3o8h6Ffqmv0CQQCKBHHaAjYAhUd3lGrseN45QSw4VpARQ7/bBKimrq+d +JCNdEpTOt6eQs8t43uOCeDjZ0H33KXG8ofcLprYE5K4G +-----END RSA PRIVATE KEY----- diff --git a/private2048.pem b/private2048.pem new file mode 100644 index 00000000..90ef8afd --- /dev/null +++ b/private2048.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAuhsl0nR17nqITIkMYqzrhbT5me5+Q+G387spzR8KWbVujoRF +Qa6DNzZS8HPNmBW8HjVZJ29uBAAtpWpoERdwvT31Kag+q9eSpX1d02uTd43cESNT +b1bYCYd3LHdPZ9GSH3gIfD/fk2P8zJytzED7cT1gDUqzCcxi3cvH6/yHb6hmYmNs +Qo1MLTn3kxyzU32J3nhNEZTlHq1XE9ydqLVqDCMRPOUuOsXcIb9ASZh3ExSPU2cx ++8WMAWWoCw/kO7EJ4Vinl8NDbsRoyXjweekYZ0bH9YrN4y05KLMhsQhoxBdmKdcJ +ErPwYad17vtmhTpdwEpCriL/iZDLUDOz1G2ngwIDAQABAoIBAQC11cqZm0KS1NQR +e6JHU073oAB0f0qNRqB2GrvX9+kkB5pS0zfb2gfIzWIyH+OUIkBgf51xY3VpoUb4 +JUQy1uVHcZ71qbY6LnHREfG3nZdDK68Ga66czYxdmyc8ogJKnMAZ0SzxQXNQTlR1 +EuzY8fD7Do2nzwGppDJBJVdb4qvt0kAEBu+0O5srUuGNzJJzktAIwzk7aFpVhw+l +cCS6ZY5rRM0AeQ2tomVvf+81bs8JOG8T7JtJ+o2cL+bZjRKhCHliQZQzByEQtOZx +J8s0mr3Tj9FzXfy6pUYSO1+GiWgrjV0nnaKj+sqE/MuuNam/nA64us8kLE7RA3gH +n7tLJwDBAoGBAOLoQf6KuE7D0WTVj5tb/CnAtPsZ18tClvElPgex/32Cjwuwy/Du +yPn/EIWO2EJX1dtRdWe34OJYtOcZk4qbFiyB/rxjsr6UAPNO/tY55nW2OqZ944Dq +3duzUb3jVeAI3HyGksvHZwBJ5t7o80BBYUdk28G2ZNjaalL1HqgXzjxjAoGBANH3 +rZPQU3yvuFQdL3HlV3h7PNyNwkyjVMgD+jCdIrySIw0DqisJFgWoVxNl92lhUkir +ZPY8dzZ6uFh6tx12QyoO2VG6Y9Pf3GvjHC1yUQqWu///+jvHwUqdPPQgHXvwKSOU +eJVaseNHwBNV35AEdRwMDOsqQUN30lhoa5fy8AJhAoGAXQR9WU2gtJlNk5qAnl2d +B7i5+F3luqt3mS99OEZdyCPnZBF76S7aMLHBIh8mxDuhraC9EmGszN00e7BebWma +M3Cu7qeoNLwTj6qIiWV+9i5X6LyesNCXVmMyVTeGkqrPSDUapHL/5HxnKmYwodyr +dksAU27j9InFIHDfumTX5KUCgYEAoXsuCOeIvfVa+33ytlLfAe8t8KYpz80x8B52 +9Zp0U7jEskamQjDbugAs7+NU87wAj5kZrfL08HZTfuDqIgOJRjhjVOLX0eRyXpst +WZp4z378Gbfh2MYZV2w0q8BjTKV4zj9qudslwpm1FGnP5bA37RkrelVmGiB2Kr4s +OZGCmyECgYBhXtk5BfZocI+l2/R2Yncn6Gx8s4s7bD+Fr5LE3FkU1neOuk7Yf1Uu +xf41e3o5YBxbt51T+pNuzF3BMxsXvp9a5HxbPsmGE99BLFwR9DompoLKeUlJcy6f +xJyXi8HBgR0OWNoTjKgaUmTgqKK5LI49C9C4Jr4c3nelo6DBb66vyw== +-----END RSA PRIVATE KEY----- diff --git a/private4096.pem b/private4096.pem new file mode 100644 index 00000000..fdecc78a --- /dev/null +++ b/private4096.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKAIBAAKCAgEAqkEuMgW6/rprdpHgAdr4IoGNGSfTQpCvojGHxrt3JTj+Mir4 +lZJw17cvMVfbnpWkCZ0H2jcxLR9t/blCSsNDbxe99Xbib6q6SfIkAtL3BvUU0TTz +D5ZjZR+Ko3kA+wq6yHPa8KW49659IXJzEW9Ci2SJmj6lqrTw0F0ZXDso5ILU1Ujr +YDJdT23hyygh4SI8geDtfmv88iP+V2emhIFgq8BKvExcxlBjX4kzUU/qDddo0OUr +A/wpSSPPAkXYKZ4P/0SZqbkAHNfQFzMcGbf9sGAszXI1QjwomV95Ro0IJ8HCDG2h +BvdR4AqAXI3o+DumF7kHT2pHUUP8yrodVEwWcmCjO9bPh+bm3sketDIgjs6cwuko +hoZFr7aYp9o9l0B6lbQXdg8isut2ebXYru+Rz5KJ2Ca5fOk4PHHMbZ4murBUsQq+ +XT8q8Sldxl7IqPcSEKzLaW+JNMyZ4nX/njARA9cTJDTWp38EqxUdzo1ZifVMZBeI ++xIyqOuyfp/zUpaoeGbCuGQHJ8A7WHagQkh9x1gjOtlIdFuX2scSHrhbbsccmIPJ +J18MquM8SnauF2cW9oLo/8n0dMmaXJFqb5sKgjitPcy+usA6e8ZquwWuDheSuEo5 +p/S7ti02hGhWTzCWvq/nnZnEvUp642LKcIYI8oJxXAFEGPm3HaucWGPNuqECAwEA +AQKCAgBiv8OHiANZ086YyyteaB8cBIBOYucJ6Nm1/Xx/LCSDFnd5qardNj71H27j +882SQcEPQmFXlEOVpHErhNSKgI8QVHj9cqMLmb7LPQLeEHbvNh+I6GlCVTv5Xz4O +axTChwqnUWtfwP9zyWUSjUtohVvXdczKBiQYyzIR6K3Y7Qde4vOHce/zr8KnW1hn +eTONXUozGr8lYIUp/O58y8WtfU0Q0UuHw9Lbw6yfsPhu5ScgOBy3bPRyL0PTBE4B +R3mFSgSFTsjBxGfoUtSDYEWgNTOTpXTXBLMXY8U4kEPLQ+nDBYMDqmKHo9N9Wxnw +lO7Xa+F+SPeQfFg5LnHlM+XnCurJIjctFBC+MnrGOFRMyTonUZJO7/UwQl4GkF65 +66GEsfTe4b4/JK6wr86kH2VJsJ/AW3mwQLQKjgv65Z9WyDsc7KB0FWqjU++h8DPB +xQnbetnFL/q5tRCW2AlH/CLrckc543/jCk4KQcJ1lEyvokfkbl8aPacztIS4BKbq +bWNkI3TmpGIzdBPwqLCjx41G5Ct+TwBRfsK4hWvYiW86jlcrjxwDqcAjvxLMIJ5X +DdAQaRzPZQNg5vVqS+2GQ6AhMstwraMjRT/UMx5uoKS5toWRZQxdwn+ctvKC7sFU +FGbVNwXL+zaRH4bnrZ3oGFFrVdZKNyfRR9e7gQCmICxM5QUdAQKCAQEA1nkYb3eT +BiPSXXFWyhZhoF5+Db7cxdt0HDtYmryYz6H/J2EzRvUcVuMFcAmm0FJBBPinvawR +hYI1B20Q9RjQJmp2ffsXCUbkdq3w70UbNup6vC/4aokIVrt0Oh2bkuCiVV/f8y8B +YAhyYW6q1ril4m5KumrinYlzcF212wkpx9uL44rkNRFliCUV3F8RUomBmuHZ1V1G +PQRvpyHWZJwUeeUAWqKN5qCfaUFz/yaEX7EF4c7PXoVVp40OMBuDnZjkVod24kBr +uoj7JabAr1EPt8dQhyfZvwpwiyYfZa8FbdeFtXNLEe5FZMaBtzrAPW4Z89Sa48zj +lJVDELZ63W2F+QKCAQEAyzhJNDpxebbXKyRD90D+TxIc3VMWPMa8DuuVz99+ROzX +KIuxBeH2K2lEtj31aeLahppnbis4B6u7Yh05bV88n4mfBOsxUMhl0Be5QI/cfPXv +52UKYkZE6qzIOiOi1prJy6mUFqc2z55YESx1pDSa+SvJYI5XMr0c9UpkXbMc739M +g5bskx9tmZHoCGaJ++RBCP1hxpq39vJByruJSwjbKWsVMvU14fpy89yPiS40AfLF +3XQgBmy3MZ+0QVDUNdKSwBkcES8EkgALXo3eaCXSy+NqdqllUct/b3G0oUbc8TI7 +NRwtGTMdz2pecw9vXa/ig8Tq0ZlYMfKHOUg2QKvj6QKCAQEAqGbvM9789447CJoM +3qMSRvzLF3ntGgKFugEzQlSh3C7EDSS6QZYGiYa6Z018yQg8+21PMJQiMeWaQ9l0 +vi6cif2ASs1UOjmK/FD55LYrd0RH2OoFsYklngyUZ2mGFZ8Cd+zPCMC44LHhNfXS +eMUFo7ScQqHYjIA3v1wlhfY88yvFPIZ7R9wAEBWmg6G2FUvZE0cRZwJVO2X3UZE1 +KUyQm2GflIscxqEKang1X3vb5tM13icoFny1U9li8Y05HA7IA9VcGK0iqZYTNW4o +z7/jipca+PTmeaX11py5fHsf1S6sU1xS7qJbpJRll/yuo82G3Tjr4cCoVauZvE68 +TI9J6QKCAQAkYqGIvmYO2tPPn6CjpnliAuY0Imo624JUUY3zOBrNkHI9ijVZzklb +IG/zCUjlen6R1xdpvEc96FuWh5D+qiyai/Ny2AFua1L/XSAIFTnvDcG0dnzTd61j +LyhycGr5baFv257uJ2ZC6iDugj1V9y1AK7zUkue95+pFaNprhGRL5Uj3zo/xD5F6 +C4u15VYTSZzzVRqqio0ho+JvwAAm9SD4W3niM9E/8q2eSAFTGHirWKJgsigBvnlW +YzfM8gHs2RT5XAWQdhCla2idt1z43LzPUJqBQHcpm/vnIj6rGZr5fHrpWXAhsOtH +dc4PX9YauiEeYqWAfaoy1y+q6+j6z0vxAoIBAA5KfaRS2+4THfqRHBsUkSNlQx60 +Ns2gQ2tjLtTCu7o45AjMUca5pkbnKU6+aLYMgYYqPw0pl8EdFJEwNiv3f0L0KWCG +aV5YasjIqRvN1wG5PzGeraGBE4W2qAP0xKKivep1oXOThgwyhlFdKic9/v9Ww9yz +3kmmRoNEooG52Vj6JN8pRPp+Z0pg/DP0fo408wt3webv4fhwNV4bd0DmdAmIOYhd +uGLpi1dvcNAwNd0V+0BAD1Ne3LsWGz5sa6i6vwsbXhqkPbaJg0q2VNpq2r0CM7tf +avK7GVXsdwl/PghGQ3ct3KYqnT8kYQJwbswWfkJfaVFo5AqOhLEsNzAvpK0= +-----END RSA PRIVATE KEY----- diff --git a/public1024.pem b/public1024.pem new file mode 100644 index 00000000..3e385418 --- /dev/null +++ b/public1024.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDc84CClRm5VqlzhvG7Izfff/VB +hycuhxYEubSQp6NXT8virjlpe/g0dW94d1UENsEZYiLxepVwkHKNjxlbKMiLTjk+ +hs+S2g6U7bIbFLClZAdD9Bf+wuKD5YUUX7cRLzFEIFCNLn9E7HZ6/8iRkMtMmZf6 +Dx0LG/WmZ3WN1CnqwwIDAQAB +-----END PUBLIC KEY----- diff --git a/public2048.pem b/public2048.pem new file mode 100644 index 00000000..892ef942 --- /dev/null +++ b/public2048.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuhsl0nR17nqITIkMYqzr +hbT5me5+Q+G387spzR8KWbVujoRFQa6DNzZS8HPNmBW8HjVZJ29uBAAtpWpoERdw +vT31Kag+q9eSpX1d02uTd43cESNTb1bYCYd3LHdPZ9GSH3gIfD/fk2P8zJytzED7 +cT1gDUqzCcxi3cvH6/yHb6hmYmNsQo1MLTn3kxyzU32J3nhNEZTlHq1XE9ydqLVq +DCMRPOUuOsXcIb9ASZh3ExSPU2cx+8WMAWWoCw/kO7EJ4Vinl8NDbsRoyXjweekY +Z0bH9YrN4y05KLMhsQhoxBdmKdcJErPwYad17vtmhTpdwEpCriL/iZDLUDOz1G2n +gwIDAQAB +-----END PUBLIC KEY----- diff --git a/public4096.pem b/public4096.pem new file mode 100644 index 00000000..5da13134 --- /dev/null +++ b/public4096.pem @@ -0,0 +1,14 @@ +-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAqkEuMgW6/rprdpHgAdr4 +IoGNGSfTQpCvojGHxrt3JTj+Mir4lZJw17cvMVfbnpWkCZ0H2jcxLR9t/blCSsND +bxe99Xbib6q6SfIkAtL3BvUU0TTzD5ZjZR+Ko3kA+wq6yHPa8KW49659IXJzEW9C +i2SJmj6lqrTw0F0ZXDso5ILU1UjrYDJdT23hyygh4SI8geDtfmv88iP+V2emhIFg +q8BKvExcxlBjX4kzUU/qDddo0OUrA/wpSSPPAkXYKZ4P/0SZqbkAHNfQFzMcGbf9 +sGAszXI1QjwomV95Ro0IJ8HCDG2hBvdR4AqAXI3o+DumF7kHT2pHUUP8yrodVEwW +cmCjO9bPh+bm3sketDIgjs6cwukohoZFr7aYp9o9l0B6lbQXdg8isut2ebXYru+R +z5KJ2Ca5fOk4PHHMbZ4murBUsQq+XT8q8Sldxl7IqPcSEKzLaW+JNMyZ4nX/njAR +A9cTJDTWp38EqxUdzo1ZifVMZBeI+xIyqOuyfp/zUpaoeGbCuGQHJ8A7WHagQkh9 +x1gjOtlIdFuX2scSHrhbbsccmIPJJ18MquM8SnauF2cW9oLo/8n0dMmaXJFqb5sK +gjitPcy+usA6e8ZquwWuDheSuEo5p/S7ti02hGhWTzCWvq/nnZnEvUp642LKcIYI +8oJxXAFEGPm3HaucWGPNuqECAwEAAQ== +-----END PUBLIC KEY----- diff --git a/script/main.lua b/script/main.lua index 11da6b6f..df91a32d 100644 --- a/script/main.lua +++ b/script/main.lua @@ -1,77 +1,106 @@ local httpd = require "httpd" -local http = require "httpd.http" local httpc = require "httpc" - - +local DB = require "DB" + +--[[ +请按照以下步奏初始化后台: + 1. 创建一个数据库(名字任意); + 2. 请手动打开lualib/db/database.sql文件, 复制里面的SQL语句在GUI工具中执行一次; + 3. 执行完成之后, 将您填写的数据库替换database字段, 并且charset需要设置一致. +]] + +local db = DB:new({ + host = 'localhost', + port = 3306, + username = 'root', + password = '123456789', + charset = 'utf8mb4', + database = 'cfadmin', + max = 100, +}) + +db:connect() + +-- 导入httpd对象 local app = httpd:new("App") +-- httpd启用Cookie扩展 +app:enable_cookie() +-- httpd设置Cookie加密的密匙 +app:cookie_secure("https://github.com/CandyMi/core_framework") +-- app:cookie_secure("candymi") +app:ws('/ws', require "ws") + +app:api('/api', function (content) + local code, response = httpc.get("https://www.baifubao.com/callback?cmd=1059&callback=phone&phone=13000000000") + return code == 200 and response or "httc请求失败" +end) --- 每次http请求在处理函数之前将调用此方法, 第三方中间件可以在此针对回调函数设计. --- 需要注意的是: 如果路由不存在, 则不会经过此回调! --- 以下为使用示例: --- 如果未使用before回调进行中间件设计或进行header验证, 请不要注册before回调 --- 只要注册了before回调, 即使不做任何操作(返回非法值(nil))也会返回http 401 code. -app:before(function (content) - if true then - return http.ok() - end - -- if true then - -- return http.redirect('https://github.com/CandyMi/core_framework') - -- end - -- if true then - -- return http.throw(431, '

      This is 413 Error, too long request header

      ') - -- end +app:use('/view', function (content) + return "

      cfadmin v0.3

      " end) --- 单个连接最大保持时间 -app:timeout(5) --- 最大URI长度 -app:max_path_size(1024) +-- 导入cf内置的admin库 +local cfadmin = require "admin" --- 最大Header长度 -app:max_header_size(65535) +-- 注册后台页面路由 +cfadmin.init_page(app, db) --- 最大Body长度 -app:max_body_size(1024 * 1024) --- 可自定义Server Name -app:server_name('Candy Server/1.0') +-- 这个函数仅在第一次初始化数据的时候适用 +-- 初始化完成之后, 请不要再运行. +cfadmin.init_db() --- 注册接口 -app:api("/api", require "api") +-- 这里设置首页的显示的页面 +-- cfadmin.init_home(location or domain + path) +-- cfadmin.init_home('https://www.baidu.com') -app:api("/app", function (opt) - local code, resp = httpc.get('http://t.weather.sojson.com/api/weather/city/101030100') - if code ~= 200 then - print(code, resp) - return '{"code":500,"message":"请求失败."}' - end - return resp +local view = require "admin.view" +-- 参数: +-- 1. ctx是一个http req 对象, 目前内置包括: get_method, get_args, get_path, get_raw_path, get_headers, get_cookie +-- 2. db初始化后的db对象, 方便用户直接使用. + +view.use('/admin/test1', function (ctx, db) + return "hello world" end) --- 注册普通路由(html/text) -app:use("/view", function (opt) - return "

      This is text/html content-type

      Server: cf/0.1" +view.api('/api/admin/test2', function (ctx, db) + return '{"code":0,"msg":"hello world"}' end) --- 批量路由注册 -app:group(app.API, '/admin', require "admin") +-- 这里是设置语言的地方 +-- 语言表在admin/locales内, 可参照key -> value进行填写. +-- 传入一个数组表: 索引1是key, 索引2为显示内容. + +-- cfadmin.add_locale_item('ZH-CN', { +-- {'login.form.title', '这是登录页Title'}, +-- {'dashboard.header.logo', '仪表盘 Logo'} +-- }) + +-- cfadmin.add_locale_item('EN-US', { +-- {'login.form.title', 'This is Login Page Title'}, +-- {'dashboard.header.logo', 'dashboard Logo'} +-- }) --- 批量路由注册 -app:group(app.USE, '/login', require "admin") +-- 开启页面缓存能显著提升页面渲染性能. 生产环境下建议开启. +-- 也因为cf缓存模板页面内容, 所以开发模式下不建议开启. +-- cfadmin.cached() --- 注册websocket路由 -app:ws("/ws", require "ws") +-- 这个方法可以用来设置静态文件域名与前缀. +-- 如果静态文件在其它域名或者无法访问, 可以使用这个参数修改.(域名后必须加上'/') +-- cfadmin.static('/') --- 注册静态文件目录 -app:static('static', 10) +-- 设置cfadmin的区域语言, 默认为: ZH-CN +-- cfadmin.set_locale('EN-US') --- 需要记录日志, 并且指定日志存放路径 --- app:log("./http.log") +-- 设置客户端静态文件ttl值内无需再次请求, 减少服务端消耗 +-- app:static('static', 30) +app:static('static') --- http监听端口 -app:listen("0.0.0.0", 8080) +-- httpd监听端口 +app:listen("0.0.0.0", 8080, 128) +-- app:listenx("cfadmin.sock", 128) -- 运行 -app:run() \ No newline at end of file +app:run() diff --git a/script/test_Cache.lua b/script/test_Cache.lua index 1832c693..1056b947 100644 --- a/script/test_Cache.lua +++ b/script/test_Cache.lua @@ -1,130 +1,139 @@ -- 测试redis local Cache = require "Cache" -local Co = require "internal.Co" -local timer = require "internal.Timer" -require "utils" +local cf = require "cf" +local Log = require("logging"):new() + local opt = { host = "localhost", port = 6379, auth = nil, - db = nil, + db = 1, max = 1, } -Co.spwan(function ( ... ) - local ok, err = Cache.init(opt) +cf.fork(function ( ... ) + local Cache = Cache:new(opt) + local ok, err = Cache:connect() if not ok then return print(err) end -- 测试 GET/SET/DEL 命令示例 local ok, ret = Cache:set("test", 1) - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) local ok, ret = Cache:get("test") - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) local ok, ret = Cache:del("test") - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) -- 测试哈希表命令示例 local ok, ret = Cache:hmset("website", "google", "www.google.com", "baidu", "www.baidu.com") - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) local ok, ret = Cache:hlen("website") - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) local ok, ret = Cache:hkeys("website") - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) local ok, ret = Cache:hgetall("website") - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) local ok, ret = Cache:hmget("website", "google", "baidu") - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) local ok, ret = Cache:hdel("website", "google", "baidu") - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) -- 测试列表命令示例 local ok, ret = Cache:lpush("language", "lua", "python", "C", "C++") - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) local ok, ret = Cache:rpush("language", "golang", "java", "ruby", "javascript") - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) local ok, ret = Cache:lrange("language", 0, -1) - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) local ok, ret = Cache:ltrim("language" , -1, 0) - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) local ok, ret = Cache:llen("language") - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) -- 测试有序集合命令示例 local ok, ret = Cache:smembers("bbs") - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) local ok, ret = Cache:sadd("bbs", "discuz.cn", "group.google.com", "oschina.net", "csdn.net") - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) local ok, ret = Cache:sismember("bbs", "oschina.net") - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) local ok, ret = Cache:srem("bbs", "discuz.cn", "group.google.com", "oschina.net", "csdn.net") - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) local ok, ret = Cache:sismember("bbs", "oschina.net") - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) local ok, ret = Cache:sadd("book1", "宝宝的C++", "宝宝的HTML", "宝宝的CSS", "C程序设计") local ok, ret = Cache:sadd("book2", "宝宝的C++", "宝宝的HTML", "宝宝的CSS", "C++从入门到放弃") local ok, ret = Cache:sadd("book3", "宝宝的C++", "宝宝的HTML", "宝宝的CSS", "MySQL从入门到删库跑路") local ok, ret = Cache:sdiff("book1", "book2", "book3") - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) local ok, ret = Cache:del("book1", "book2", "book3") - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) -- 测试有序集合命令示例 local ok, ret = Cache:zadd("scores", 10, "admin", 20, "Candy", 30, "QQ", 40, "Guest") - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) local ok, ret = Cache:zrange("scores", 0, -1) -- local ok, ret = Cache:zrange("scores", 0, -1, "WITHSCORES") - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) local ok, ret = Cache:zcount("scores", 10, 100) - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) local ok, ret = Cache:zscore("scores", "QQ") - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) local ok, ret = Cache:zrank("scores", "Candy") - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) local ok, ret = Cache:zrem("scores", "admin", "Candy", "QQ", "Guest") - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) local ok, ret = Cache:del("scores") - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) -- 脚本支持 local ok, ret = Cache:script_load("return 10086") - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) - local sha = ret + local sha = ret local ok, ret = Cache:script_exists(sha) - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) local ok, ret = Cache:evalsha(sha, 0) - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) local ok, ret = Cache:script_flush() - print(ok); var_dump(ret) + Log:DEBUG(ok, ret) -- 其它一些特殊方法支持 -- type, move, rename, keys, randomkey等等 - + Log:DEBUG(Cache:count()) + + -- 管道命令支持 + local ok, ret = Cache:pipeline { + {"HMSET", "USER_INFO", "name", "Candy", "email", '869646063@qq.com', 'phone', '13000000000'}, + {"HGET", "USER_INFO", "email"}, + {"HGET", "USER_INFO", "phone"}, + } + Log:DEBUG(ok, ret) end) diff --git a/script/test_DB.lua b/script/test_DB.lua index 475d6f44..49444728 100644 --- a/script/test_DB.lua +++ b/script/test_DB.lua @@ -1,139 +1,66 @@ +local LOG = require "logging" +local cf = require "cf" local DB = require "DB" -local co = require "internal.Co" -require "utils" -local ok, err = DB.init({ - host = "localhost", - port = 3306, - database = "test", - user = "root", - password = "123456789" - }) +local db = DB:new { + host = 'localhost', + port = 3306, + database = 'mysql', + username = 'root', + password = '123456789', + max = 1, +} + +local ok = db:connect() if not ok then - return print("连接mysql 失败: "..err) + return LOG:DEBUG("连接mysql失败") end +LOG:DEBUG("连接成功") --[[ - 复制下面语句到任意管理工具即可导入测试表进行测试 + /* 复制下面语句到任意管理工具即可导入测试表进行测试 */ SET NAMES utf8; SET FOREIGN_KEY_CHECKS = 0; + CREATE DATABASE IF NOT EXISTS `test` DEFAULT CHARSET utf8 COLLATE utf8_general_ci; - -- ---------------------------- - -- Table structure for `user` - -- ---------------------------- - DROP TABLE IF EXISTS `user`; - CREATE TABLE `user` ( + CREATE TABLE IF NOT EXISTS `test`.`user` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, - `name` varchar(255) NOT NULL, - `user` varchar(255) NOT NULL, - `passwd` varchar(255) NOT NULL, + `name` varchar(255) CHARACTER SET utf8mb4 NOT NULL, + `user` varchar(255) CHARACTER SET utf8mb4 NOT NULL, + `passwd` varchar(255) CHARACTER SET utf8mb4 NOT NULL, PRIMARY KEY (`id`) - ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4; + ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; SET FOREIGN_KEY_CHECKS = 1; - --]] --- 插入语句示例 -co.spwan(function ( ... ) - local ret, err = DB.insert("user") - :fields({"name", "user", "passwd"}) - :values({ - {"candy", "root", "123456789"}, - {"水果糖", "admin", "123456789"}, - }) - :execute() - +-- 测试普通数据库语句 +cf.fork(function ( ... ) + local ret, err = db:query("show variables like 'wait_timeout'") if not ret then - return print(err) + return LOG:DEBUG(err) end - - var_dump(ret) + LOG:DEBUG(ret) end) --- 查询语句示例 -co.spwan(function ( ... ) - local ret, err = DB.select({"id", "name", "user", "passwd"}) - :from({"user"}) - :where({ - {"id", "!=", "0"}, - "AND", - {"id", ">=", "1"}, - "OR", - {"user", "!=", "admin"}, - "AND", - {"user", "=", "admin"}, - "OR", - {"user", "IS", "NOT", "NULL"}, - "AND", - {"user", "IS", "NULL"}, - "AND", - {"user", "IN", {1, 2, 3, 4, 5}}, - "AND", - {"user", "NOT", "IN", {1, 2, 3, 4, 5}}, - "AND", - {"user", "BETWEEN", {1, 100}}, - "AND", - {"user", "NOT", "BETWEEN", {1, 100}}, - }) - :groupby('id') -- groupby({"name", "user"}) - :orderby("id") -- orderby({"name", "user"}) - :asc() -- or desc() - :limit(1) -- limit("1") limit(1, 100) - :execute() -- 所有语句最后必须指定这个方法才会真正执行 - - if not ret then - return print(err) - end - - var_dump(ret) -end) - --- 更新语句示例 -co.spwan(function ( ... ) - local ret, err = DB.update("user") - :set({ - {"name", "=", "管理员"}, - {"user", "=", "Administrator"}, - {"passwd", "=", "Administrator"}, - }) - :where({ - {"id", "<=", 1}, - }) - :limit(1) - :execute() - - if not ret then - return print(err) - end - - var_dump(ret) +-- 测试预编译语句 +cf.fork(function ( ... ) + local ret, err = db:execute([[SELECT version() AS version]]) + if not ret then + return LOG:DEBUG(err) + end + LOG:DEBUG(ret) end) --- 删除语句示例 -co.spwan(function ( ... ) - local ret, err = DB.delete("user") - :where({ - {"id", ">", 1}, - }) - :orderby("id") - :limit(1) - :execute() - - if not ret then - return print(err) - end +-- 测试开启事务 +cf.fork(function () + local status, err = db:transaction(function (session) + LOG:WARN(session:query("show databases")) + LOG:WARN(session:execute("show tables")) - var_dump(ret) + -- return session:rollback() + return session:commit() + end) + LOG:DEBUG(status, err) end) - - -co.spwan(function ( ... ) - local ret, err = DB.query("show variables like 'wait_timeout'") - if not ret then - return print(err) - end - - var_dump(ret) -end) \ No newline at end of file diff --git a/script/test_MQ.lua b/script/test_MQ.lua index d25bbfc9..060cb29b 100644 --- a/script/test_MQ.lua +++ b/script/test_MQ.lua @@ -1,45 +1,34 @@ --- 基于redis的订阅与发布消息队列(同步处理) -local Timer = require "internal.Timer" -local MQ = require "MQ" +-- local MQ = require "MQ.stomp" +local MQ = require "MQ.redis" +-- local MQ = require "MQ.mqtt" + +local cf = require "cf" require "utils" -local rds = MQ:new { +local mq = MQ:new { host = 'localhost', + -- port = 61613, + -- port = 1883, port = 6379, - type = 'redis' + -- vhost = '/exchange', + -- auth = "admin", + -- username = "guest", + -- password = "guest", } -print(rds:on('/test/*', function (msg) +mq:on('/test', function (msg) + print("收到来自/test的消息.") var_dump(msg) -end)) - -Timer.at(1, function ( ... ) - rds:emit("/test/admin", '{"code":100}') end) -Timer.sleep(10) -print("停止消息队列监听与投递.") -rds:close() - --- 基于mqtt的订阅与发布消息队列(同步处理) -local Timer = require "internal.Timer" -local MQ = require "MQ" -require "utils" - -local mqtt = MQ:new { - host = 'localhost', - port = 1883, - type = 'mqtt' -} - -Timer.at(0.1, function ( ... ) - mqtt:emit("/test/admin", '{"code":100}') +mq:on('/admin', function (msg) + print("收到来自/admin的消息.") + var_dump(msg) end) -print(mqtt:on('/test/*', function (msg) - var_dump(msg) -end)) +cf.at(0.1, function (args) + print(mq:emit('/test', '{"code":'..math.random(1, 100)..',"from":"/test"}')) + print(mq:emit('/admin', '{"code":'..math.random(1, 100)..',"from":"/admin"}')) +end) -Timer.sleep(10) -print("停止消息队列监听与投递.") -mqtt:close() +mq:start() diff --git a/script/test_SSDB.lua b/script/test_SSDB.lua new file mode 100644 index 00000000..c8f26b73 --- /dev/null +++ b/script/test_SSDB.lua @@ -0,0 +1,153 @@ +-- 测试redis +local Cache = require "Cache.ssdb" +local cf = require "cf" +local Log = require("logging"):new() + +local opt = { + host = "127.0.0.1", + -- port = 6379, + port = 8888, + -- auth = "rTVB9Fm2l2rctOJlIzVxIN0BreQQoiET", -- 如果需要验证 + -- db = 1, -- SSDB不支持多DB + max = 1, +} + +cf.fork(function ( ... ) + local Cache = Cache:new(opt) + local ok, err = Cache:connect() + if not ok then + return print(err) + end + -- 测试 GET/SET/DEL 命令示例 + local ok, ret = Cache:set("test", 1) + Log:DEBUG(ok, ret) + + local ok, ret = Cache:get("test") + Log:DEBUG(ok, ret) + + local ok, ret = Cache:del("test") + Log:DEBUG(ok, ret) + + -- 测试哈希表命令示例 + local ok, ret = Cache:hmset("website", "google", "www.google.com", "baidu", "www.baidu.com") + Log:DEBUG(ok, ret) + + local ok, ret = Cache:hlen("website") + Log:DEBUG(ok, ret) + + local ok, ret = Cache:hkeys("website") + Log:DEBUG(ok, ret) + + local ok, ret = Cache:hgetall("website") + Log:DEBUG(ok, ret) + + local ok, ret = Cache:hmget("website", "google", "baidu") + Log:DEBUG(ok, ret) + + local ok, ret = Cache:hdel("website", "google", "baidu") + Log:DEBUG(ok, ret) + + -- 测试列表命令示例 + local ok, ret = Cache:lpush("language", "lua", "python", "C", "C++") + Log:DEBUG(ok, ret) + + local ok, ret = Cache:rpush("language", "golang", "java", "ruby", "javascript") + Log:DEBUG(ok, ret) + + local ok, ret = Cache:lrange("language", 0, -1) + Log:DEBUG(ok, ret) + + -- SSDB不支持Ltrim + -- local ok, ret = Cache:ltrim("language" , -1, 0) + -- Log:DEBUG(ok, ret) + + local ok, ret = Cache:llen("language") + Log:DEBUG(ok, ret) + + -- 测试集合命令示例(SSDB 不支持集合) + -- local ok, ret = Cache:smembers("bbs") + -- Log:DEBUG(ok, ret) + + -- local ok, ret = Cache:sadd("bbs", "discuz.cn", "group.google.com", "oschina.net", "csdn.net") + -- Log:DEBUG(ok, ret) + + -- local ok, ret = Cache:sismember("bbs", "oschina.net") + -- Log:DEBUG(ok, ret) + + -- local ok, ret = Cache:srem("bbs", "discuz.cn", "group.google.com", "oschina.net", "csdn.net") + -- Log:DEBUG(ok, ret) + + -- local ok, ret = Cache:sismember("bbs", "oschina.net") + -- Log:DEBUG(ok, ret) + + -- local ok, ret = Cache:sadd("book1", "宝宝的C++", "宝宝的HTML", "宝宝的CSS", "C程序设计") + -- local ok, ret = Cache:sadd("book2", "宝宝的C++", "宝宝的HTML", "宝宝的CSS", "C++从入门到放弃") + -- local ok, ret = Cache:sadd("book3", "宝宝的C++", "宝宝的HTML", "宝宝的CSS", "MySQL从入门到删库跑路") + + -- local ok, ret = Cache:sdiff("book1", "book2", "book3") + -- Log:DEBUG(ok, ret) + + local ok, ret = Cache:del("book1", "book2", "book3") + Log:DEBUG(ok, ret) + + -- 测试有序集合命令示例 + local ok, ret = Cache:zadd("scores", 10, "admin", 20, "Candy", 30, "QQ", 40, "Guest") + Log:DEBUG(ok, ret) + + local ok, ret = Cache:zrange("scores", 0, -1) + -- local ok, ret = Cache:zrange("scores", 0, -1, "WITHSCORES") + Log:DEBUG(ok, ret) + + local ok, ret = Cache:zcount("scores", 10, 100) + Log:DEBUG(ok, ret) + + local ok, ret = Cache:zscore("scores", "QQ") + Log:DEBUG(ok, ret) + + local ok, ret = Cache:zrank("scores", "Candy") + Log:DEBUG(ok, ret) + + local ok, ret = Cache:zrem("scores", "admin", "Candy", "QQ", "Guest") + Log:DEBUG(ok, ret) + + local ok, ret = Cache:del("scores") + Log:DEBUG(ok, ret) + + -- 测试脚本支持 (SSDB 不支持脚本操作) + -- local ok, ret = Cache:script_load("return 10086") + -- Log:DEBUG(ok, ret) + + -- local sha = ret + -- local ok, ret = Cache:script_exists(sha) + -- Log:DEBUG(ok, ret) + + -- local ok, ret = Cache:evalsha(sha, 0) + -- Log:DEBUG(ok, ret) + + -- local ok, ret = Cache:script_flush() + -- Log:DEBUG(ok, ret) + + -- 其它一些特殊方法支持 + -- type, move, rename, keys, randomkey等等 + Log:DEBUG(Cache:count()) + + Log:DEBUG(Cache:multi_set("a1", "1001", "a2", "1002")) + + Log:DEBUG(Cache:multi_get("a1", "a2")) + + Log:DEBUG(Cache:multi_del("a1", "a2")) + + Log:DEBUG(Cache:multi_hset("jmap", "a1", "1001", "a2", "1002")) + + Log:DEBUG(Cache:multi_hget("jmap", "a1", "a2")) + + Log:DEBUG(Cache:multi_hdel("jmap", "a1", "a2")) + + -- 管道命令支持 + local ok, ret = Cache:pipeline { + {"HMSET", "USER_INFO", "name", "Candy", "email", '869646063@qq.com', 'phone', '13000000000'}, + {"HGET", "USER_INFO", "email"}, + {"HGET", "USER_INFO", "phone"}, + } + Log:DEBUG(ok, ret) +end) diff --git a/script/test_aio.lua b/script/test_aio.lua new file mode 100644 index 00000000..3fd554fa --- /dev/null +++ b/script/test_aio.lua @@ -0,0 +1,218 @@ +local LOG = require "logging" +local aio = require "aio" +local cf = require "cf" + +require "utils" + +local function run(func_name, ...) + local ret, err = aio[func_name](...) + return ret, err +end + +--[[ + + 众所周知! 线程之间是"并行执行"的. 当mkdir与rmdir运行在不同的协程后, 即使是最简单工作, 我们也不保证哪个协程先运行, 哪个任务先结束. + + 如果你需要aio库进行串行化的工作时, 请至少将aio的方法按顺序写在同一个协程当中. 这样至少能将其运行在"逻辑顺序性"的情况下. + + example_1展示就了一个错误使用示例! 它将不同的任务放在不同的协程中并让其乱序执行. 这样将打乱你原本的计划. 因为它可能正确(也可能不正确). + + 软件设计其中一项重点就是对软件设计质量的把控! 当程序运行在没有人知道会发生什么的情况下(包括作者本人), 那么这就是错误的问题思考方式. + +]] + + + +-- -- example_1 +-- for i = 1, 100 do + +-- cf.fork(function ( ... ) +-- LOG:DEBUG("开始删除: logs/" .. i) +-- print(run("rmdir", "logs/" .. i)) +-- LOG:DEBUG("结束删除: logs/" .. i) +-- end) + +-- cf.fork(function ( ... ) +-- LOG:DEBUG("开始创建: logs/" .. i) +-- print(run("mkdir", "logs/" .. i)) +-- LOG:DEBUG("结束创建") +-- end) + +-- end + + +--[[ + + 如果您已经看到这里! 说明已经接受上述建议开始尝试编写"正确"的代码! 那么如何编写逻辑正确的异步IO呢? 下面有些建议供大家参考. + + example_2示例描述了正确的使用方法, 同时也是最可能大家使用到的代码. 它绝大部分场景应该都能工作的很好, 并且不会因为文件IO导致阻塞. + + 我们可以看到, 当example_2运行在相同的协程的时候. 他们可以工作的非常好. 因为aio底层已经将异步回调代码修改为同步非阻塞(至少看起来是这样). + + 这样的编码可以让底层处于串行化工作领域范围内:"即创建完毕之后才会执行删除操作", 既保留了异步IO的能力, 也保证了逻辑正确性. + +]] + + +-- -- example_2 +-- for i = 1, 100 do +-- LOG:DEBUG("开始创建: logs/" .. i) +-- print(run("mkdir", "logs/" .. i)) +-- LOG:DEBUG("结束创建") +-- LOG:DEBUG("开始删除: logs/" .. i) +-- print(run("rmdir", "logs/" .. i)) +-- LOG:DEBUG("结束删除: logs/" .. i) +-- end + + +--[[ + + example_3在批量任务中派上了用场了! 协程与异步IO的并发使用场景就是:"让多个无相关性的任务提交, 然后统一等待任务完成的那个时刻到来." + + 这在执行批量任务的时候优势极其明显. 即使其实际运行结果中并不是按照1->2->3 ... ->100的顺序执行, 但是我们的目的是一致的. + + 因为当过程不重要的时候, 只需要保证结果的正确性即可. 就类似编译器指令重排, 虽然顺序不一致但是结果是一致的. + + 注意: example_3实现的代码一样与其他两种的场景意义不一样. + +]] + + +-- local index = 1 +-- for i = 1, 100 do +-- cf.fork(function ( ... ) +-- run("mkdir", "logs/" .. i) +-- index = index + 1 +-- if index == 100 then +-- print("所有文件夹创建完成.") +-- end +-- end) +-- end + +-- cf.sleep(3) + +-- local index = 1 +-- for i = 1, 100 do +-- cf.fork(function ( ... ) +-- run("rmdir", "logs/" .. i) +-- index = index + 1 +-- if index == 100 then +-- print("所有文件夹删除完成.") +-- end +-- end) +-- end + +--[[ + + 所以如果您需要外部使用, 请知悉这些内容. 这能让您明白是否真正的需要用到它. 由aio.open创建(一般情况下不可能失败)的对象并不是io库的file对象(元表是__AIO__). + + __AIO__相关方法的行为根据pread/pwrite行为而定, 且read_seek方法并不是真正调用的底层函数. 根据不同平台实现可能也并不是线程安全的, 而且__AIO__会在必要的时候做重入检查(抛出异常). + + 同时需要知道__AIO__的读取与写入性能并不会比原生io库高. 所以性能并不是__AIO__对象的优势, 它们的优势在于不会因为同步文件I/O操作长时间阻塞内置事件循环. + + 最后, 它与io库的file对象一样需要显示关闭文件描述符(fd). 虽然__AIO__对象在触发gc的时候可以检查并且关闭可能造成的泄露, 但是请不要过于依赖它(因为open files too many错误可能会比gc先到来). + +]] +local function test_aio_file_operations() + + local f = assert(aio.open("message.txt")) + + print("写入字节: [hello world!], 长度为: " .. f:write("hello world!")) + + print("读取1个字节为 : " .. f:read(1)) + + print("读取2个字节为 : " .. f:read(2)) + + print("读取剩余内容为 : " .. f:readall()) + + print("重置读取起始位置(offset)", f:read_lseek(0)) + + print("读取剩余内容为 : " .. f:readall()) + + print("清空文件: ", f:clean() and "成功") + + print("关闭文件: ", f:close() and "成功") + + print("删除message.txt文件: " , assert(aio.remove("message.txt")) and "成功") + +end + +-- aio.create方法在文件存在的时候会因为创建文件失败而返回nil与错误信息 +-- aio.remove方法在文件不存在的时候会因为删除失败而返回nil与错误信息 +local function test_aio_create_file_and_delete() + + print("创建message文件: ", assert(aio.create("message")) and "成功") + + print("删除message文件或文件夹: " , assert(aio.remove("message")) and "成功") + +end + +-- aio.mkdir与aio.rmdir方法用于创建于删除文件夹(aio.remove也可完成删除文件夹) +local function test_aio_create_dir_and_delete() + print("创建message文件夹: ", assert(aio.mkdir("message")) and "成功") + + print("删除message文件或文件夹: " , assert(aio.rmdir("message")) and "成功") +end + +-- aio.truncate方法会截断文件内容, 当它的第二个参数为0/nil/不传递第二个参数的时候您就如同做了清空文件的操作(效果等同于调用f:clean()) +-- 这在某些情况下是非常危险的操作, 因为这会立即影响到文件内容. 所以除非您知道自己在做什么, 否则请谨慎使用此方法. 一般情况下不会用到它. +local function test_aio_truncate_file() + -- aio.truncate("filename", "filesize") +end + +-- aio.currentdir方法会返回表示当前路径的字符串 +local function test_aio_display_current_dir() + print("当前目录为: " .. (aio.currentdir() or "")) +end + +-- aio.dir方法返回指定目录下的所有文件/文件夹名称(数组) +local function test_aio_display_dir() + var_dump(aio.dir(".")) +end + +-- aio.stat / aio.attributes 方法返回指定名称的文件/文件夹的属性 +local function test_aio_stat() + var_dump(aio.stat(".")) + -- var_dump(aio.attributes(".")) +end + +-- aio.rename 方法将会将会对指定的文件/文件夹重命名, 例如将3rd命名为4rd后, 最后再将其改回3rd +local function test_aio_rename() + print("将3rd文件夹改为4rd: ", assert(aio.rename("3rd", "4rd")) and "成功") + print("将4rd文件夹改为3rd: ", assert(aio.rename("4rd", "3rd")) and "成功") +end + +-- aio.fflush 方法将会刷新io.file对象的缓冲区(前提是您设置了), 这是一个同步非阻塞的操作. +-- 真正的刷新操作将会在其它系统线程内执行, 所以您并不用担心主线程会被影响到. 但一般情况下您并不会用用到它 +local function test_aio_fflush( ... ) + local f = assert(io.open("test.txt", "a")) + -- 设置完全缓冲区, 这在缓冲区未被写满或者f:close之前是不会刷写到磁盘上的. + f:setvbuf("full", "1024") + f:write("hello world!") + -- 如果您将以此之后的代码删除或者注释, 您会发现test.txt内并无任何内容. + -- 但如果您未注释下面的代码, test.txt会正常写入到磁盘上. + aio.fflush(f) + f:close() + -- 如果您不注释下面的这段代码! 文件操作完毕将会被删除, 您将不会知晓任何操作细节. + aio.remove("test.txt") +end + +-- test_aio_create_file_and_delete() + +-- test_aio_create_dir_and_delete() + +-- test_aio_file_operations() + +-- test_aio_display_current_dir() + +-- test_aio_display_dir() + +-- test_aio_stat() + +-- test_aio_rename() + +-- test_aio_fflush() + + + +cf.wait() \ No newline at end of file diff --git a/script/test_cf.lua b/script/test_cf.lua index 09ac0403..feecd9a3 100644 --- a/script/test_cf.lua +++ b/script/test_cf.lua @@ -10,7 +10,7 @@ cf.fork(function() print("fork", "我现在被唤醒了") end) -local times, timer = 0 +local times, timer = 0, nil timer = cf.at(1, function() if times >= 3 then print("循环定时器运行次数到了.") @@ -20,7 +20,7 @@ timer = cf.at(1, function() print("定时器运行次数:", times) end) -local timer = cf.timeout(1, function() +cf.timeout(1, function() print("一次性定时器运行.") end) diff --git a/script/test_cfadmin.lua b/script/test_cfadmin.lua new file mode 100644 index 00000000..95205453 --- /dev/null +++ b/script/test_cfadmin.lua @@ -0,0 +1,105 @@ +local httpd = require "httpd" +local DB = require "DB" + +--[[ +请按照以下步奏初始化后台: + 1. 创建一个数据库(名字任意); + 2. 请手动打开lualib/db/database.sql文件, 复制里面的SQL语句在GUI工具中执行一次; + 3. 执行完成之后, 将您填写的数据库替换database字段, 并且charset需要设置一致. +]] + +local db = DB:new({ + host = '10.0.0.16', + port = 3306, + username = 'root', + password = '123456789', + charset = 'utf8mb4', + database = 'cfadmin', + max = 100, +}) + +db:connect() + +-- 导入httpd对象 +local app = httpd:new("App") +-- httpd启用Cookie扩展 +app:enable_cookie() +-- httpd设置Cookie加密的密匙 +app:cookie_secure("https://github.com/CandyMi/core_framework") +-- app:cookie_secure("candymi") + +-- 导入cf内置的admin库 +local cfadmin = require "admin" + +-- 注册后台页面路由 +cfadmin.init_page(app, db) + + +-- 这个函数仅在第一次初始化数据的时候适用 +-- 初始化完成之后, 请不要再运行. +cfadmin.init_db() + +-- 这里设置首页的显示的页面 +-- cfadmin.init_home(location or domain + path) +-- cfadmin.init_home('https://www.baidu.com') + +local view = require "admin.view" +-- 参数: +-- 1. ctx是一个http req 对象, 目前内置包括: get_method, get_args, get_path, get_raw_path, get_headers, get_cookie +-- 2. db初始化后的db对象, 方便用户直接使用. + +view.use('/admin/test1', function (ctx, db) + return "hello world" +end) + +view.api('/api/admin/test2', function (ctx, db) + return '{"code":0,"msg":"hello world"}' +end) + +app:ws('/ws', require "ws") + +app:api('/api', function (content) + require('logging'):new():DEBUG(content.args or content.body) + return '{"code":200}' +end) + +app:use('/view', function (content) + require('logging'):new():DEBUG(content.files) + return '

      cfadmin v0.3

      ' +end) + +-- 这里是设置语言的地方 +-- 语言表在admin/locales内, 可参照key -> value进行填写. +-- 传入一个数组表: 索引1是key, 索引2为显示内容. + +-- cfadmin.add_locale_item('ZH-CN', { +-- {'login.form.title', '这是登录页Title'}, +-- {'dashboard.header.logo', '仪表盘 Logo'} +-- }) + +-- cfadmin.add_locale_item('EN-US', { +-- {'login.form.title', 'This is Login Page Title'}, +-- {'dashboard.header.logo', 'dashboard Logo'} +-- }) + +-- 开启页面缓存能显著提升页面渲染性能. 生产环境下建议开启. +-- 也因为cf缓存模板页面内容, 所以开发模式下不建议开启. +-- cfadmin.cached() + +-- 这个方法可以用来设置静态文件域名与前缀. +-- 如果静态文件在其它域名或者无法访问, 可以使用这个参数修改.(域名后必须加上'/') +-- cfadmin.static('/') + +-- 设置cfadmin的区域语言, 默认为: ZH-CN +-- cfadmin.set_locale('EN-US') + +-- 设置客户端静态文件ttl值内无需再次请求, 减少服务端消耗 +-- app:static('static', 30) +app:static('static') + +-- httpd监听端口 +app:listen("0.0.0.0", 8080, 128) +-- app:listenx("cfadmin.sock", 128) + +-- 运行 +app:run() diff --git a/script/test_crypt.lua b/script/test_crypt.lua new file mode 100644 index 00000000..53727eab --- /dev/null +++ b/script/test_crypt.lua @@ -0,0 +1,512 @@ +local crypt = require "crypt" +local Log = require "logging" + +local function test_hex() + print("----------*** 开始测试 HEXENCODE/HEXDECODE ***----------") + + -- 测试hexencode编码 + Log:DEBUG("测试hexencode", crypt.hexencode("1234567890"), crypt.hexencode("1234567890") == "31323334353637383930") + -- 测试hexdecode编码 + Log:DEBUG("测试hexdecode", crypt.hexdecode("31323334353637383930"), crypt.hexdecode("31323334353637383930") == "1234567890") + + print("----------*** 开始测试 HEXENCODE/HEXDECODE ***----------\n") +end + + +local function test_crc() + print("----------*** 开始测试CRC32/CRC64 ***----------") + + -- 测试crc32编码 + Log:DEBUG("测试crc32",crypt.crc32("admin")) + -- 测试crc64编码 + Log:DEBUG("测试crc64", crypt.crc64("admin")) + + print("----------*** CRC32/CRC64 测试完毕 ***----------\n") +end + + +local function test_url( ... ) + print("----------*** 开始测试 urlencode/urldecode ***----------") + + local url = "https://www.baiud.com/我是谁/api?name=水果糖的小铺子&age=30" + + Log:DEBUG("测试urlencode: " .. crypt.urlencode(url)) + + Log:DEBUG("测试urlencode: " .. crypt.urldecode(crypt.urlencode(url))) + + assert(crypt.urldecode(crypt.urlencode(url)) == url, "测试失败") + + print("----------*** urlencode/urldecode 测试完毕 ***----------\n") +end + +local function test_sha() + + print("----------*** 开始测试MD5/SHA128/SHA256/SHA512 ***----------") + + local text = "123456789admin" + + Log:DEBUG("测试md5 :" .. crypt.md5(text, true)) + assert(crypt.md5(text, true) == "dce70093fd997b5e1a37c86dadaf0a48", "MD5测试失败") + + Log:DEBUG("测试sha128 :" .. crypt.sha1(text, true)) + assert(crypt.sha1(text, true) == "1e2f566cbda0a9c855240bf21b8bae030404cad7", "SHA128测试失败") + + Log:DEBUG("测试sha224 :" .. crypt.sha224(text, true)) + assert(crypt.sha224(text, true) == "47e386607ca91384c4ccca3dfd3da211aaf618b7a043bac2aa138495", "SHA224测试失败") + + Log:DEBUG("测试sha256 :" .. crypt.sha256(text, true)) + assert(crypt.sha256(text, true) == "e39594b63146c3f089bc12e1421cb3fe2fb9e4925908a995989e635d9bd1b096", "SHA256测试失败") + + Log:DEBUG("测试sha384 :" .. crypt.sha384(text, true)) + assert(crypt.sha384(text, true) == "18f26760edbc390cd61834e3100fbf88a14f2fe7b4dfdb11d2d5aea92388274163d5f4ae0cd9662b8a88e148d3b358f4", "SHA384测试失败") + + Log:DEBUG("测试sha512 :" .. crypt.sha512(text, true)) + assert(crypt.sha512(text, true) == "434042f8e8a262ffa53cb2ac1366aa4647c66464e9db8442338f0398cf400d7f966b360f0d12f1670fba01f2a0e900a3295143162ec5a215cf2d6b321294d02e", "SHA512测试失败") + + print("----------*** MD5/SHA128/SHA256/SHA512 测试完毕 ***----------\n") +end + +local function test_hmac() + + print("----------*** 开始测试HMAC(MD5/SHA128/SHA256/SHA512) ***----------") + + local text = "123456789admin" + local key = "admin" + + Log:DEBUG("hmac_md5 :" .. crypt.hmac_md5(key, text, true)) + assert(crypt.hmac_md5(key, text, true) == "fbe0f8e2cfb44139cfdf7162d6b9e709", "HMAC_MD5测试失败") + + Log:DEBUG("hmac_sha128 :" .. crypt.hmac_sha1(key, text, true)) + assert(crypt.hmac_sha1(key, text, true) == "197c3254b4c935717b7d7ca38fbdb642d22a63f9", "HMAC_SHA128测试失败") + + Log:DEBUG("hmac_sha256 :" .. crypt.hmac_sha256(key, text, true)) + assert(crypt.hmac_sha256(key, text, true) == "824902bf7fc037243b6cf0444a4887d21779526b0937b9f76b8ac23dafa0eb45", "HMAC_SHA256测试失败") + + Log:DEBUG("hmac_sha512 :" .. crypt.hmac_sha512(key, text, true)) + assert(crypt.hmac_sha512(key, text, true) == "346b81fb7771816ad1206d996de063859e8225d09a74776ded859d1e3e388b34aae09636c5c60b2ad7d88f5518483cc3d952753573b856aab4b96531a3cb4094", "HMAC_SHA512测试失败") + + -- 测试hmac64编码 + Log:DEBUG("hmac64", crypt.hmac64("12345678", "abcdefgh", true)) + + -- 测试hmac64_md5编码 + Log:DEBUG("hmac64_md5", crypt.hmac64_md5("12345678", "abcdefgh", true)) + + -- 测试hmac_hash编码 + Log:DEBUG("hmac_hash", crypt.hmac_hash("12345678", "abcdefgh", true)) + + print("----------*** HMAC(MD5/SHA128/SHA256/SHA512) 测试完毕 ***----------\n") + +end + +local function test_des( ... ) + + print("----------*** 开始测试 desencode/desdecode ***----------") + + local text = [[{"code":200,"data":[1,2,3,4,5,6,7,8,9,10]}]] + + local key = "12345678" + + -- 测试desencode编码 + Log:DEBUG("测试desencode", crypt.desencode(key, text, true)) + -- 测试desdecode编码 + Log:DEBUG("测试desdecode", crypt.desdecode(key, crypt.desencode(key, text)) == text) + + print("----------*** desencode/desdecode 测试完毕 ***----------\n") + + +end + +local function test_other( ... ) + print("----------*** 开始测试 hashkey/randomkey ***----------") + -- 测试randomkey编码 + Log:DEBUG("测试randomkey", crypt.randomkey(true)) + + -- 测试hashkey编码 + Log:DEBUG("测试hashkey", crypt.hashkey("admin", true)) + + print("----------*** hashkey/randomkey 测试完毕 ***----------\n") +end + + +local function test_xor_str( ... ) + + print("----------*** 开始测试 xor_str ***----------") + + local rawData = "admin00000" + local key = "1234567890-----" + + local xor = crypt.xor_str(rawData, key) + + local raw = crypt.xor_str(xor, key) + + assert(raw == rawData, "转换失败") + + Log:DEBUG( "xor data = " .. crypt.hexencode(xor), "raw data = " .. raw) + + print("----------*** xor_str 测试完成 ***----------\n") + +end + +local function test_b64() + + print("----------*** 开始测试 base64 ***----------") + + local base64encode = require"crypt".base64encode + local base64decode = require"crypt".base64decode + local base64urlencode = require"crypt".base64urlencode + local base64urldecode = require"crypt".base64urldecode + + local LOG = require "logging" + + -- 1. 测试base64与base64url的编码解码 + local text = "Base64编码及解码测试" + local nb, ub = base64encode(text), base64urlencode(text) + local txt1, txt2 = base64decode(nb), base64urldecode(ub) + LOG:DEBUG(nb, ub) + LOG:DEBUG(assert(text == txt2), assert(text == txt1), assert(txt1 == txt2)) + + local v = 'Some_data_to_be_converted' + + -- 2. 测试padding是否会影响解码 + local b1 = 'U29tZV9kYXRhX3RvX2JlX2NvbnZlcnRlZA==' + local b2 = 'U29tZV9kYXRhX3RvX2JlX2NvbnZlcnRlZA=' + local b3 = 'U29tZV9kYXRhX3RvX2JlX2NvbnZlcnRlZA' + LOG:DEBUG(1, v, assert(v == base64decode(b1))) + LOG:DEBUG(2, v, assert(v == base64decode(b2))) + LOG:DEBUG(3, v, assert(v == base64decode(b3))) + + -- 3. 递推鲁棒性测试编码 + local v1, v2 + local t1 = 'A' -- 'QQ==' + local t2 = 'AB' -- 'QUI=' + local t3 = 'ABC' -- 'QUJD' + local t4 = 'ABCD' -- 'QUJDRA==' + local t5 = 'ABCDE' -- 'QUJDREU=' + local t6 = 'ABCDEF' -- 'QUJDREVG' + local t7 = 'ABCDEFG' -- 'QUJDREVGRw==' + local t8 = 'ABCDEFGH' -- 'QUJDREVGR0g=' + local t9 = 'ABCDEFGHI' -- 'QUJDREVGR0hJ' + + v1, v2 = base64decode("QQ=="), base64decode("QQ") + LOG:DEBUG(1, t1, assert(v1 == v2), assert(t1 == v1), assert(t1 == v2)) + + v1, v2 = base64decode("QUI="), base64decode("QUI") + LOG:DEBUG(2, t2, assert(v1 == v2), assert(t2 == v1), assert(t2 == v2)) + + v1, v2 = base64decode("QUJD"), base64decode("QUJD") + LOG:DEBUG(3, t3, assert(v1 == v2), assert(t3 == v1), assert(t3 == v2)) + + v1, v2 = base64decode("QUJDRA=="), base64decode("QUJDRA") + LOG:DEBUG(4, t4, assert(v1 == v2), assert(t4 == v1), assert(t4 == v2)) + + v1, v2 = base64decode("QUJDREU="), base64decode("QUJDREU") + LOG:DEBUG(5, t5, assert(v1 == v2), assert(t5 == v1), assert(t5 == v2)) + + v1, v2 = base64decode("QUJDREVG"), base64decode("QUJDREVG") + LOG:DEBUG(6, t6, assert(v1 == v2), assert(t6 == v1), assert(t6 == v2)) + + v1, v2 = base64decode("QUJDREVGRw=="), base64decode("QUJDREVGRw") + LOG:DEBUG(7, t7, assert(v1 == v2), assert(t7 == v1), assert(t7 == v2)) + + v1, v2 = base64decode("QUJDREVGR0g="), base64decode("QUJDREVGR0g") + LOG:DEBUG(8, t8, assert(v1 == v2), assert(t8 == v1), assert(t8 == v2)) + + v1, v2 = base64decode("QUJDREVGR0hJ"), base64decode("QUJDREVGR0hJ") + LOG:DEBUG(9, t9, assert(v1 == v2), assert(t9 == v1), assert(t9 == v2)) + + -- 4. 测试篡改字符 + local bdata1 = 'NWE3MGQzMTBhYWYyODUxZTFlN2QwOWY2OWFmOGE5ZjMtMmUzOGIxZWNlZTVkNDUzNjkyYTg2NDAxYTVhZjk0MzUwMDAyLUx3QTF1OGpXWC8zelM2TUt0NG9pbW1qZzVEOD1=' + local ubdata1 = 'NWE3MGQzMTBhYWYyODUxZTFlN2QwOWY2OWFmOGE5ZjMtMmUzOGIxZWNlZTVkNDUzNjkyYTg2NDAxYTVhZjk0MzUwMDAyLUx3QTF1OGpXWC8zelM2TUt0NG9pbW1qZzVEOD1' + LOG:DEBUG(base64decode(bdata1), base64decode(ubdata1)) + + local bdata2 = 'NWE3MGQzMTBhYWYyODUxZTFlN2QwOWY2OWFmOGE5ZjMtMmUzOGIxZWNlZTVkNDUzNjkyYTg2NDAxYTVhZjk0MzUwMDAyLUx3QTF1OGpXWC8zelM2TUt0NG9pbW1qZzVEOD0=' + local ubdata2 = 'NWE3MGQzMTBhYWYyODUxZTFlN2QwOWY2OWFmOGE5ZjMtMmUzOGIxZWNlZTVkNDUzNjkyYTg2NDAxYTVhZjk0MzUwMDAyLUx3QTF1OGpXWC8zelM2TUt0NG9pbW1qZzVEOD0' + LOG:DEBUG(base64decode(bdata2), base64decode(ubdata2)) + + -- 5. paddding测试 1 + local c1, c2 + local test = "我刚刚想了一下,现在是不是应该为咱们的真实数据添加一点特殊字符.!" + -- 携带paddding测试 + c1, c2 = base64encode(test), base64urlencode(test) + LOG:DEBUG(c1, c2) + LOG:DEBUG(assert(base64decode(c1) == test), assert(base64urldecode(c2) == test)) + -- 去除paddding测试 + c1, c2 = base64encode(test, true), base64urlencode(test, true) + LOG:DEBUG(c1, c2) + LOG:DEBUG(assert(base64decode(c1) == test), assert(base64urldecode(c2) == test)) + + -- 6. paddding测试 2 + test = "A." + -- 携带paddding测试 + c1, c2 = base64encode(test), base64urlencode(test) + LOG:DEBUG(c1, c2) + LOG:DEBUG(assert(base64decode(c1) == test), assert(base64urldecode(c2) == test)) + -- 去除paddding测试 + c1, c2 = base64encode(test, true), base64urlencode(test, true) + LOG:DEBUG(c1, c2) + LOG:DEBUG(assert(base64decode(c1) == test), assert(base64urldecode(c2) == test)) + + print("----------*** base64 测试完毕 ***----------\n") + +end + +local function test_aes() + + print("----------*** 开始测试 aes_cbc/aes_ecb ***----------") + + local text = [[{"code":200,"msg":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]}]] + local key_128 = "abcdefghabcdefgh" + local iv = "98765432100admin" + + local function test_aes_128_bit() + -- ECB + local ecb_encryptData = crypt.aes_128_ecb_encrypt(key_128, text, iv) + local ecb_rawData = crypt.aes_128_ecb_decrypt(key_128, ecb_encryptData, iv) + Log:DEBUG("测试aes_128_ecb_encrypt: " .. crypt.hexencode(ecb_encryptData)) + assert(ecb_rawData == text, "aes加密/解密失败") + + -- CBC + local cbc_encryptData = crypt.aes_128_cbc_encrypt(key_128, text, iv) + local cbc_rawData = crypt.aes_128_cbc_decrypt(key_128, cbc_encryptData, iv) + Log:DEBUG("测试aes_128_cbc_encrypt: " .. crypt.hexencode(cbc_encryptData)) + assert(cbc_rawData == text, "aes加密/解密失败") + end + + + local function test_aes_192_bit() + local key_192 = "abcdefghabcdefghabcdefgh" + -- ECB + local ecb_encryptData = crypt.aes_192_ecb_encrypt(key_192, text, iv) + local ecb_rawData = crypt.aes_192_ecb_decrypt(key_192, ecb_encryptData, iv) + Log:DEBUG("测试aes_192_ecb_encrypt: " .. crypt.hexencode(ecb_encryptData)) + assert(ecb_rawData == text, "aes加密/解密失败") + + -- CBC + local cbc_encryptData = crypt.aes_192_cbc_encrypt(key_192, text, iv) + local cbc_rawData = crypt.aes_192_cbc_decrypt(key_192, cbc_encryptData, iv) + Log:DEBUG("测试aes_192_cbc_encrypt: " .. crypt.hexencode(cbc_encryptData)) + assert(cbc_rawData == text, "aes加密/解密失败") + end + + local function test_aes_256_bit() + local key_256 = "abcdefghabcdefghabcdefghabcdefgh" + -- ECB + local ecb_encryptData = crypt.aes_256_ecb_encrypt(key_256, text, iv) + local ecb_rawData = crypt.aes_256_ecb_decrypt(key_256, ecb_encryptData, iv) + Log:DEBUG("测试aes_256_ecb_encrypt: " .. crypt.hexencode(ecb_encryptData)) + assert(ecb_rawData == text, "aes加密/解密失败") + + -- CBC + local cbc_encryptData = crypt.aes_256_cbc_encrypt(key_256, text, iv) + local cbc_rawData = crypt.aes_256_cbc_decrypt(key_256, cbc_encryptData, iv) + Log:DEBUG("测试aes_256_cbc_encrypt: " .. crypt.hexencode(cbc_encryptData)) + assert(cbc_rawData == text, "aes加密/解密失败") + end + + test_aes_128_bit() + + test_aes_192_bit() + + test_aes_256_bit() + + print("----------*** aes_cbc/aes_ecb 测试完毕 ***----------\n") + +end + +local function test_rsa() + + + local function test_rsa_1024 ( ... ) + -- 生成公钥/私钥方法: + + -- 1. 使用openssl命令生成1024位私钥: openssl genrsa -out private1024.pem 1024 + + -- 2. 使用openssl命令根据1024位私钥生成公钥: openssl rsa -in private1024.pem -out public1024.pem -pubout + + local publick_key_path = "public1024.pem" + local private_key_path = "private1024.pem" + + local text = [[{"code":200,"data":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,26,26,27,28,29,30,31,32,33,34,35]}]] + + local encData = crypt.rsa_public_key_encode(text, publick_key_path, true) + -- print(encData) + local decData = crypt.rsa_private_key_decode(encData, private_key_path, true) + + assert(decData == text, "rsa 1024 公钥加密 -> 私钥解密 失败." .. decData) + + + -- local encData = crypt.rsa_private_key_encode(text, private_key_path, true) + -- -- print(encData) + -- local decData = crypt.rsa_public_key_decode(encData, publick_key_path, true) + + -- assert(decData == text, "rsa 1024 私钥加密 -> 公钥解密 失败.") + + end + + local function test_rsa_2048( ... ) + -- 生成公钥/私钥方法: + + -- 1. 使用openssl命令生成2048位私钥: openssl genrsa -out private2048.pem 2048 + + -- 2. 使用openssl命令根据2048位私钥生成公钥: openssl rsa -in private2048.pem -out public2048.pem -pubout + + local publick_key_path = "public2048.pem" + local private_key_path = "private2048.pem" + + local text = [[{"code":200,"data":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,26,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40]}]] + + local encData = crypt.rsa_public_key_encode(text, publick_key_path, true) + -- print(encData) + local decData = crypt.rsa_private_key_decode(encData, private_key_path, true) + -- print(decData) + assert(decData == text, "rsa 2048 公钥加密 -> 私钥解密 失败." .. decData) + + + -- local encData = crypt.rsa_private_key_encode(text, private_key_path, true) + -- -- print(encData) + -- local decData = crypt.rsa_public_key_decode(encData, publick_key_path, true) + -- -- print(decData) + -- assert(decData == text, "rsa 2048 私钥加密 -> 公钥解密 失败.") + + end + + local function test_rsa_4096( ... ) + -- 生成公钥/私钥方法: + + -- 1. 使用openssl命令生成4096位私钥: openssl genrsa -out private4096.pem 4096 + + -- 2. 使用openssl命令根据4096位私钥生成公钥: openssl rsa -in private4096.pem -out public4096.pem -pubout + + local publick_key_path = "public4096.pem" + local private_key_path = "private4096.pem" + + local text = [[{"code":200,"data":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,26,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,]}]] + + local encData = crypt.rsa_public_key_encode(text, publick_key_path, true) + -- print(encData) + local decData = crypt.rsa_private_key_decode(encData, private_key_path, true) + -- print(decData) + assert(decData == text, "rsa 4096 公钥加密 -> 私钥解密 失败." .. decData) + + + -- local encData = crypt.rsa_private_key_encode(text, private_key_path, true) + -- -- print(encData) + -- local decData = crypt.rsa_public_key_decode(encData, publick_key_path, true) + -- -- print(decData) + -- assert(decData == text, "rsa 4096 私钥加密 -> 公钥解密 失败.") + + end + + local function test_rsa_sign_and_rsa_verify(...) + + local text = [[{"code":200,"data":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,26,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40]}]] + + local public_1024 = "public1024.pem" + local private_1024 = "private1024.pem" + + local public_2048 = "public2048.pem" + local private_2048 = "private2048.pem" + + local public_4096 = "public4096.pem" + local private_4096 = "private4096.pem" + + local hex = true -- 是结果hex输出 + + local sign_1024 = crypt.rsa_sign(text, private_1024, "md5", hex) + print("md5 with rsa: 1024", sign_1024, crypt.rsa_verify(text, public_1024, sign_1024, "md5", hex)) + local sign_1024 = crypt.rsa_sign(text, private_1024, "sha128", hex) + print("sha128 with rsa: 1024", sign_1024, crypt.rsa_verify(text, public_1024, sign_1024, "sha128", hex)) + local sign_1024 = crypt.rsa_sign(text, private_1024, "sha256", hex) + print("sha256 with rsa: 1024", sign_1024, crypt.rsa_verify(text, public_1024, sign_1024, "sha256", hex)) + local sign_1024 = crypt.rsa_sign(text, private_1024, "sha512", hex) + print("sha512 with rsa: 1024, ", sign_1024, crypt.rsa_verify(text, public_1024, sign_1024, "sha512", hex)) + + local sign_2048 = crypt.rsa_sign(text, private_2048, "md5", hex) + print("md5 with rsa: 2048", sign_2048, crypt.rsa_verify(text, public_2048, sign_2048, "md5", hex)) + local sign_2048 = crypt.rsa_sign(text, private_2048, "sha128", hex) + print("sha128 with rsa: 2048", sign_2048, crypt.rsa_verify(text, public_2048, sign_2048, "sha128", hex)) + local sign_2048 = crypt.rsa_sign(text, private_2048, "sha256", hex) + print("sha256 with rsa: 2048", sign_2048, crypt.rsa_verify(text, public_2048, sign_2048, "sha256", hex)) + local sign_2048 = crypt.rsa_sign(text, private_2048, "sha512", hex) + print("sha512 with rsa: 2048", sign_2048, crypt.rsa_verify(text, public_2048, sign_2048, "sha512", hex)) + + + local sign_4096 = crypt.rsa_sign(text, private_4096, "md5", hex) + print("md5 with rsa: 4096", sign_4096, crypt.rsa_verify(text, public_4096, sign_4096, "md5", hex)) + local sign_4096 = crypt.rsa_sign(text, private_4096, "sha128", hex) + print("sha128 with rsa: 4096", sign_4096, crypt.rsa_verify(text, public_4096, sign_4096, "sha128", hex)) + local sign_4096 = crypt.rsa_sign(text, private_4096, "sha256", hex) + print("sha256 with rsa: 4096", sign_4096, crypt.rsa_verify(text, public_4096, sign_4096, "sha256", hex)) + local sign_4096 = crypt.rsa_sign(text, private_4096, "sha512", hex) + print("sha512 with rsa: 4096", sign_4096, crypt.rsa_verify(text, public_4096, sign_4096, "sha512", hex)) + + end + + print("----------*** 开始测试 rsa public/private encode/decode ***----------") + + test_rsa_1024() + + test_rsa_2048() + + test_rsa_4096() + + test_rsa_sign_and_rsa_verify() + + print("----------*** rsa public/private encode/decode 测试完成 ***----------\n") + +end + +local function test_uuid( ... ) + + print("----------*** 开始测试 uuid/guid 生成 ***----------") + + Log:DEBUG("生成的UUID为: " .. crypt.uuid()) + + Log:DEBUG("生成的GUID为: " .. crypt.guid()) + + print("----------*** uuid 测试完成 ***----------\n") +end + +local function main() + + local examples = { + + -- test_xor_str, + + -- test_hex, + + -- test_crc, + + -- test_url, + + -- test_b64, + + -- test_sha, + + -- test_hmac, + + -- test_des, + + -- test_strxor, + + -- test_aes, + + -- test_other, + + test_rsa, + + -- test_uuid, + + } + + for _, f in ipairs(examples) do + + f() + + end + + Log:DEBUG("总共完成了" .. #examples .. "个测试用例.") + +end + +main() \ No newline at end of file diff --git a/script/test_csv.lua b/script/test_csv.lua new file mode 100644 index 00000000..90bb584d --- /dev/null +++ b/script/test_csv.lua @@ -0,0 +1,7 @@ +local csv = require "csv" + +local LOG = require("logging") + +LOG:DEBUG(csv.loadfile("./Excel.csv")) + +LOG:DEBUG(csv.writefile("./Excel-1.csv", csv.loadfile("./Excel.csv"))) diff --git a/script/test_dingtalk.lua b/script/test_dingtalk.lua new file mode 100644 index 00000000..9046682d --- /dev/null +++ b/script/test_dingtalk.lua @@ -0,0 +1,65 @@ +local wb = require "webhook.dingtalk" +local LOG = require "logging" + +-- dingtalk API文档: https://ding-doc.dingtalk.com/doc#/serverapi2/qf2nxq + +local token = "just_your_token_not_url_and_token" -- such as "de2b0b8a3c4b8d454f47584354a794a12657aa9ff7ccf36b521368d566949e7f" + +LOG:DEBUG(wb.send_text({ + token = token, + content = "一条测试消息哦.", + -- ignore mobiles if atall equal true. + -- mobiles = {18680684684, 13000000000}, + -- atall = true, +})) + +LOG:DEBUG(wb.send_link { + token = token, + msg_title = "这是一条测试公告", + msg_link = "https://github.com/candymi", + -- optional + msg_pic = "https://avatars2.githubusercontent.com/u/13453599", + msg_describe = "这是测试公告的描述信息, 它描述了这条公告的一些外链关键内容.", +}) + +LOG:DEBUG(wb.send_actioncard{ + token = token, + msg_title = "## 消息头部", + msg_describe = "## 消息内容", + single = { + title = "阅读全文", + url = "https://www.baidu.com" + } +}) + +LOG:DEBUG(wb.send_actioncard{ + token = token, + -- 头部 + msg_title = "## 消息头部", + -- 描述 + msg_describe = "## 消息内容", + -- 隐藏机器人头像 + hide_avatar = true, + -- 设置按钮 + btns = { + { title = "按钮1", url = "https://www.qq.com" }, + { title = "按钮2", url = "https://www.baidu.com" }, + { title = "点赞支持", url = "https://www.163.com" }, + { title = "残忍关闭", url = "https://www.taobao.com" } + } +}) + +LOG:DEBUG(wb.send_feedcard { + token = token, + msg_links = { + { + msg_title = "第1个公告", msg_link = "https://www.baidu.com", + -- msg_pic = "https://avatars2.githubusercontent.com/u/13453599" -- optional + }, + { + msg_title = "第2个公告", msg_link = "https://www.qq.com", + -- optional + -- msg_pic = "https://avatars2.githubusercontent.com/u/13453599" + } + } +}) diff --git a/script/test_dns.lua b/script/test_dns.lua index b0895f08..04950b51 100644 --- a/script/test_dns.lua +++ b/script/test_dns.lua @@ -1,37 +1,71 @@ +local LOG = require "logging" local dns = require "protocol.dns" -local co = require "internal.Co" -co.spwan(function ( ... ) - print("申请解析 1") - local ok, ip = dns.resolve('www.baidu.com') - print("解析完成 1", ok, ip) -end) +local dns_resolve = dns.resolve -co.spwan(function ( ... ) - print("申请解析 2") - local ok, ip = dns.resolve('www.baidu.com') - print("解析完成 2", ok, ip) -end) +local cf = require "cf" +local fork = cf.fork +local wait = cf.wait -co.spwan(function ( ... ) - print("申请解析 3") - local ok, ip = dns.resolve('www.baidu.com') - print("解析完成 3", ok, ip) -end) +local system = require "system" +local now = system.now +local fmt = string.format +local find = string.find +local match = string.match -co.spwan(function ( ... ) - print("申请解析 11") - local ok, ip = dns.resolve('www.qq.com') - print("解析完成 11", ok, ip) -end) +local pairs = pairs + +-- 去除ipv4->ipv6映射前缀 +local function delete_ipv4_prefix (ip) + if system.is_ipv4(ip) then + return ip + end + return find(ip or "", '::ffff') and match(ip, "::[fF]+:([%d%.]+)") or ip +end + +local domains = { + ["百度"] = "www.baidu.com", + ["腾讯"] = "www.qq.com", + ["淘宝"] = "www.taobao.com", + ["谷歌"] = "www.google.com", + ["网易"] = "www.163.com", + ["京东"] = "www.jd.com", + ["唯品会"] = "www.vip.com", + ["盛大"] = "www.sdo.com", + ["测试"] = "pwaj.dwauidwa.raw" +} + +--[[ +计算方式 +(min - max + 1) * 1 为单个域名查询总次数 +(min - max + 1) * #domain 为所有域名查询总次数 +]] +local min, max = 1, 1 -co.spwan(function ( ... ) - print("申请解析 12") - local ok, ip = dns.resolve('www.qq.com') - print("解析完成 12", ok, ip) +--[[ +测试方式: 每隔3秒后为每个域名建立1万个协程进行并发查询测试; +空间消耗: 内存消耗预计在250MB左右; +时间消耗: 每次网络查询应该在0.1/Sec左右, 每次缓存查询应该在0.001/Sec左右; +]] +cf.at(3, function ( ) + for company, domain in pairs(domains) do + local start = now() + local total = 0 + for i = min, max do + fork(function (...) + local t1 = now() + local ok, ip = dns_resolve(domain) + if not ok then + LOG:WARN(fmt("测试失败: <%s>[%s]的结果:[%s]", company, domain, ip)) + return + end + total = total + (now() - t1) + if i == max then + LOG:DEBUG(fmt("测试完成 : <%s>[%s]的结果:[%s]; 平均消耗: %0.5f, 总计消耗: %0.8f", company, domain, delete_ipv4_prefix(ip), total / max, now() - start)) + end + end) + end + end + collectgarbage() end) -co.spwan(function ( ... ) - print("申请解析 13") - local ok, ip = dns.resolve('www.qq.com') - print("解析完成 13", ok, ip) -end) \ No newline at end of file +wait() \ No newline at end of file diff --git a/script/test_es.lua b/script/test_es.lua new file mode 100644 index 00000000..f3073562 --- /dev/null +++ b/script/test_es.lua @@ -0,0 +1,32 @@ +require "utils" +local es = require "es" + +---@type ElasticSearch +local o = es { + domain = "http://localhost:9200", + -- 如果需要验证 + username = "elastic", password = "PV+nXLk8z*ypGBxmO55d", +} + +assert(o:connect()) + +-- 插入数据 +print(o:insert("test", { name = "車先生1", sex = 'male', age = 9 }, 1)) +print(o:insert("test", { name = "車太太1", sex = 'female', age = 7 }, 2)) +print(o:insert("test", { name = "車先生2", sex = 'male', age = 9 }, 3)) +print(o:insert("test", { name = "車太太2", sex = 'female', age = 7 }, 4)) +print(o:insert("test", { name = "車爪嘟", sex = 'female', age = 1 }, 5)) + +-- 修改数据 +print(o:update("test", { doc = { age = 29 } }, 3)) +print(o:update("test", { doc = { age = 27 } }, 4)) + +-- 删除数据 +print(o:delete("test", 1)) +print(o:delete("test", 2)) +print(o:delete_by_query("test", { query = { match_all = { } } })) + +-- 普通查询 +var_dump(o:query("test", { --[[ 查询规则 ]] })) +-- SQL查询 +var_dump(o:sql({ query = "select * from test", --[[ 其它查询规则 ]] })) \ No newline at end of file diff --git a/script/test_http_auth.lua b/script/test_http_auth.lua new file mode 100644 index 00000000..98429066 --- /dev/null +++ b/script/test_http_auth.lua @@ -0,0 +1,19 @@ +-- 从httpc库导入并且使用 +local httpc = require "httpc" + +local basic_key, basic_value = httpc.basic_authorization("myusername", "mypassword") +print(basic_key, basic_value) + +-- 从httpc类库中导入并且使用 +local httpc_cls = require "httpc.class" +local hc = httpc_cls:new {} + +local basic_key, basic_value = hc:basic_authorization("myusername", "mypassword") +print(basic_key, basic_value) + +-- 从json.jwt导出 +local jwt = require "json.jwt" +local raw = '{"name":"Hello world."}' +local enc = jwt.encode(raw, "secret", "HS256") +local dec = jwt.decode(enc, "secret", "HS256") +print("jwt test:", raw == dec, enc) \ No newline at end of file diff --git a/script/test_httpc.lua b/script/test_httpc.lua index 02cee87b..66c1a097 100644 --- a/script/test_httpc.lua +++ b/script/test_httpc.lua @@ -1,34 +1,133 @@ -local Co = require "internal.Co" -local timer = require "internal.Timer" +local cf = require "cf" local httpc = require "httpc" +local http = require "httpc.class" local json = require "json" +local system = require "system" +local now = system.now -local ti = timer.timeout(2, function ( ... ) - Co.spwan(function ( ... ) - -- GET请求: 在参数固定的情况下可以直接写在url内 - local code, body = httpc.get("http://localhost:8080/api?page=1&limit=10", {{"Auth", "admin"}}) - print(code, body) - - -- GET请求: 在参数为动态的情况下K呀提供请求数组由框架拼接 - local code, body = httpc.get("http://[::1]:8080/api", {{"Auth", "admin"}}, {{'page', 1}, {'limit', 10}}) - print(code, body) - - -- POST HEADER 为数组, BODY为数组 - local code, body = httpc.post("http://[::ffff:127.0.0.1]:8080/api", {{"Auth", "admin"}}, {{'page', 1}, {'limit', 10}}) - print(code, body) - - -- POST HEADER 为数组, BODY为字符串 - local code, body = httpc.post("http://127.0.0.1:8080/api", {{"Auth", "admin"}}, "page=1&limit=10") - print(code, body) - - -- http json请求示例 - local code, body = httpc.json("http://localhost:8080/api", {{"Auth", "admin"}}, json.encode({page=1, limit=10})) - print(code, body) - -- http 上传文件示例 - local code, body = httpc.file('http://localhost:8080/view', nil, { - {name='1', filename='1.jpg', file='1', type='abc'}, - {name='2', filename='2.jpg', file='2', type='abc'}, - }) - print(code, body) - end) +require "utils" + +-- 这里列举出了httpc库在各种情况下的使用方式. +local domain = 'http://localhost:8080' -- domain +-- local domain = 'http://127.0.0.1:8080' -- ipv4 +-- local domain = 'http://[::1]:8080' -- ipv6 +-- local domain = 'http://[fe80::875:95ce:bcaa:f66%en0]:8080' -- internal ipv6 + +cf.timeout(3, function ( ... ) + cf.fork(function ( ... ) + local t1 = now() + print("开始时间:", t1) + -- GET请求: 在参数固定的情况下可以直接写在url内 + local code, body = httpc.get(domain.."/api?page=1&limit=10", {{"Auth", "admin"}}) + print(code, body) + + -- GET请求: 在参数为动态的情况下可以提供请求数组由httpc库进行拼接 + local code, body = httpc.get(domain.."/api", {{"Auth", "admin"}}, {{'page', 1}, {'limit', 10}}) + print(code, body) + + -- POST HEADER 为数组, BODY为数组 + local code, body = httpc.post(domain.."/api", {{"Auth", "admin"}}, {{'page', 1}, {'limit', 10}}) + print(code, body) + + -- POST HEADER 为数组, BODY为字符串 + local code, body = httpc.post(domain.."/api", {{"Auth", "admin"}}, "page=1&limit=10") + print(code, body) + + -- http json请求示例 + local code, body = httpc.json(domain.."/api", {{"Auth", "admin"}}, json.encode({page=1, limit=10})) + print(code, body) + + -- http 上传文件示例 + local code, body = httpc.file(domain..'/api', nil, { + {name='1', filename='1.jpg', file='1', type='jpg'}, + {name='2', filename='2.jpg', file='2', type='jpg'}, + }) + print(code, body) + + local t2 = now() + print("结束时间:", t1, "总耗时:", t2 - t1) + end) +end) + + +cf.timeout(1, function ( ... ) + cf.fork(function ( ... ) + local hc = http:new {} + local t1 = now() + print("开始时间:", t1) + -- GET请求: 在参数固定的情况下可以直接写在url内 + local code, body = hc:get(domain.."/api?page=1&limit=10", {{"Auth", "admin"}}) + print(code, body) + + -- GET请求: 在参数为动态的情况下可以提供请求数组由httpc库进行拼接 + local code, body = hc:get(domain.."/api", {{"Auth", "admin"}}, {{'page', 1}, {'limit', 10}}) + print(code, body) + + -- POST HEADER 为数组, BODY为数组 + local code, body = hc:post(domain.."/api", {{"Auth", "admin"}}, {{'page', 1}, {'limit', 10}}) + print(code, body) + + -- POST HEADER 为数组, BODY为字符串 + local code, body = hc:post(domain.."/api", {{"Auth", "admin"}}, "page=1&limit=10") + print(code, body) + + -- http json请求示例 + local code, body = hc:json(domain.."/api", {{"Auth", "admin"}}, json.encode({page=1, limit=10})) + print(code, body) + + local t2 = now() + print("结束时间:", t1, "总耗时:", t2 - t1) + hc:close() + end) end) + +cf.timeout(5, function () + cf.fork(function ( ... ) + local hc = http:new {} + local t1 = now() + print("开始时间:", t1) + local ok, response = hc:multi_request { + { + domain = domain.."/api", + method = "get", + headers = {{"Auth", "admin"}}, + args = {{'page', 1}, {'limit', 10}} + }, + { + domain = domain.."/api", + method = "post", + headers = {{"Auth", "admin"}}, + body = {{'page', 1}, {'limit', 10}} + }, + { + domain = domain.."/api", + method = "json", + headers = {{"Auth", "admin"}}, + json = json.encode({page=1, limit=10}) + }, + { + domain = domain.."/api", + method = "file", + headers = {{"Auth", "admin"}}, + files = { + {name='1', filename='1.jpg', file='1', type='jpg'}, + {name='2', filename='2.jpg', file='2', type='jpg'}, + } + } + } + local t2 = now() + print("结束时间:", t2, "总耗时:", t2 - t1) + + require('logging'):new({path = 'test_httpc', dump=true}):DEBUG(response, "回应数量: " .. #response) + hc:close() + end) +end) + +-- -- 如果有需要可以开启这段注释 +-- local httpd = require "httpd" +-- local app = httpd:new() +-- app:api('/api', function (content) +-- return "{}" +-- end) +-- app:listen("", 8080) +-- app:run() diff --git a/script/test_lfs.lua b/script/test_lfs.lua new file mode 100644 index 00000000..e4a4e050 --- /dev/null +++ b/script/test_lfs.lua @@ -0,0 +1,37 @@ +local Log = require "logging":new() + +local lfs = require "lfs" + +-- Log:DEBUG(lfs) + +local function list_logs_files () + local logs = {} + for filename in lfs.dir("logs") do + -- mode为"directory"表示为目录, mode为"file"表示文件 + if lfs.attributes("logs".."/"..filename).mode == "file" then + logs[#logs+1] = filename + end + end + return logs +end + +local function change_dir (dir) + local old = "将当前目录路径["..lfs.currentdir().."]修改为" + lfs.chdir(lfs.currentdir()..dir) + local new = "["..lfs.currentdir().."]" + return old..new +end + +Log:DEBUG("lfs版本为:"..lfs._VERSION) + +Log:DEBUG(change_dir("/script")) + +Log:DEBUG(change_dir("/../")) + +Log:DEBUG("查看LICENSE文件属性:", lfs.attributes("LICENSE")) + +Log:DEBUG("创建test文件夹:", lfs.mkdir("test")) + +Log:DEBUG("删除test文件夹:", lfs.rmdir("test")) + +Log:DEBUG("列出logs文件夹目录", list_logs_files()) diff --git a/script/test_location.lua b/script/test_location.lua new file mode 100644 index 00000000..5f79c3ea --- /dev/null +++ b/script/test_location.lua @@ -0,0 +1,89 @@ +local location = require "cloud.tencent.location" + +--[[ + 腾讯定位服务 + 使用详情请参考: lualib/cloud/location/tencent.lua +]] + +-- local accesskey = "Your_Access_key" +-- local secretkey = "Your_Secret_Key" + +-- local code, ret = location.getIpLocation(accesskey, secretkey, '8.8.8.8') +-- print(code, ret) + +-- local code, ret = location.getDistrictList(accesskey, secretkey) +-- print(code, ret) + +-- local code, ret = location.getDistrictChildren(accesskey, secretkey, 110000) +-- print(code, ret) + +-- local code, ret = location.searchDistrict(accesskey, secretkey, "香格里拉") +-- print(code, ret) + +-- local code, ret = location.searchSuggestion(accesskey, secretkey, "盐津铺子", "广州", 1) +-- print(code, ret) + +-- local code, ret = location.getDistance(accesskey, secretkey, { +-- mode = "walking", from = "39.071510,117.190091", to="39.840177,116.463318" +-- }) +-- print(code, ret) + +-- local code, ret = location.searchPlace(accesskey, secretkey, "长沙", "region(湖南,0)") +-- print(code, ret) + + +local amap = require "cloud.location.amap" + +--[[ + 高德定位服务 + 使用详情请参考: lualib/cloud/location/amap.lua +]] + +-- local accesskey = "Your_Access_key" +-- local secretkey = "Your_Secret_Key" + +-- local code, ret = amap.ip(accesskey, secretkey, "114.114.114.114") +-- print(code, ret) + +-- local code, ret = amap.weather(accesskey, secretkey, "110101") +-- print(code, ret) + +-- local code, ret = amap.tips(accesskey, secretkey, { keywords = "三环" }) +-- print(code, ret) + +-- local code, ret = amap.district(accesskey, secretkey, { keywords = "天马山"}) +-- print(code, ret) + +-- local code, ret = amap.convert(accesskey, secretkey, "116.481499,39.990475|116.481499,39.990375", "gps") +-- print(code, ret) + +local baidu = require "cloud.location.baidu" + +--[[ + 百度定位服务 + 使用详情请参考: lualib/cloud/location/tencent.lua +]] + +-- local accesskey = "Your_Access_key" +-- local secretkey = "Your_Secret_Key" + +-- local code, ret = baidu.geosuggestion(accesskey, secretkey, { query = "天安门", region = "北京市", output = "json"}) +-- print(code, ret) + +-- local code, ret = baidu.geosearch(accesskey, secretkey, { query = "天安门", region = "北京市", output = "json"}) +-- print(code, ret) + +-- local code, ret = baidu.geoip(accesskey, secretkey, { ip = "114.114.114.114"}) +-- print(code, ret) + +-- local code, ret = baidu.timezone(accesskey, secretkey, { location = "39.934,116.387", timestamp = os.time()}) +-- print(code, ret) + +-- local code, ret = baidu.convert(accesskey, secretkey, { coords = "114.21892734521,29.575429778924", from = 1, to = 5}) +-- print(code, ret) + +-- local code, ret = baidu.parking(accesskey, secretkey, { location = "116.313064,40.048541", coordtype = "bd09ll"}) +-- print(code, ret) + +-- local code, ret = baidu.road(accesskey, secretkey, { road_name = "天河路", city = "广州市"}) +-- print(code, ret) diff --git a/script/test_logging.lua b/script/test_logging.lua new file mode 100644 index 00000000..b24a2a7a --- /dev/null +++ b/script/test_logging.lua @@ -0,0 +1,34 @@ +local LOG = require "logging" + +--[[ + 日志库分为2种使用方式: 1. 初始化后使用, 2. 直接导入使用; 两者之间的差异根据不同需求不同使用. + + 初始化使用时为了将不同的日志打印并dump到不同的日志文件中, 这中方式在调试开发的时候非常有用. + + 直接导入使用不会将日志持久化到磁盘(输出到stdout), 相对于dump到本地磁盘. 它更是适用于docker这种做为集中式日志收集. + + `path`参数通常是一个日志文件名, 它的前缀为`logs/{your_path}`, 但是你可以根据实际情况加上路径对文件进行分割. + + `dump`参数决定是否将打印内容序列化到磁盘. + + 注意: 如果您的path中包含了目录, 需要先自行创建目录. 否则将会打印错误. +]] + +-- 初始化日志 +local log = LOG:new { path = 'admin/main' , dump = true } + +print() + +-- dump到磁盘 +log:INFO('this is INFO LOG', nil, 1, nil) +log:DEBUG('this is DEBUG LOG', nil, nil, 1) +log:WARN('this is WARN LOG', 1, nil, nil) +log:ERROR('this is ERROR LOG', nil, nil, nil) + +print() + +-- 仅输出到stdout +LOG:INFO('this is INFO LOG', nil, 1, nil) +LOG:DEBUG('this is DEBUG LOG', nil, nil, 1) +LOG:WARN('this is WARN LOG', 1, nil, nil) +LOG:ERROR('this is ERROR LOG', nil, nil, nil) diff --git a/script/test_lua_table.lua b/script/test_lua_table.lua index 97b13b20..2619521c 100644 --- a/script/test_lua_table.lua +++ b/script/test_lua_table.lua @@ -1,19 +1,19 @@ -- 0m0.190s --- local t = {} --- local insert = table.insert --- for i = 1, 10000000 do --- insert(t, i) --- end +local t = {} +local insert = table.insert +for i = 1, 10000000 do + insert(t, i) +end -- 0m1.359s --- local t = {} --- for i = 1, 10000000 do --- t[#t+1] = i --- end +local t = {} +for i = 1, 10000000 do + t[#t+1] = i +end --- -- 0m0.666s --- local t = {} --- local insert = table.insert --- for i = 1, 10000 do --- insert(t, 1, i) --- end \ No newline at end of file +-- 0m0.666s +local t = {} +local insert = table.insert +for i = 1, 10000 do + insert(t, 1, i) +end diff --git a/script/test_msgpack.lua b/script/test_msgpack.lua new file mode 100644 index 00000000..1c47a0b0 --- /dev/null +++ b/script/test_msgpack.lua @@ -0,0 +1,8 @@ +local Log = require ("logging"):new() + +local msgpack = require "msgpack" + +local msg = msgpack.encode({1, 2, 3, 4, name = "CandyMi"}) +Log:DEBUG("序列化完成:"..msg) + +Log:DEBUG(msgpack.decode(msg)) diff --git a/script/test_mysql.lua b/script/test_mysql.lua deleted file mode 100644 index 8466ce51..00000000 --- a/script/test_mysql.lua +++ /dev/null @@ -1,54 +0,0 @@ --- 测试MySQL -local mysql = require "protocol.mysql" -local config = { - host = "localhost", - port = 3306, - database = "mysql", - user = "root", - password = "123456789" -} - --- 创建->查询->销毁100次mysql连接 -local times = 100 -for i = 1, times do - local db, err = mysql:new() - if not db then - return - end - local ok, err, errno, sqlstate = db:connect(config) - if not ok then - return print("连接失败.", i, err) - end - local resp, err = db:query("select * from user") - if not resp then - return print('查询失败', err) - end - db:close() -end -print("创建并销毁"..tostring(times).."次MySQL连接成功") - - --- 保存mysql连接池 -> 销毁连接池 -local pool = {} -for i = 1, times do - local db, err = mysql:new() - if not db then - return - end - local ok, err, errno, sqlstate = db:connect(config) - if not ok then - return print("连接失败.", i, err) - end - local resp, err = db:query("select * from user") - if not resp then - return print('查询失败', err) - end - pool[#pool+1] = db -end -print('创建'..tostring(times)..'MySQL连接成功') -for _, db in ipairs(pool) do - db:close() -end -pool = nil -print('销毁MySQL连接') -print("测试完成") \ No newline at end of file diff --git a/script/test_protobuf.lua b/script/test_protobuf.lua new file mode 100644 index 00000000..29cc7bf4 --- /dev/null +++ b/script/test_protobuf.lua @@ -0,0 +1,37 @@ +local Log = require "logging" + +local pb = require "protobuf" + +--[[ +测试与使用指南: + + 1. 安装protobuf的命令行二进制生成工具protoc. + + 2. 使用命令 protoc Person.proto -o Person.pb 生成协议文件. + + 3. 使用loadfile读取生成的协议文件Person.pb后就完成了数据结构注册与导入. + + 4. 这有时候就可以开始使用encode/decode方法进行代码测试. + + 5. 需要注意的是: protobuf协议需要"先定义(注册), 后使用", 支持protobuf v2/v3版本语法. +]] + +Log:DEBUG(pb.loadfile("Person.pb")) + +local pb_string = pb.encode("Person", { + name = "CandyMi", + age = 2^32 - 1, + hand = { + left = "左手", + right = "右手", + }, + foot = { + left = "左脚", + right = "右脚", + } +}) +Log:DEBUG(pb.tohex(pb_string)) + +Log:DEBUG(pb.decode("Person", pb_string)) + +Log:DEBUG(pb.clear("Person")) diff --git a/script/test_qiniu.lua b/script/test_qiniu.lua new file mode 100644 index 00000000..75cd6f36 --- /dev/null +++ b/script/test_qiniu.lua @@ -0,0 +1,114 @@ +local token = require "cloud.qiniu.token" +local censor = require "cloud.qiniu.censor" +local Stream = require "cloud.qiniu.Stream" +local ocr = require "cloud.qiniu.ocr" +local sms = require "cloud.qiniu.sms" +local oss = require "cloud.qiniu.oss" +local LOG = require "logging" + +local AccessKey = 'Your_Real_AccessKey' +local SecretKey = 'Your_Real_SecretKey' + +-- -- *******短信服务******** +-- +-- -- 发送短信 +-- local ok, err = sms.sendSMS(AccessKey, SecretKey, { template_id = '1174104085482180608', mobiles = {'+8613000000000'}, parameters = { code = tostring(math.random(1, 1024)) }}) +-- LOG:DEBUG(ok, err) +-- +-- -- 获取短信模板列表 +-- local ok, err = sms.getTemplates(AccessKey, SecretKey, { page = 1, page_size = 100 }) +-- LOG:DEBUG(ok, err) +-- +-- -- 获取短信签名列表 +-- local ok, err = sms.getSignatures(AccessKey, SecretKey, { page = 1, page_size = 90 }) +-- LOG:DEBUG(ok, err) +-- +-- -- 获取发送短信记录 +-- local ok, err = sms.getSMSRecord(AccessKey, SecretKey, { start = '1568131200', ['end'] = '1568822399'}) +-- LOG:DEBUG(ok, err) +-- +-- -- 删除模板 +-- local ok, err = sms.detTemplate(AccessKey, SecretKey, '1174104085482180608') +-- LOG:DEBUG(ok, err) +-- +-- -- 删除签名 +-- local ok, err = sms.delSignature(AccessKey, SecretKey, '1174104085482180608') +-- LOG:DEBUG(ok, err) +-- +-- -- *******Token与验权******** +-- +-- -- 生成上传token +-- local upToken = oss.getUploadToken(AccessKey, SecretKey, { bucket = 'candymi' }) +-- LOG:DEBUG(upToken) +-- +-- -- 生成下载token +-- local upToken, err = oss.getDownloadToken(AccessKey, SecretKey, { url = 'https://www.gitub.com/a.img', expires = os.time() + 180 }) +-- LOG:DEBUG(upToken, err) +-- +-- +-- local url = "http://rs.qiniu.com/move/bmV3ZG9jczpmaW5kX21hbi50eHQ=/bmV3ZG9jczpmaW5kLm1hbi50eHQ=" +-- LOG:DEBUG(token.getAccessToken(AccessKey, SecretKey, { path = "/move/bmV3ZG9jczpmaW5kX21hbi50eHQ=/bmV3ZG9jczpmaW5kLm1hbi50eHQ=" })) +-- +-- -- *******直播服务******** +-- +-- -- 创建推流 +-- local code, ret = Stream.createStream(AccessKey, SecretKey, 'hub', 'candymi') +-- LOG:DEBUG(code, ret) +-- +-- -- 查询推流 +-- local code, ret = Stream.queryStream(AccessKey, SecretKey, 'hub', 'candymi') +-- LOG:DEBUG(code, ret) +-- +-- -- 关闭推流 +-- local code, ret = Stream.disableStream(AccessKey, SecretKey, 'hub', 'candymi') +-- LOG:DEBUG(code, ret) +-- +-- -- 查询推流详情 +-- local code, ret = Stream.getStreamLive(AccessKey, SecretKey, 'hub', 'candymi') +-- LOG:DEBUG(code, ret) +-- +-- -- 批量查询推流详情 +-- local code, ret = Stream.getStreamLives(AccessKey, SecretKey, 'hub', {'candymi', "abc", "def"}) +-- LOG:DEBUG(code, ret) +-- +-- -- 批量查询推流历史记录 +-- local code, ret = Stream.getStreamHistory(AccessKey, SecretKey, 'hub', 'candymi', {["start"] = os.time(), ["end"] = os.time()}) +-- LOG:DEBUG(code, ret) +-- +-- -- rtmp推流地址 +-- local url = Stream.rtmpPublishUrl(AccessKey, SecretKey, "pub-rtmp.test.com", 'hub', 'candymi') +-- LOG:DEBUG(url) +-- +-- -- rtmp观看地址 +-- local url = Stream.rtmpSubscribeUrl("sub-rtmp.test.com", 'hub', 'candymi') +-- LOG:DEBUG(url) +-- +-- -- hls观看地址 +-- local url = Stream.hlsSubscribeUrl("sub-hls.test.com", 'hub', 'candymi') +-- LOG:DEBUG(url) +-- +-- -- hdl观看地址 +-- local url = Stream.hdlSubscribeUrl("sub-hdl.test.com", 'hub', 'candymi') +-- LOG:DEBUG(url) +-- +-- -- 封面快照地址 +-- local url = Stream.getSnapshot("snap-shot.test.com", 'hub', 'candymi') +-- LOG:DEBUG(url) +-- +-- -- *******内容服务******** +-- +-- local code, ret = censor.newImageCensor(AccessKey, SecretKey, "http://img3.redocn.com/20120528/Redocn_2012052817213559.jpg") +-- LOG:DEBUG(code, ret) +-- +-- local code, ret = censor.newVideoCensor(AccessKey, SecretKey, "https://www.w3school.com.cn/i/movie.ogg") +-- LOG:DEBUG(code, ret) +-- +-- local job_id = "5d90cb95093ff70008e32e0a" +-- +-- local code, ret = censor.getVideoCensorResult(AccessKey, SecretKey, job_id) +-- LOG:DEBUG(code, ret) +-- +-- -- *******身份证识别******** +-- +-- local code, ret = ocr.idcard(AccessKey, SecretKey, "http://5b0988e595225.cdn.sohucs.com/images/20190111/fe9b7bb1afac482c89963174b2feac14.jpeg") +-- LOG:DEBUG(code, ret) diff --git a/script/test_translate.lua b/script/test_translate.lua new file mode 100644 index 00000000..96783217 --- /dev/null +++ b/script/test_translate.lua @@ -0,0 +1,33 @@ +local baidu = require "cloud.translate.baidu" +local youdao = require "cloud.translate.youdao" + +--[[ + 免费的有道词典接口, 采用https安全传输. + youdao.ZH_CN2EN -- 中文 >> 英语 + youdao.ZH_CN2JA -- 中文 >> 日语 + youdao.ZH_CN2KR -- 中文 >> 韩语 + youdao.ZH_CN2FR -- 中文 >> 法语 + youdao.ZH_CN2RU -- 中文 >> 俄语 + youdao.ZH_CN2SP -- 中文 >> 西语 + youdao.EN2ZH_CN -- 英语 >> 中文 + youdao.JA2ZH_CN -- 日语 >> 中文 + youdao.KR2ZH_CN -- 韩语 >> 中文 + youdao.FR2ZH_CN -- 法语 >> 中文 + youdao.RU2ZH_CN -- 俄语 >> 中文 + youdao.SP2ZH_CN -- 西语 >> 中文 +]] + +local query = "床前明月光, 疑是地上霜. 举头望明月, 低头思故乡." + +-- 免费 +local code, ret = youdao.translate(youdao.ZH_CN2EN, query) +print(code, ret) + +-- 数量收费 +local app_id, app_key = "your_app_id", "your_app_key" +local code, ret = baidu.translate(app_id, app_key, { + q = query, + to = "en", + from = "zh", +}) +print(code, ret) \ No newline at end of file diff --git a/script/test_upload.lua b/script/test_upload.lua new file mode 100644 index 00000000..4e86c42d --- /dev/null +++ b/script/test_upload.lua @@ -0,0 +1,86 @@ +local httpd = require "httpd" +local json = require "json" + +local app = httpd:new("httpd") + +app:use("/upload", function(content) + return [[ + + + + 本地文件上传 + + +
      + +
      + +
      + +
      + + +]] +end) + +app:api("/upload_local", function (content) + if content.files then + for _, item in ipairs(content.files) do + local f, err = io.open("static/"..item.filename, "w+") + if f then + f:write(item.file) + f:flush() + f:close() + else + print("ERROR: " .. err) + end + end + return json.encode { code = 200, status = "OK" } + end + return json.encode { code = 404, status = "没有上传内容." } +end) + + +app:use('/qiniu_upload', function(content) + return [[ + + + + 七牛文件上传 + + + +
      + + + +
      +

      无token

      + + + +]] +end) + +local oss = require "cloud.qiniu.oss" +app:api('/qiniu_token', function (content) + local access_key = "your_access_key" + local secret_key = "your_secret_key" + return json.encode { + code = 200, + token = oss.getUploadToken(access_key, secret_key, { + bucket = "candymi", -- 对应bucket名称(这个必填, 其它选填) + }), + } +end) + +app:static("static") + +app:listen("0.0.0.0", 8080) + +app:run() diff --git a/script/test_wsclient.lua b/script/test_wsclient.lua new file mode 100644 index 00000000..ea488b2c --- /dev/null +++ b/script/test_wsclient.lua @@ -0,0 +1,23 @@ +local wc = require "protocol.websocket.client" + +-- local w = wc:new {url = "wss://[::1]/ws"} +-- local w = wc:new {url = "wss://[::1]:8080/ws"} +local w = wc:new {url = "ws://localhost:8080/ws"} +local ok, ret = w:connect() +if not ok then + return print(ok, ret) +end + +w:ping('ping') + +print(w:recv()) + +while 1 do + local data, typ = w:recv() + print(data, typ) + if not data then + break + end +end + +w:close() diff --git a/script/test_xml.lua b/script/test_xml.lua new file mode 100644 index 00000000..5ed16e9a --- /dev/null +++ b/script/test_xml.lua @@ -0,0 +1,48 @@ +local xml2lua = require "xml2lua" +require "utils" + +local xml = [[ + + + 老虎 + meta + + + 狮子 + meta + + + + 水果糖 + 車先生 + + + + 买买买 + 玩玩玩 + 逛逛逛 + + 肉肉 + 小宝贝 + 小QQ + 車爪鱼 + + + + +]] + +-- benchmark time: ./cfadmin 耗时:3.6xx/Sec +for i = 1, 10000 do + xml2lua.parser(xml) +end + +-- 打印解析后的表结构 +local tab = xml2lua.parser(xml) +var_dump(tab) + +-- 原版xml2lua打印会出现相等的情况 +-- 这在cf中可能导致不可预知的情况. +local tab1 = xml2lua.parser(xml) +local tab2 = xml2lua.parser(xml) +print(tab1, tab2) diff --git a/script/test_zlib.lua b/script/test_zlib.lua new file mode 100644 index 00000000..a9cd1f89 --- /dev/null +++ b/script/test_zlib.lua @@ -0,0 +1,71 @@ +local z = require "lz" + +local LOG = require "logging" + +local text = [[ + + Lua 轻量级网络开发框架(A lua Lightweight Network Development Framework) + + 生态多 —— 集成社区库最多的框架之一, 并自行实现了一些网络协议生态. + + Multi-ecology —— Integrate most community third-party libraries and implement many protocols on their own. + + 稳定性好 —— 许多领域的企业已经开始使用,并且用户数量也在逐渐增加. + + Good stability —— Enterprises in many fields have begun to use, and users are gradually increasing. + + 高效率 —— 高效的静态语言与高效的虚拟机实现优秀的运行时框架. + + High efficiency - Efficient static language and efficient virtual machine to achieve excellent runtime framework. + + 高可维护性 —— 通俗易懂的框架编写方式可以让开发者快速适应并且上手. + + High readability —— The easy-to-understand framework writing method allows developers to quickly adapt and get started. + + 《运行》 + + cf框架在整体构建完毕后会在项目根目录产生一个可执行文件: cfadmin, 它会根据当前目录环境执行对应的入口文件(script/main.lua). + + 《命令与参数》 + + ./cfadmin, 前台执行; 使用ctrl + z、ctrl + c、ctrl + \等组合键就能让它停止执行. + + ./cfadmin -d, 后台执行; 通常你需要使用killall cfadmin与kill -9 PID这样的命令才能终止它. + + 《选择合适的运行方式》 + + cf默认情况下会将日志输出在stdout. 在开发与测试期间通常会前台运行, 这样无论是打印日志还是定位问题都会变得较为简单. + + 如果您将cf放置在容器内部, 前台运行通常会是一个比较好的选择. 有利于贴合容器日志生态, 合理配合日志收集器做集中式日志检索与管理. + + 如果您将cf放置到原生系统下, 建议将cf至于后台运行. + +]] + +local cp_text = z.compress(text) + +local raw_text = z.uncompress(cp_text) + +assert(raw_text == text, "测试LZ77压缩/解压方法失败") + +LOG:DEBUG("compress压缩前的文本长度为:" .. #raw_text, "压缩后的文本长度为:" .. #cp_text) + +-- 分割线 --- + +local cp_text = z.compress2(text) + +local raw_text = z.uncompress2(cp_text) + +assert(raw_text == text, "测试gzip压缩/解压失败") + +LOG:DEBUG("compress2压缩前的文本长度为:" .. #raw_text, "压缩后的文本长度为:" .. #cp_text) + +-- 分割线 --- + +local cp_text = z.gzcompress(text) + +local raw_text = z.gzuncompress(cp_text) + +assert(raw_text == text, "测试gzip压缩/解压失败") + +LOG:DEBUG("gzcompress压缩前的文本长度为:" .. #raw_text, "压缩后的文本长度为:" .. #cp_text) \ No newline at end of file diff --git a/script/ws.lua b/script/ws.lua index d5e8a3c0..731612c1 100644 --- a/script/ws.lua +++ b/script/ws.lua @@ -1,16 +1,19 @@ local class = require "class" local cf = require "cf" -local json = require "json" -local MQ = require "MQ" +require "utils" local websocket = class("websocket") + function websocket:ctor(opt) self.ws = opt.ws -- websocket对象 + self.args = opt.args -- GET传递的参数 + self.headers = opt.headers -- HTTP请求头部 self.send_masked = false -- 掩码(默认为false, 不建议修改或者使用) self.max_payload_len = 65535 -- 最大有效载荷长度(默认为65535, 不建议修改或者使用) - self.timeout = 15 + self.timeout = 15 -- 默认为一直等待, 非number类型会导致异常. self.count = 0 - self.mq = MQ:new({host = 'localhost', port = 6379, type = 'redis'}) + var_dump(self.args) + var_dump(self.headers) end function websocket:on_open() @@ -19,19 +22,12 @@ function websocket:on_open() self.count = self.count + 1 self.ws:send(tostring(self.count)) end) - self.mq:on('/test/*', function (msg) -- 消息队列 - if not msg then - self.ws:send('{"code":500,"message":"无法连接到mq(reds)"}') - return self.ws:close() - end - self.ws:send(json.encode(msg)) - end) end function websocket:on_message(data, typ) print('on_message', self.ws, data) self.ws:send('welcome') - self.ws:close(data) + -- self.ws:close(data) end function websocket:on_error(error) @@ -40,13 +36,10 @@ end function websocket:on_close(data) print('on_close', self.ws, data) - if self.mq then -- 清理消息队列 - self.mq:close() - self.mq = nil - end - if self.timer then -- 清理定时器 - self.timer:stop() - self.timer = nil + if self.timer then -- 清理定时器 + print("清理定时器") + self.timer:stop() + self.timer = nil end end diff --git a/src/Makefile b/src/Makefile index 906dca5e..81aee102 100644 --- a/src/Makefile +++ b/src/Makefile @@ -9,46 +9,38 @@ default : CC = cc -LIBS += -L/usr/local/lib -INCLUDES += -I/usr/local/include +LIBS += -L. -L../ -L/usr/local/lib +INCLUDES += -I. -I../ -I/usr/local/include # 使用jemalloc内存分配器请启用这段 -# CFLAGS += -Wall -Os -fPIC --shared -DJEMALLOC -# DLL += -ljemalloc -lev -llua -# MACRO += -w -Os -L./ -L../ -DJEMALLOC +# CFLAGS += -Wall -O3 -fPIC --shared -DJEMALLOC -fno-strict-aliasing -Wl,-rpath,. -Wl,-rpath,.. -Wl,-rpath,/usr/local/lib +# MACRO += -O3 -Wl,-rpath,. -Wl,-rpath,.. -Wl,-rpath,/usr/local/lib/ -DJEMALLOC +# DLL += -ljemalloc -lev -llua -ldl -lm # 使用tcmalloc内存分配器请启用这段 -# CFLAGS += -Wall -Os -fPIC --shared -DTCMALLOC -# DLL += -ltcmalloc -lev -llua -# MACRO += -w -Os -L./ -L../ -DTCMALLOC +# CFLAGS += -Wall -O3 -fPIC --shared -DTCMALLOC -fno-strict-aliasing -Wl,-rpath,. -Wl,-rpath,.. -Wl,-rpath,/usr/local/lib +# MACRO += -O3 -Wl,-rpath,. -Wl,-rpath,.. -Wl,-rpath,/usr/local/lib -DTCMALLOC +# DLL += -ltcmalloc -lev -llua -ldl -lm # 默认情况下使用系统内存分配器 -CFLAGS += -Wall -Os -fPIC --shared -DLL += -lev -llua -MACRO += -w -Os +CFLAGS += -Wall -O3 -fPIC --shared -fno-strict-aliasing -Wl,-rpath,. -Wl,-rpath,.. -Wl,-rpath,/usr/local/lib +MACRO += -O3 -Wl,-rpath,. -Wl,-rpath,.. -Wl,-rpath,/usr/local/lib +DLL += -lev -llua -ldl -lm build : - - $(CC) -o libcore.so core.c core_memory.c core_sys.c $(CFLAGS) $(INCLUDES) $(LIBS) $(DLL) - - mv *.so /usr/local/lib - - $(CC) core_start.c -o cfadmin $(MACRO) -lcore -ldl - - mv cfadmin ../ - -rebuild: - - rm -rf *.o *.so - - $(CC) -o libcore.so core.c core_memory.c core_sys.c $(CFLAGS) $(INCLUDES) $(LIBS) $(DLL) - - mv *.so /usr/local/lib - - $(CC) core_start.c -o cfadmin $(MACRO) -lcore -ldl - - mv cfadmin ../ - -clean : - - rm -rf *.o *.so + @echo "********** Core library and executable **********" + @if [ "`uname -s`" = 'Linux' ] || [ "`uname -s`" = 'Android' ] || [ "`uname -s`" = 'Darwin' ] || \ + [ "`uname -s`" = 'FreeBSD' ] || [ "`uname -s`" = 'OpenBSD' ] || [ "`uname -s`" = 'NetBSD' ] || [ "`uname -s`" = 'DragonFlyBSD' ] \ + ; then \ + $$@echo "CC - libcore" ; \ + $$@cc -o libcore.so core.c core_ev.c core_memory.c core_sys.c $(CFLAGS) $(INCLUDES) $(LIBS) $(DLL) ; \ + $$@echo "CC - cfadmin" ; \ + $$@cc -o cfadmin core_start.c $(MACRO) $(INCLUDES) $(LIBS) -lcore ; \ + $$@mv *.so cfadmin ../ ; \ + else \ + $$@echo "CC - libcore" ; \ + $$@cc -o libcore.dll core.c core_ev.c core_memory.c core_sys.c $(CFLAGS) $(INCLUDES) $(LIBS) $(DLL) ; \ + $$@echo "CC - cfadmin" ; \ + $$@cc -o cfadmin.exe core_start.c $(MACRO) $(INCLUDES) $(LIBS) -lcore ; \ + $$@mv *.dll cfadmin.exe ../ ; \ + fi; diff --git a/src/core.c b/src/core.c index c3b63398..9a114c7b 100644 --- a/src/core.c +++ b/src/core.c @@ -1,276 +1,333 @@ #include "core.h" -/* =========== Timer =========== */ -void -core_timer_init(core_timer *timer, _TIMER_CB cb){ - - timer->repeat = timer->at = 0x0; - - ev_init(timer, cb); - -} - -void -core_timer_start(core_loop *loop, core_timer *timer, ev_tstamp timeout){ - - timer->repeat = timeout; - - ev_timer_again(loop ? loop : CORE_LOOP, timer); - -} - -void -core_timer_stop(core_loop *loop, core_timer *timer){ - - timer->repeat = timer->at = 0x0; - - ev_timer_again(loop ? loop : CORE_LOOP, timer); - -} -/* =========== Timer =========== */ - - - - -/* =========== IO =========== */ -void -core_io_init(core_io *io, _IO_CB cb, int fd, int events){ - - ev_io_init(io, cb, fd, events); - -} - -void -core_io_start(core_loop *loop, core_io *io){ - - ev_io_start(loop ? loop : CORE_LOOP, io); - -} - -void -core_io_stop(core_loop *loop, core_io *io){ - - if (io->events || io->fd){ - - ev_io_stop(loop ? loop : CORE_LOOP, io); - - io->fd = io->events = 0x0; - - } - +#define MASTER (1) +#define WORKER (2) +#define IS_MASTER(mode) (mode == MASTER) +#define IS_WORKER(mode) (mode == WORKER) + +#define LUALIBS_PATH \ + "lualib/?.lua;;lualib/?/init.lua;;" \ + "lualib/?.lc;;lualib/?/init.lc;;" \ + \ + "3rd/?.lua;;3rd/?/init.lua;;" \ + "3rd/?.lc;;3rd/?/init.lc;;" \ + \ + "script/?.lua;;script/?/init.lua;;" \ + "script/?.lc;;script/?/init.lc;;" + + +#define LUACLIBS_PATH \ + "luaclib/?.so;;luaclib/lib?.so;;" \ + "3rd/?.so;;3rd/lib?.so;;" \ + \ + "3rd/?.dylib;;3rd/lib?.dylib;;" \ + "luaclib/?.dylib;;luaclib/lib?.dylib;;" \ + \ + "luaclib/?.dll;;luaclib/msys-?.dll;;" \ + "3rd/?.dll;;3rd/msys-?.dll;;" + +/* Master 进程 `Main`函数 */ +static const char* master_boot = "\n\ +local process = require 'process.master'\n\ +\ +process.init(...)\n\ +\ +local f = loadfile('script/boot.lua')\n\ +\ +if f then\n\ +\ + require 'cf'.fork(f)\n\ +\ +end\n\ +require 'cf'.wait()\n\ +"; + +/* Worker 进程 `Main`函数 */ +static const char* worker_boot = "\n\ +local process = require 'process.worker'\n\ +\ +process.init()\n\ +\ +local f = assert(loadfile(...))\n\ +\ +require 'cf'.fork(f)\n\ +\ +require 'cf'.wait()\n\ +"; + +static lua_State *L = NULL; + +/* 打印堆栈 */ +static void SIG_DUMP(int signo){ + // printf("收到信号\n"); + if (!L) + return; + + int top = lua_gettop(L); + if (lua_getfield(L, LUA_REGISTRYINDEX, "co") != LUA_TTHREAD) + return lua_settop(L, top); + + luaL_traceback(L, lua_tothread(L, -1), NULL, 0); + printf("\n===========Lua Stack===========\n"); + printf("%s", lua_tostring(L, -1)); + printf("\n===========Lua Stack===========\n"); + + return lua_settop(L, top); } -/* =========== IO =========== */ - - -/* =========== TASK =========== */ - -void -core_task_init(core_task *task, _TASK_CB cb){ - - ev_idle_init(task, cb); +/* 忽略信号 */ +static void SIG_IGNORE(core_loop *loop, core_signal *signal, int revents){ + (void)loop; (void)signal; (void)revents; + return ; } -void -core_task_start(core_loop *loop, core_task *task){ - - ev_idle_start(loop ? loop : CORE_LOOP, task); - +/* 退出信号 */ +static void SIG_EXIT(core_loop *loop, core_signal *signal, int revents){ + (void)signal; (void)revents; + /* 只有主进程退出的时候才需要通知子进程退出 */ + if (ev_userdata(loop)) + kill(0, SIGQUIT); + return exit(EXIT_SUCCESS); } -void -core_task_stop(core_loop *loop, core_task *task){ - - ev_idle_stop(loop ? loop : CORE_LOOP, task); - +/* 内部异常 */ +static void EV_ERROR_CB(const char *msg){ + LOG("ERROR", msg); + LOG("ERROR", strerror(errno)); + if (CORE_LOOP) { + pid_t *pids = (pid_t *)ev_userdata(CORE_LOOP); + if (pids) + kill(0, SIGKILL); + } + /* 减少无效打印, 专注错误提示 */ + return exit(EXIT_SUCCESS); } -/* =========== TASK =========== */ - - -core_loop * -core_default_loop(){ - // ev_supported_backends() & EVBACKEND_EPOLL || // Linux 使用 epoll - // ev_supported_backends() & EVBACKEND_KQUEUE || // mac|BSD 使用 kqueue - // ev_supported_backends() & EVBACKEND_SELECT || // other 使用 select - // EVFLAG_AUTO // select 都没有就自动选择 - return ev_default_loop(ev_embeddable_backends() & ev_supported_backends() || EVFLAG_AUTO); +/* 为libev内存hook注入日志 */ +static void *EV_ALLOC(void *ptr, long nsize){ + if (nsize == 0) return xfree(ptr), NULL; + for (;;) { + void *newptr = xrealloc(ptr, nsize); + if (newptr) return newptr; + LOG("WARN", "Allocate failed, Sleep sometime.."); + sleep(1); + } } -void -core_break(core_loop *loop, int mode){ - return ev_break(loop ? loop : CORE_LOOP, mode); +/* 为lua内存hook注入日志 */ +static void* L_ALLOC(void *ud, void *ptr, size_t osize, size_t nsize){ + /* 用户自定义数据 */ + (void)ud; (void)osize; + if (nsize == 0) return xfree(ptr), NULL; + for (;;) { + void *newptr = xrealloc(ptr, nsize); + if (newptr) return newptr; + LOG("WARN", "Allocate failed, Sleep sometime.."); + sleep(1); + } } - -int -core_start(core_loop *loop, int mode){ - - return ev_run(loop ? loop : CORE_LOOP, mode); - +void init_lua_mode(lua_State *L, int mode) { + /* worker */ + if (IS_WORKER(mode) && getenv("cfadmin_isWorker")) { + lua_pushliteral(L, "worker"); + lua_createtable(L, 0, 0); + lua_pushliteral(L, "id"); + lua_pushinteger(L, getpid() - getppid()); + lua_rawset(L, -3); + lua_pushliteral(L, "pid"); + lua_pushinteger(L, getpid()); + lua_rawset(L, -3); + lua_pushliteral(L, "ppid"); + lua_pushinteger(L, getppid()); + lua_rawset(L, -3); + lua_pushliteral(L, "nprocess"); + lua_pushinteger(L, atoi(getenv("cfadmin_nprocess"))); + lua_rawset(L, -3); + lua_rawset(L, -3); + /* Master */ + } else if (IS_MASTER(mode)) { + lua_pushliteral(L, "master"); + lua_createtable(L, 0, 0); + lua_pushliteral(L, "pid"); + lua_pushinteger(L, getpid()); + lua_rawset(L, -3); + lua_pushliteral(L, "nprocess"); + lua_pushinteger(L, atoi(getenv("cfadmin_nprocess"))); + lua_rawset(L, -3); + lua_rawset(L, -3); + } } -static void -SIG_CB(core_loop *loop, core_signal *signal, int revents){ - // LOG("WARN", "RECV SIGNAL..."); - return ; -} +void init_lua_libs(lua_State *L, int mode){ + /* lua 标准库 */ + luaL_openlibs(L); -static void -ERROR_CB(const char *msg){ - LOG("ERROR", msg); - return ; -} + // 获取全局表 + lua_pushglobaltable(L); + // 将我可能被运行的文件名放置到全局表 + lua_pushliteral(L, " return 'Hello world.' "); + lua_setfield(L, -2, "mycode"); -static void * -EV_ALLOC(void *ptr, long nsize){ - // 为libev内存hook注入日志; - if (ptr && 0 > nsize){ - LOG("ERROR", "attemp to pass a negative number to malloc or free") - return NULL; - } - if (nsize == 0) return xfree(ptr), NULL; - for (;;) { - void *newptr = xrealloc(ptr, nsize); - if (newptr) return newptr; - LOG("WARN", "Allocate Failt, sleep sometime.."); - sleep(1); - } -} + lua_pushliteral(L, "null"); + lua_pushlightuserdata(L, NULL); + lua_rawset(L, -3); + lua_pushliteral(L, "NULL"); + lua_pushlightuserdata(L, NULL); + lua_rawset(L, -3); -static void * -L_ALLOC(void *ud, void *ptr, size_t osize, size_t nsize){ - // 为lua内存hook注入日志; - /* 用户自定义数据 */ - (void)ud; (void)osize; - if (nsize == 0) return xfree(ptr), NULL; - for (;;) { - void *newptr = xrealloc(ptr, nsize); - if (newptr) return newptr; - LOG("WARN", "Allocate Failt, sleep sometime.."); - sleep(1); - } -} + /*注入*/ + init_lua_mode(L, mode); -void -init_lua_libs(lua_State *L){ - /* lua 标准库 */ - luaL_openlibs(L); + lua_settop(L, 0); - lua_pushglobaltable(L); - lua_pushliteral(L, "null"); - lua_pushlightuserdata(L, NULL); - lua_rawset(L, -3); - lua_pushliteral(L, "NULL"); - lua_pushlightuserdata(L, NULL); - lua_rawset(L, -3); + /* 注入lua搜索域 */ + lua_getglobal(L, "package"); - lua_settop(L, 0); + /* 注入lualib搜索路径 */ + lua_pushliteral(L, LUALIBS_PATH); + lua_setfield(L, 1, "path"); - /* 注入lua搜索域 */ - lua_getglobal(L, "package"); + /* 注入luaclib搜索路径 */ + lua_pushliteral(L, LUACLIBS_PATH); + lua_setfield(L, 1, "cpath"); - lua_pushliteral(L, "./script/?.lua;./script/?/init.lua;./lualib/?.lua;./lualib/?/init.lua;./?/init.lua"); - lua_setfield(L, 1, "path"); + lua_settop(L, 0); - lua_pushliteral(L, "./luaclib/?.so;./luaclib/?/init.so;"); - lua_setfield(L, 1, "cpath"); - - lua_settop(L, 0); + /* 优化Lua的GC */ + CO_GCRESET(L); } -static lua_State *L; +/* 注册需要忽略的信号 */ core_signal sighup; core_signal sigpipe; -core_signal sigquit; core_signal sigtstp; -void -signal_init(){ +/* 注册需要退出的信号(docker需要) */ +core_signal sigint; +core_signal sigterm; +core_signal sigquit; - /* 忽略父进程退出的信号 */ - ev_signal_init(&sighup, SIG_CB, SIGHUP); - ev_signal_start(CORE_LOOP_ &sighup); +static inline void signal_init(){ - /* 忽略管道信号 */ - ev_signal_init(&sigpipe, SIG_CB, SIGPIPE); - ev_signal_start(CORE_LOOP_ &sigpipe); + signal(SIGUSR1, SIG_DUMP); + signal(SIGUSR2, SIG_DUMP); - /* 忽略Ctrl-/操作信号 */ - ev_signal_init(&sigquit, SIG_CB, SIGQUIT); - ev_signal_start(CORE_LOOP_ &sigquit); + /* 忽略连接中断信号 */ + core_signal_init(&sighup, SIG_IGNORE, SIGHUP); + core_signal_start(CORE_LOOP_ &sighup); - /* 忽略Ctrl-Z操作信号 */ - ev_signal_init(&sigtstp, SIG_CB, SIGTSTP); - ev_signal_start(CORE_LOOP_ &sigtstp); + /* 忽略无效的管道读/写信号 */ + core_signal_init(&sigpipe, SIG_IGNORE, SIGPIPE); + core_signal_start(CORE_LOOP_ &sigpipe); -} + /* 忽略Ctrl-Z操作信号 */ + core_signal_init(&sigtstp, SIG_IGNORE, SIGTSTP); + core_signal_start(CORE_LOOP_ &sigtstp); -void -init_main(){ - - int status; - L = lua_newstate(L_ALLOC, NULL); - if (!L) return ; - - init_lua_libs(L); - - status = luaL_loadfile(L, "script/main.lua"); - - // 停止GC - lua_gc(L, LUA_GCSTOP, 0); - - // 设置 GC间歇率 = 每次开启一次新的GC所需的等待时间与条件; 默认为:200 - // lua_gc(L, LUA_GCSETPAUSE, 200); - - // 设置 GC步进率倍率 = 控制垃圾收集器相对于内存分配速度的倍数; 默认为:200 - // lua_gc(L, LUA_GCSETSTEPMUL, 200); - - if(status != LUA_OK) { - switch(status){ - case LUA_ERRFILE : - LOG("ERROR", "Can't find file or load file Error."); - exit(-1); - case LUA_ERRSYNTAX: - LOG("ERROR", lua_tostring(L, -1)); - exit(-1); - case LUA_ERRMEM: - LOG("ERROR", "Memory Allocated faild."); - exit(-1); - case LUA_ERRGCMM: - LOG("ERROR", "An Error from lua GC Machine."); - exit(-1); - } - return ; - } - - status = lua_resume(L, NULL, 0); - if (status > 1){ - LOG("ERROR", lua_tostring(L, -1)); - return lua_close(L), exit(-1); - } - if (status == LUA_YIELD) { - signal_init(); - } - /* 重启GC */ - lua_gc(L, LUA_GCRESTART, 0); -} + if (ev_userdata(CORE_LOOP)) { -void -core_sys_init(){ + /* TERM信号 显示退出 */ + core_signal_init(&sigterm, SIG_EXIT, SIGTERM); + core_signal_start(CORE_LOOP_ &sigterm); - /* hook libev 内存分配 */ - ev_set_allocator(EV_ALLOC); + /* INT信号 显示退出 */ + core_signal_init(&sigint, SIG_EXIT, SIGINT); + core_signal_start(CORE_LOOP_ &sigint); - /* hook 事件循环错误信息 */ - ev_set_syserr_cb(ERROR_CB); + /* QUIT信号 显示退出 */ + core_signal_init(&sigquit, SIG_EXIT, SIGQUIT); + core_signal_start(CORE_LOOP_ &sigquit); - /* 初始化Lua脚本 */ - init_main(); + } +} +int core_worker_run(const char entry[]) { + /* hook libev 内存分配 */ + core_ev_set_allocator(EV_ALLOC); + /* hook 事件循环错误信息 */ + core_ev_set_syserr_cb(EV_ERROR_CB); + /* 初始化事件循环对象 */ + core_loop *loop = core_loop_fork(CORE_LOOP); + + int status = 0; + + L = lua_newstate(L_ALLOC, NULL); + if (!L) + core_exit(); + + init_lua_libs(L, WORKER); + + /* 根据进程运行模式选择不同的启动方式 */ + status = getenv("cfadmin_isWorker") ? + luaL_loadbufferx(L, worker_boot, strlen(worker_boot), "=[worker.lua]", "t") : luaL_loadfile(L, entry); + if (status > 1){ + LOG("ERROR", lua_tostring(L, -1)); + lua_close(L); + core_exit(); + } + + if (getenv("cfadmin_isWorker")) { + lua_pushstring(L, entry); + } + + status = CO_RESUME(L, NULL, lua_gettop(L) - 1); + if (status > 1){ + LOG("ERROR", lua_tostring(L, -1)); + lua_close(L); + core_exit(); + } + + if (status == LUA_YIELD) + signal_init(); + + return core_start(loop, 0); } -int -core_sys_run(){ - return core_start(CORE_LOOP_ 0); +int core_master_run(pid_t *pids, int* pidcount) { + /* hook libev 内存分配 */ + core_ev_set_allocator(EV_ALLOC); + /* hook 事件循环错误信息 */ + core_ev_set_syserr_cb(EV_ERROR_CB); + /* 初始化事件循环对象 */ + core_loop *loop = core_loop_fork(CORE_LOOP); + /* 设置pid */ + ev_set_userdata(loop, pids); + /* 初始化信号 */ + signal_init(); + + L = lua_newstate(L_ALLOC, NULL); + if (!L){ + LOG("ERROR", "New Lua State failed."); + kill(0, SIGQUIT); + core_exit(); + } + + /* 加载 Lua 标准库 */ + init_lua_libs(L, MASTER); + + /* 读取文件或默认运行 */ + int status = luaL_loadbufferx(L, master_boot, strlen(master_boot), "=[master.lua]", "t"); + if (status != LUA_OK) { + lua_close(L); + kill(0, SIGQUIT); + core_exit(); + } + /* 注入子进程Pid */ + lua_createtable(L, 0, 0); + int index; + for (index = 0; index < *pidcount; index++) { + lua_pushinteger(L, pids[index]); + lua_seti(L, -2, index + 1); + } + /* 开始执行 */ + status = CO_RESUME(L, NULL, lua_gettop(L) - 1); + if (status != LUA_YIELD){ + LOG("ERROR", lua_tostring(L, -1)); + lua_close(L); + kill(0, SIGQUIT); + core_exit(); + } + /* 初始化主进程 */ + return core_start(loop, 0); } diff --git a/src/core.h b/src/core.h index cee828f1..648cbc90 100644 --- a/src/core.h +++ b/src/core.h @@ -1,67 +1,23 @@ #ifndef __CORE_H__ #define __CORE_H__ +#ifdef __cplusplus + extern "C" { +#endif + +#ifndef _GNU_SOURCE + #define _GNU_SOURCE +#endif + #include "core_sys.h" #include "core_memory.h" #include "core_ev.h" -#define CORE_LOOP core_default_loop() - -#define CORE_LOOP_ CORE_LOOP, - -#define CORE_P core_loop *loop - -#define CORE_P_ core_loop *loop, - -/* 获取用户数据 */ -#define core_get_watcher_userdata(watcher) ((watcher)->data ? (watcher)->data: NULL) - -/* 设置用户数据 */ -#define core_set_watcher_userdata(watcher, userdata) ((watcher)->data = (userdata)) - - -typedef ev_io core_io; -typedef ev_idle core_task; -typedef ev_timer core_timer; -typedef ev_signal core_signal; -typedef struct ev_loop core_loop; - -typedef void (*_IO_CB)(core_loop *loop, core_io *io, int revents); -typedef void (*_TASK_CB)(core_loop *loop, core_task *task, int revents); -typedef void (*_TIMER_CB)(core_loop *loop, core_timer *timer, int revents); - -/* =========== Timer =========== */ -void core_timer_init(core_timer *timer, _TIMER_CB cb); - -void core_timer_start(core_loop *loop, core_timer *timer, ev_tstamp timeout); - -void core_timer_stop(core_loop *loop, core_timer *timer); -/* =========== Timer =========== */ - -/* =========== IO =========== */ -void core_io_init(core_io *io, _IO_CB cb, int fd, int events); - -void core_io_start(core_loop *loop, core_io *io); - -void core_io_stop(core_loop *loop, core_io *io); -/* =========== IO =========== */ - -/* =========== TASK =========== */ -void core_task_init(core_task *task, _TASK_CB cb); - -void core_task_start(core_loop *loop, core_task *task); - -void core_task_stop(core_loop *loop, core_task *task); -/* =========== TASK =========== */ - -void core_break(core_loop *loop, int mode); - -int core_start(core_loop *loop, int mode); - -core_loop* core_default_loop(); - -void core_sys_init(); - -int core_sys_run(); +#ifdef __cplusplus + } +#endif +static inline void core_exit() { + return _exit(EXIT_FAILURE); +} #endif diff --git a/src/core_ev.c b/src/core_ev.c new file mode 100644 index 00000000..d5209227 --- /dev/null +++ b/src/core_ev.c @@ -0,0 +1,113 @@ +#include "core_ev.h" + +/* =========== Timer =========== */ +void core_timer_init(core_timer *timer, _TIMER_CB cb){ + timer->repeat = timer->at = 0x0; + ev_init(timer, cb); +} + +void core_timer_start(core_loop *loop, core_timer *timer, ev_tstamp timeout){ + timer->repeat = timeout; + ev_timer_again(loop ? loop : CORE_LOOP, timer); +} + +void core_timer_stop(core_loop *loop, core_timer *timer){ + timer->repeat = timer->at = 0x0; + ev_timer_again(loop ? loop : CORE_LOOP, timer); +} +/* =========== Timer =========== */ + + +/* =========== IO =========== */ +void core_io_init(core_io *io, _IO_CB cb, int fd, int events){ + ev_io_init(io, cb, fd, events); +} + +void core_io_start(core_loop *loop, core_io *io){ + ev_io_start(loop ? loop : CORE_LOOP, io); +} + +void core_io_stop(core_loop *loop, core_io *io){ + if (io->events || io->fd){ + ev_io_stop(loop ? loop : CORE_LOOP, io); + io->fd = io->events = -1; + } +} +/* =========== IO =========== */ + + +/* =========== TASK =========== */ +void core_task_init(core_task *task, _TASK_CB cb){ + ev_idle_init(task, cb); +} + +void core_task_start(core_loop *loop, core_task *task){ + ev_idle_start(loop ? loop : CORE_LOOP, task); +} + +void core_task_stop(core_loop *loop, core_task *task){ + ev_idle_stop(loop ? loop : CORE_LOOP, task); +} +/* =========== TASK =========== */ + +/* =========== Signal =========== */ +void core_signal_init(core_signal *signal, _SIGNAL_CB cb, int signum){ + ev_signal_init(signal, cb, signum); +} + +void core_signal_start(core_loop *loop, core_signal *signal){ + ev_signal_start(loop ? loop : CORE_LOOP, signal); +} +/* =========== Signal =========== */ + +/* =========== Child =========== */ +void core_child_init(core_child *w, _CHILD_CB cb, pid_t pid, int trace){ + ev_child_init(w, cb, pid, trace); +} + +void core_child_start(core_loop *loop, core_child *w){ + ev_child_start(loop ? loop : CORE_LOOP, w); +} + +void core_child_stop(core_loop *loop, core_child *w){ + ev_child_stop(loop ? loop : CORE_LOOP, w); +} +/* =========== Child =========== */ + + +core_loop* core_loop_fork(core_loop *loop) { + ev_loop_fork(loop); + return loop; +} + +core_loop* core_default_loop(){ + if (ev_default_loop_uc_()) + return ev_default_loop_uc_(); + int BEST_BACKEND = 0; +#if defined(__MSYS__) || defined(__CYGWIN__) + BEST_BACKEND |= EVBACKEND_SELECT | EVFLAG_SIGNALFD | EVFLAG_NOINOTIFY; +#elif defined(__APPLE__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) + BEST_BACKEND |= EVBACKEND_KQUEUE | EVFLAG_NOINOTIFY | EVFLAG_NOTIMERFD; +#elif defined(linux) || defined(__linux) || defined(__linux__) + BEST_BACKEND |= EVBACKEND_EPOLL | EVFLAG_SIGNALFD; +#else + BEST_BACKEND |= EVBACKEND_SELECT | EVFLAG_NOINOTIFY | EVFLAG_NOTIMERFD; +#endif + return ev_default_loop( BEST_BACKEND | EVFLAG_FORKCHECK ); +} + +void core_break(core_loop *loop, int mode){ + return ev_break(loop ? loop : CORE_LOOP, mode); +} + +void core_ev_set_allocator(void *(*cb)(void *ptr, long size)){ + return ev_set_allocator(cb); +} + +void core_ev_set_syserr_cb(void (*cb)(const char *msg)){ + return ev_set_syserr_cb(cb); +} + +int core_start(core_loop *loop, int mode){ + return ev_run(loop ? loop : CORE_LOOP, mode); +} diff --git a/src/core_ev.h b/src/core_ev.h index d3d8bd27..43365e1a 100644 --- a/src/core_ev.h +++ b/src/core_ev.h @@ -4,32 +4,119 @@ /* 关闭3.x版本兼容选择特性 */ #define EV_COMPAT3 0 -// #define EV_FEATURES (1 | 2 | 4 | 8 | 16 | 32 | 64) +/* 关闭事件循环验证 */ +#define EV_VERIFY 0 -// #define EV_VERIFY 3 +/* 使用Math库的Floor方法计算 */ +#define EV_USE_FLOOR 1 -#define EV_VERIFY 0 +#define EV_FORK_ENABLE 0 +#define EV_EMBED_ENABLE 0 +#define EV_CHECK_ENABLE 0 +#define EV_PREPARE_ENABLE 0 +/* 使用四叉堆结构 */ #define EV_USE_4HEAP 1 - #define EV_HEAP_CACHE_AT 1 -/* 单进程/单线程模型 */ +/* 单线程模型 */ #define EV_NO_SMP 1 #define EV_NO_THREADS 1 -/* eventfd 与 signalfd */ -#if defined(__linux) || defined(__linux__) - #define EV_USE_INOTIFY 1 +#if defined(linux) || defined(__linux__) || defined(__MSYS__) || defined(__CYGWIN__) + #if defined(__MSYS__) || defined(__CYGWIN__) + #define EV_USE_POLL 1 + // #define EV_USE_SELECT 1 + #else + #define EV_USE_EPOLL 1 + #define EV_USE_INOTIFY 1 + #define EV_USE_EVENTFD 1 + #endif #define EV_USE_SIGNALFD 1 - #define EV_USE_EVENTFD 1 - #define EV_USE_EPOLL 1 + #define EV_USE_TIMERFD 1 +#elif defined(__APPLE__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) + #define EV_USE_KQUEUE 1 +#else + #define EV_USE_SELECT 1 #endif -#if defined(__APPLE__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__) - #define EV_USE_KQUEUE 1 -#endif +#include "ev.h" + +#define CORE_LOOP core_default_loop() + +#define CORE_LOOP_ CORE_LOOP, + +#define CORE_P core_loop *loop + +#define CORE_P_ core_loop *loop, + +/* 获取用户数据 */ +#define core_get_watcher_userdata(watcher) ((watcher)->data ? (watcher)->data: NULL) + +/* 设置用户数据 */ +#define core_set_watcher_userdata(watcher, userdata) ((watcher)->data = (userdata)) + +void core_ev_set_allocator (void *(*cb)(void *ptr, long size)); + +void core_ev_set_syserr_cb (void (*cb)(const char *msg)); + +typedef ev_io core_io; +typedef ev_idle core_task; +typedef ev_timer core_timer; +typedef ev_signal core_signal; +typedef ev_child core_child; +typedef struct ev_loop core_loop; + +typedef void (*_IO_CB)(core_loop *loop, core_io *io, int revents); +typedef void (*_TASK_CB)(core_loop *loop, core_task *task, int revents); +typedef void (*_TIMER_CB)(core_loop *loop, core_timer *timer, int revents); +typedef void (*_SIGNAL_CB)(core_loop *loop, core_signal *signal, int revents); +typedef void (*_CHILD_CB)(core_loop *loop, core_child *w, int revents); + +/* =========== Timer =========== */ +void core_timer_init(core_timer *timer, _TIMER_CB cb); + +void core_timer_start(core_loop *loop, core_timer *timer, ev_tstamp timeout); -#include +void core_timer_stop(core_loop *loop, core_timer *timer); +/* =========== Timer =========== */ -#endif \ No newline at end of file +/* =========== IO =========== */ +void core_io_init(core_io *io, _IO_CB cb, int fd, int events); + +void core_io_start(core_loop *loop, core_io *io); + +void core_io_stop(core_loop *loop, core_io *io); +/* =========== IO =========== */ + +/* =========== TASK =========== */ +void core_task_init(core_task *task, _TASK_CB cb); + +void core_task_start(core_loop *loop, core_task *task); + +void core_task_stop(core_loop *loop, core_task *task); +/* =========== TASK =========== */ + +/* =========== Signal =========== */ +void core_signal_init(core_signal *signal, _SIGNAL_CB cb, int signum); + +void core_signal_start(core_loop *loop, core_signal *signal); +/* =========== Signal =========== */ + +/* =========== Child =========== */ +void core_child_init(core_child *w, _CHILD_CB, pid_t pid, int trace); + +void core_child_start(core_loop *loop, core_child *w); + +void core_child_stop(core_loop *loop, core_child *w); +/* =========== Child =========== */ + +void core_break(core_loop *loop, int mode); + +int core_start(core_loop *loop, int mode); + +core_loop* core_loop_fork(core_loop* loop); + +core_loop* core_default_loop(); + +#endif diff --git a/src/core_memory.c b/src/core_memory.c index 7273cdd3..8aa7f364 100644 --- a/src/core_memory.c +++ b/src/core_memory.c @@ -1,26 +1,74 @@ -#include "core_memory.h" - -void * -xmalloc(size_t size){ - return malloc(size); -} +#include +#include +#include +#if defined(TCMALLOC) || defined(USE_TCMALLOC) + #include + #define _malloc tc_malloc + #define _calloc tc_calloc + #define _realloc tc_realloc + #define _free tc_free +#elif defined(JEMALLOC) || defined(USE_JEMALLOC) + #include + #define _malloc je_malloc + #define _calloc je_calloc + #define _realloc je_realloc + #define _free je_free +#elif defined(MIMALLOC) || defined(USE_MIMALLOC) + #include + #define _malloc mi_malloc + #define _calloc mi_calloc + #define _realloc mi_realloc + #define _free mi_free +#else + #define _malloc malloc + #define _calloc calloc + #define _realloc realloc + #define _free free +#endif -void * -xcalloc(size_t nmemb, size_t size){ - return calloc(nmemb, size); +void* xmalloc(size_t size){ + return _malloc(size); } +void* xcalloc(size_t nmemb, size_t size){ + return _calloc(nmemb, size); +} - -void * -xrealloc(void* ptr, size_t size){ - return realloc(ptr, size); +void* xrealloc(void* ptr, size_t size){ + return _realloc(ptr, size); } +void xfree(void *ptr){ + return _free(ptr); +} +char* xstrdup(const char *s) { + if (!s) + return NULL; + size_t n = strlen(s); + char* p = _malloc(n + 1); + if (!p) + return NULL; + memcpy(p, s, n); + p[n] = '\x00'; + return p; +} +char* xstrndup(const char *s, size_t n) { + if (!s) + return NULL; + char* p = _malloc(n + 1); + if (!p) + return NULL; + memcpy(p, s, n); + p[n] = '\x00'; + return p; +} -void -xfree(void *ptr){ - return free(ptr); +char* xrealpath(const char *path, char *resolved) { + if (!path) + return NULL; + if (!resolved) + resolved = _calloc(1, sysconf(_PC_PATH_MAX)); + return realpath(path, resolved); } \ No newline at end of file diff --git a/src/core_memory.h b/src/core_memory.h index fff81959..b9cb9948 100644 --- a/src/core_memory.h +++ b/src/core_memory.h @@ -1,25 +1,42 @@ #ifndef __CORE_MEMORY__ #define __CORE_MEMORY__ -#if JEMALLOC - #include "jemalloc/jemalloc.h" -#elif TCMALLOC - #include "gperftools/tcmalloc.h" - #define malloc tc_malloc - #define calloc tc_calloc - #define realloc tc_realloc - #define free tc_free -#else - #include -#endif +#include + +#define malloc xmalloc +#define calloc xcalloc +#define realloc xrealloc +#define free xfree +#define xmalloc xmalloc +#define xcalloc xcalloc +#define xrealloc xrealloc +#define xfree xfree -void *xmalloc (size_t size); +#define strdup xstrdup +#define strndup xstrndup +#define realpath xrealpath -void *xcalloc (size_t nmemb, size_t size); +char* xstrdup(const char *s); +char* xstrndup(const char *s, size_t n); +char* xrealpath(const char *path, char *resolved); -void *xrealloc(void* ptr, size_t size); +void* xmalloc(size_t size); +void* xcalloc(size_t nmemb, size_t size); +void* xrealloc(void* ptr, size_t size); +void xfree(void *ptr); -void xfree(void *ptr); +#ifdef __cplusplus + #if defined(__llvm__) || defined(__clang__) + #pragma GCC diagnostic ignored "-Winline-new-delete" + #endif + #include + /* malloc */ + inline void* operator new(std::size_t size) noexcept(false) { return xmalloc(size); } + inline void* operator new[](std::size_t size) noexcept(false) { return xmalloc(size); } + /* free */ + inline void operator delete(void *ptr) noexcept { xfree(ptr); } + inline void operator delete[](void *ptr) noexcept { xfree(ptr); } +#endif -#endif \ No newline at end of file +#endif diff --git a/src/core_start.c b/src/core_start.c index d24249d5..ecbc2f80 100644 --- a/src/core_start.c +++ b/src/core_start.c @@ -1,16 +1,349 @@ #include "core.h" -int -main(int argc, char const *argv[]) -{ -// #if !defined(__APPLE__) - /* 后台运行 */ - if (argc > 1 && 0 == strcmp("-d", argv[argc-1])) daemon(1, 0); -// #endif - /* 系统初始化 */ - core_sys_init(); - /* 运行事件循环 */ - core_sys_run(); - - return 0; +int core_worker_run(const char entry[]); + +int core_master_run(pid_t *pids, int* pidcount); + +enum { + isMaster = 1, + isWorker = 2, +}; + +#define __CFADMIN_VERSION__ "1.0" + +#define MAX_FILENAME_LEN (1 << 10) + +#define exe_filename cfadmin_get_exefile() + +static char script_entry[MAX_FILENAME_LEN] = "script/main.lua"; + +static char pid_filename[MAX_FILENAME_LEN] = "cfadmin.pid"; + +static int nprocess = 1; + +static int daemoned = 0; + +// static int pmode = 0; + +static int nostd = 1; + +#define MasterPrefix ("cfadmin - Manager Process :") +#define WorkerPrefix ("cfadmin - Worker Process") + +const char* cfadmin_get_exefile() { + const char *filename = getenv("cfadmin_exefile"); + if (!filename) + filename = "./cfadmin"; + return filename; +} + +void cfadmin_set_exefile(const char *filename) { + setenv("cfadmin_exefile", filename, 1); +} + +/* 打印使用指南 */ +static inline void cfadmin_usage_print() { + printf("cfadmin System : %s(%s)\n", __OS__, __VERSION__ ); + printf("\n"); + printf("cfadmin Version : %s\n", __CFADMIN_VERSION__ ); + printf("\n"); + printf( + "cfadmin Usage: %s [options]\n" \ + "\n" \ + " -h \"Print `cfadmin` usage.\"\n" \ + "\n" \ + " -d \"Make `cfadmin` run in daemon mode.\"\n" \ + "\n" \ + " -e \"Specified `lua` entry file name.\"\n" \ + "\n" \ + " -p \"Specified the process `Pid` write file name.\"\n" \ + "\n" \ + " -k \"Send `SIGKILL` signal to `Pid` or `Pid File`.\"\n" \ + "\n" \ + " -w \"Spawn specified number of worker processes.\"\n" \ + "\n" \ + , exe_filename); + exit(0); +} + +/* 指定入口文件路径 */ +static inline void cfadmin_specify_entry_file(const char *filename) { + memset(script_entry, 0x0, MAX_FILENAME_LEN); + memmove(script_entry, filename, strlen(filename)); +} + +/* 指定pid文件路径 */ +static inline void cfadmin_specify_pid_file(const char *filename) { + memset(pid_filename, 0x0, MAX_FILENAME_LEN); + memmove(pid_filename, filename, strlen(filename)); +} + +/* 给指定`PID`或包含`PID`的文件发送`SIGQUIT`信号 */ +static inline void cfadmin_specify_kill_process(const char *spid) { + int pid = atoi(spid); + if (pid <= 1) { + FILE *fp = NULL; + if ((fp = fopen(spid, "rb")) == NULL) { + LOG("ERROR", "Invalid Pid or pid file name."); + return exit(0); + } + char pbuf[20]; memset(pbuf, 0x00, 20); + if (fread(pbuf, 1, 20, fp) <= 0 || (pid = atoi(pbuf)) <= 1) { + LOG("ERROR", "Invalid Pid or File name."); + fclose(fp); + return exit(0); + } + fclose(fp); + remove(pid_filename); + } + kill(pid, SIGQUIT); + exit(0); +} + +/* 指定子进程数量 */ +static inline void cfadmin_specify_nprocess(const char* w) { + nprocess = atoi(w); + /* 检查核心数量是否有效 */ + if (nprocess <= 0 || nprocess > 255 ) + nprocess = 1; + /* 自动检查可用核心数量 */ + if (!strcmp(w, "auto")) + nprocess = sysconf(_SC_NPROCESSORS_ONLN) < 2 ? 2 : sysconf(_SC_NPROCESSORS_ONLN); +} + +/* 后台运行 */ +static inline void cfadmin_specify_process_daemon() { + daemoned = 1; +} + +static inline void cfadmin_init_args(int argc, char const *argv[]) { + + int opt = -1; + // int opterr = 0; + while ((opt = getopt(argc, (char *const *)argv, "hde:p:k:w:")) != -1) { + switch(opt) { + case 'd': + cfadmin_specify_process_daemon(); + continue; + case 'w': + cfadmin_specify_nprocess(optarg); + continue; + case 'e': + cfadmin_specify_entry_file(optarg); + continue; + case 'p': + cfadmin_specify_pid_file(optarg); + continue; + case 'k': + cfadmin_specify_kill_process(optarg); + case '?': + case 'h': + default : + cfadmin_usage_print(); + } + } + return; +} + +/* 将主进程ID写入到文件内 */ +static inline void cfadmin_write_pid_file(const char *filename, pid_t pid) { + errno = 0; + FILE *f = fopen(filename, "w"); + if (!f) { + LOG("ERROR", strerror(errno)); + return exit(-1); + } + fprintf(f, "%d", pid); + fflush(f); + fclose(f); +} + +/* 设置当前进程运行模式 */ +static inline void cfadmin_set_parameters(int mode) { + if (mode == isMaster){ + unsetenv("cfadmin_isWorker"); + setenv("cfadmin_isMaster", "true", 1); + setenv("cfadmin_script", script_entry, 1); + char np[3]; + memset(np, 0x0, 3); + sprintf(np, "%d", nprocess); + setenv("cfadmin_nprocess", np, 1); + } else { + unsetenv("cfadmin_isMaster"); + setenv("cfadmin_isWorker", "true", 1); + } +} + +/* 设置不同系统下的CPU亲缘性 */ +static inline void cfadmin_set_cpu_affinity(int num, pid_t pid) { +#if defined(__linux__) + #include + if (nprocess <= sysconf(_SC_NPROCESSORS_ONLN)){ + /* 在Linux会尝试绑定CPU亲缘性以提高进程执行效率. */ + cpu_set_t mask; CPU_ZERO(&mask); CPU_SET((num + 1) % sysconf(_SC_NPROCESSORS_ONLN), &mask); + sched_setaffinity(pid, sizeof(mask), (const cpu_set_t *)&mask); + } +#elif defined(__FreeBSD__) + #include + #include + if (nprocess <= sysconf(_SC_NPROCESSORS_ONLN)){ + /* FreeBSD的CPU绑定的方式有函数与头文件的差异 */ + cpuset_t mask; CPU_ZERO(&mask); CPU_SET((num + 1) % sysconf(_SC_NPROCESSORS_ONLN), &mask); + cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID, -1, sizeof(cpuset_t), (const cpuset_t *)&mask); + } +#else + (void)num;(void)pid; +#endif +} + +/* 多进程 - 运行工作进程 */ +static inline int cfadmin_worker_run(const char* entry) { + return core_worker_run(entry); +} + +/* 多进程 - 运行主进程 */ +static inline pid_t cfadmin_master_run() { + // /* 获取父进程ID */ + // pid_t ppid = getpid(); + /* 所有子进程的PID数组 */ + pid_t npid[nprocess]; + /* 初始化工作进程命令行参数 */ + char *argp[] = { WorkerPrefix, NULL }; + /* 子进程模式 */ + cfadmin_set_parameters(isWorker); + /* 开始创建进程 */ + int i; + for (i = 0; i < nprocess; i++){ + int pid = fork(); + if (pid <= 0) { + if (!pid) { + /* 启动工作进程 */ + cfadmin_set_cpu_affinity(i, getpid()); + int e = execvp(exe_filename, (char *const *)argp); + if (e < 0) + LOG("ERROR", strerror(errno)); + } + kill(0, SIGQUIT); + return (pid_t)-1; + } + npid[i] = pid; + } + return core_master_run(npid, &nprocess); +} + +/* 单进程模式 */ +static inline int cfadmin_standalone_run(const char* entry) { + return cfadmin_worker_run(entry); +} + +/* 设置为daemon进程, 并指定`entry`文件与`Pid`文件路径. */ +static inline pid_t cfadmin_daemon(int nostd) { + + pid_t pid = fork(); + if (pid != 0) + exit(EXIT_SUCCESS); + + /* 获取`Pid` */ + pid = getpid(); + + /* 设置`SID` */ + setsid(); + + /* 打开空设备 */ + int stdin_fd, stdout_fd; + + /* 关闭标准输入输出 */ + if (!nostd) { + int nfd = open("/dev/null", O_RDWR); + if (nfd < 0) { + LOG("ERROR", strerror(errno)); + exit(-1); + } + stdin_fd = stdout_fd = nfd; + } else { + int rfd = open("/dev/null", O_RDWR); + if (rfd < 0) { + LOG("ERROR", strerror(errno)); + exit(-1); + } + int wfd = open("cfadmin_stdout.log", O_CREAT | O_WRONLY | O_APPEND, 0644); + if (wfd < 0) { + LOG("ERROR", strerror(errno)); + exit(-1); + } + stdin_fd = rfd; + stdout_fd = wfd; + } + + /* 关闭资源 */ + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + /* 重定向输入/输出 */ + (void)dup2(stdin_fd, STDIN_FILENO); + (void)dup2(stdout_fd, STDOUT_FILENO); + (void)dup2(stdout_fd, STDERR_FILENO); + + return pid; + +} + +int main(int argc, char const *argv[]) { + + /* 命令行参数初始化 */ + cfadmin_init_args(argc, argv); + +#if defined(__MSYS__) || defined(__CYGWIN__) + /* Windows下不可使用多进程 */ + nprocess = 1; +#else + int n = -1; + if (getenv("cfadmin_nprocess")) + n = atoi(getenv("cfadmin_nprocess")); + if (n < 255 && n > 0) + nprocess = n; +#endif + + /* 工作进程执行代码 */ + if(getenv("cfadmin_isWorker") && getenv("cfadmin_script")) + return cfadmin_worker_run(getenv("cfadmin_script")); + + /* 主进程执行代码 */ + if (getenv("cfadmin_isMaster")) + return cfadmin_master_run(); + + /* + 注意: + 下面为`cfadmin`检查配置的相关代码; 根据配置检查的实际情况决定是否需要使用多进程 + 多进程模型目前仅在Linux下可以利用到多核`Accept`, 其他环境仅用多进程来做为`Worker`使用. + */ + + cfadmin_set_exefile(argv[0]); + + /* 是否需要后台运行 */ + pid_t p = daemoned ? cfadmin_daemon(nostd) : getpid() ; + + /*将主进程的PID写入到*/ + cfadmin_write_pid_file(pid_filename, p); + + /* 如果是单进程模型, 就无需继续创建父子进程管理. */ + if (nprocess <= 1) + return cfadmin_standalone_run(script_entry); + + /* 初始化命令行参数 */ + argv[0] = MasterPrefix; + + /* 设置环境变量 */ + cfadmin_set_parameters(isMaster); + + /* 执行代码*/ + int e = execvp(exe_filename, (char *const *)argv); + if (e < 0) { + LOG("ERROR", strerror(errno)); + exit(-1); + } + + /* 如果失败就删除pid文件*/ + return remove(pid_filename); + } diff --git a/src/core_sys.c b/src/core_sys.c index a31ae86f..6ea65538 100644 --- a/src/core_sys.c +++ b/src/core_sys.c @@ -1,24 +1,29 @@ #include "core_sys.h" -double -now(){ - struct timeval now; - gettimeofday(&now, NULL); - return (double)((double)now.tv_sec + (double)now.tv_usec / 1000000); + /* 此方法提供一个精确到微秒级的时间戳 */ +double now(void){ + struct timespec now = {}; + clock_gettime(CLOCK_REALTIME, &now); + return now.tv_sec + now.tv_nsec * 1e-9; } -int /* 此方法可用于检查是否为有效ipv4地址*/ -ipv4(const char *IP){ +/* 此方法可用于检查是否为有效ipv4地址*/ +int ipv4(const char *IP){ if (!IP) return 0; - struct in_addr addr; - if (inet_pton(AF_INET, IP, &addr) == 1) return 1; - return 0; + struct in_addr addr = {}; + if (inet_pton(AF_INET, IP, &addr) == 1) return 1; + return 0; } -int /* 此方法可用于检查是否为有效ipv6地址*/ -ipv6(const char *IP){ +/* 此方法可用于检查是否为有效ipv6地址*/ +int ipv6(const char *IP){ if (!IP) return 0; - struct in6_addr addr; - if (inet_pton(AF_INET6, IP, &addr) == 1) return 1; - return 0; + struct in6_addr addr = {}; + if (inet_pton(AF_INET6, IP, &addr) == 1) return 1; + return 0; +} + +/* 返回当前操作系统类型 */ +const char* os(void) { + return __OS__; } \ No newline at end of file diff --git a/src/core_sys.h b/src/core_sys.h index 3521d3af..fb2e2c32 100644 --- a/src/core_sys.h +++ b/src/core_sys.h @@ -14,9 +14,11 @@ #include #include +#include #include #include #include +#include #include #include #include @@ -26,25 +28,58 @@ #include #include +#if defined(__MSYS__) || defined(__CYGWIN__) + #define __OS__ ("Windows") +#elif defined(__APPLE__) + #define __OS__ ("Apple") +#elif defined(linux) || defined(__linux) || defined(__linux__) + #define __OS__ ("Linux") +#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) + #define __OS__ ("BSD") +#else + #define __OS__ ("Unix") +#endif + +#if LUA_VERSION_NUM >= 504 + #ifndef CO_GCRESET + #define CO_GCRESET(L) lua_gc(L, LUA_GCGEN, NULL, NULL); + #endif + #ifndef CO_RESUME + #define CO_RESUME(L, from, nargs) ({int nout = 0; lua_resume(L, from, nargs, &nout);}) + #endif +#else + #ifndef CO_GCRESET + #define CO_GCRESET(L) + #endif + #ifndef CO_RESUME + #define CO_RESUME(L, from, nargs) lua_resume(L, from, nargs) + #endif +#endif + #ifndef EWOULDBLOCK #define EWOULDBLOCK EAGAIN #endif -#define non_blocking(socket) (fcntl(socket, F_SETFL, fcntl(socket, F_GETFL, 0) | O_NONBLOCK)); +// 设置子进程自动关闭进程fd. +#define non_exec(socket) {fcntl(socket, F_SETFD, fcntl(socket, F_GETFL, 0) | FD_CLOEXEC);} + +// 设置非阻塞模式 +#define non_blocking(socket) {non_exec(socket); fcntl(socket, F_SETFL, fcntl(socket, F_GETFL, 0) | O_NONBLOCK);} + +// 设置nodelay模式 +#define non_delay(socket) ({int Enable = 1; setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, &Enable, sizeof(Enable));}) /* [datetime][level][file][function][line][具体打印内容] */ -#define LOG(log_level, content) { \ - time_t t; struct tm* lt; \ - /*获取Unix时间戳、转为时间结构。*/ \ - time(&t); lt = localtime(&t); \ - fprintf(stdout, "[%04d/%02d/%02d][%02d:%02d:%02d][%s][%s][%s:%d] : %s\n", \ - lt->tm_year+1900, 1+lt->tm_mon, lt->tm_mday, lt->tm_hour, lt->tm_min, lt->tm_sec, \ - log_level, \ - __FILE__, __FUNCTION__, __LINE__, \ - content);} - -/* 微秒级double时间戳 */ -double now(); +#define LOG(LEVEL, CONTENT) { \ + time_t t = time(NULL); struct tm* lt = localtime(&t); \ + fprintf(stdout, "[%04d/%02d/%02d][%02d:%02d:%02d][%s][%s][%s:%d] : %s\n", \ + lt->tm_year + 1900, 1 + lt->tm_mon, lt->tm_mday, lt->tm_hour, lt->tm_min, lt->tm_sec, \ + LEVEL, __FILE__, __FUNCTION__, __LINE__, CONTENT); \ + fflush(stdout); \ +} + +/* 微秒级时间戳函数 */ +double now(void); /* 检查是否为有效ipv4地址 */ int ipv4(const char *IP); @@ -52,4 +87,7 @@ int ipv4(const char *IP); /* 检查是否为有效ipv6地址 */ int ipv6(const char *IP); -#endif \ No newline at end of file +/* 返回当前操作系统类型 */ +const char* os(void); + +#endif diff --git a/static/css/font.css b/static/css/font.css new file mode 100755 index 00000000..b83e5b4b --- /dev/null +++ b/static/css/font.css @@ -0,0 +1,16 @@ +@font-face { + font-family: 'iconfont'; + src: url('../fonts/iconfont.eot'); + src: url('../fonts/iconfont.eot?#iefix') format('embedded-opentype'), + url('../fonts/iconfont.woff') format('woff'), + url('../fonts/iconfont.ttf') format('truetype'), + url('../fonts/iconfont.svg#iconfont') format('svg'); +} +.iconfont{ + font-family:"iconfont" !important; + font-size:16px;font-style:normal; + -webkit-font-smoothing: antialiased; + -webkit-text-stroke-width: 0.2px; + -moz-osx-font-smoothing: grayscale; +} + diff --git a/static/css/login.css b/static/css/login.css new file mode 100755 index 00000000..07d94773 --- /dev/null +++ b/static/css/login.css @@ -0,0 +1,98 @@ +/* +* @Author: xuebingsi +* @Date: 2019-04-01 13:37:17 +* @Last Modified by: zhibinm +* @Last Modified time: 2019-04-01 13:37:19 +*/ +.login-bg{ + /*background: #eeeeee url() 0 0 no-repeat;*/ + background:url(../images/bg.png) no-repeat center; + background-size: cover; + overflow: hidden; + display: flex; + align-items: center; + justify-items: center; +} +.login{ + min-height: 420px; + max-width: 420px; + padding: 40px; + background-color: #ffffff; + border-radius: 4px; + /* overflow-x: hidden; */ + box-sizing: border-box; +} +.login a.logo{ + display: block; + height: 58px; + width: 167px; + margin: 0 auto 30px auto; + background-size: 167px 42px; +} +.login .message { + margin: 10px 0 0 -58px; + padding: 18px 10px 18px 60px; + background: #189F92; + position: relative; + color: #fff; + font-size: 16px; +} + +.login input[type=text], +.login input[type=file], +.login input[type=password], +.login input[type=email], select { + border: 1px solid #DCDEE0; + vertical-align: middle; + border-radius: 3px; + height: 50px; + padding: 0px 16px; + font-size: 14px; + color: #555555; + outline:none; + width:100%; + box-sizing: border-box; +} +.login input[type=text]:focus, +.login input[type=file]:focus, +.login input[type=password]:focus, +.login input[type=email]:focus, select:focus { + border: 1px solid #27A9E3; +} +.login input[type=submit], +.login input[type=button]{ + display: inline-block; + vertical-align: middle; + padding: 12px 24px; + margin: 0px; + font-size: 18px; + line-height: 24px; + text-align: center; + white-space: nowrap; + vertical-align: middle; + cursor: pointer; + color: #ffffff; + background-color: #189F92; + border-radius: 3px; + border: none; + -webkit-appearance: none; + outline:none; + width:100%; +} +.login hr { + background: #fff url() 0 0 no-repeat; +} +.login hr.hr15 { + height: 15px; + border: none; + margin: 0px; + padding: 0px; + width: 100%; +} +.login hr.hr20 { + height: 20px; + border: none; + margin: 0px; + padding: 0px; + width: 100%; +} diff --git a/static/css/print.min.css b/static/css/print.min.css new file mode 100644 index 00000000..78412d78 --- /dev/null +++ b/static/css/print.min.css @@ -0,0 +1 @@ +.printModal{font-family:sans-serif;display:flex;text-align:center;font-weight:300;font-size:30px;left:0;top:0;position:absolute;color:#0460b5;width:100%;height:100%;background-color:hsla(0,0%,100%,.91)}.printClose{position:absolute;right:10px;top:10px}.printClose:before{content:"\00D7";font-family:Helvetica Neue,sans-serif;font-weight:100;line-height:1px;padding-top:.5em;display:block;font-size:2em;text-indent:1px;overflow:hidden;height:1.25em;width:1.25em;text-align:center;cursor:pointer} \ No newline at end of file diff --git a/static/css/theme1.css b/static/css/theme1.css new file mode 100755 index 00000000..79b2dc8e --- /dev/null +++ b/static/css/theme1.css @@ -0,0 +1,21 @@ +body{ + background:#F2F1F2; +} +.container{ + background:#1A1B20; +} +.left-nav{ + background:#1A1B20; +} + +.left-nav a{ + color:rgba(255,255,255,.7); +} +..left-nav a.active{ + background: #009688 !important; + color: #fff; +} +.left-nav a:hover{ + background: #009688 !important; + color: #fff; +} \ No newline at end of file diff --git a/static/css/theme2.css b/static/css/theme2.css new file mode 100755 index 00000000..04e91717 --- /dev/null +++ b/static/css/theme2.css @@ -0,0 +1,21 @@ +body{ + background:#EEF5F9; +} +.container{ + background:#323640; +} +.left-nav{ + background:#fff; +} + +.left-nav a{ + color:#686a76; +} +.left-nav a.active{ + background: #786AED !important; + color: #fff; +} +.left-nav a:hover{ + background: #786AED !important; + color: #fff; +} diff --git a/static/css/theme3.css b/static/css/theme3.css new file mode 100755 index 00000000..d5919c57 --- /dev/null +++ b/static/css/theme3.css @@ -0,0 +1,22 @@ +body{ + background:#E8E8E8; +} +.container{ + background:#F34743; +} + +.left-nav{ + background:#F4F4F4; +} + +.left-nav a{ + color:#686a76; +} +.left-nav a.active{ + background: #FEFEFE !important; + color: #F34743; +} +.left-nav a:hover{ + background: #FEFEFE !important; + color: #F34743; +} \ No newline at end of file diff --git a/static/css/theme4.css b/static/css/theme4.css new file mode 100755 index 00000000..0a0ff61d --- /dev/null +++ b/static/css/theme4.css @@ -0,0 +1,21 @@ +body{ + background:#E4E4E4; +} +.container{ + background:#019587; +} +.left-nav{ + background:#263035; +} + +.left-nav a{ + color:#fff; +} +.left-nav a.active{ + background: #212525 !important; + color: #fff !important; +} +.left-nav a:hover{ + background: #212525 !important; + color: #fff !important; +} \ No newline at end of file diff --git a/static/css/theme5.css b/static/css/theme5.css new file mode 100755 index 00000000..92c9a06d --- /dev/null +++ b/static/css/theme5.css @@ -0,0 +1,27 @@ +body{ + background:#EEF5F9 !important; +} +.container{ + background:linear-gradient(to left, #7b4397, #2196f3); +} + +.left-nav{ + background:#fff !important; +} + +.left-nav a{ + color:#686a76 !important; +} +.left-nav a.active{ + background: linear-gradient(to left, #7c8ce4, #2196f3) !important; + color: #fff !important; + border-color: #7b4397 !important; +} +.left-nav a:hover{ + background: linear-gradient(to left, #7c8ce4, #2196f3) !important; + color: #fff !important; + border-color: #7b4397 !important; +} +.container .logo a{ + background: rgba(0,0,0,0) !important; +} \ No newline at end of file diff --git a/static/css/xadmin.css b/static/css/xadmin.css new file mode 100755 index 00000000..2a4d4b4b --- /dev/null +++ b/static/css/xadmin.css @@ -0,0 +1,530 @@ +@charset "utf-8"; +@import url(../lib/layui/css/layui.css); +*{ + margin: 0px; + padding: 0px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; +} +a{ + text-decoration: none; +} +html{ + width: 100%; + height: 100%; + overflow-x:hidden; + overflow-y:auto; +} +body{ + width: 100%; + min-height: 100%; + background: #f1f1f1; + /*background: #fff;*/ +} +.x-red{ + color: red; +} + +.layui-form-switch{ + margin-top: 0px; +} +.layui-input:focus, .layui-textarea:focus { + border-color: #189f92!important; +} + +.layui-fluid{ + padding:15px; +} +.x-nav{ + padding: 0 20px; + position: relative; + z-index: 99; + border-bottom: 1px solid #e5e5e5; + line-height: 39px; + height: 39px; + overflow: hidden; + background: #fff; +} +.page{ + text-align: center; + +} +.page a{ + display: inline-block; + background: #fff; + color: #888; + padding: 5px; + min-width: 15px; + border: 1px solid #E2E2E2; + +} +.page span{ + display: inline-block; + padding: 5px; + min-width: 15px; + border: 1px solid #E2E2E2; +} +.page span.current{ + display: inline-block; + background: #009688; + color: #fff; + padding: 5px; + min-width: 15px; + border: 1px solid #009688; +} +.page .pagination li{ + display: inline-block; + margin-right: 5px; + text-align: center; +} +.page .pagination li.active span{ + background: #009688; + color: #fff; + border: 1px solid #009688; + +} + +/*登录样式*/ +/*头部*/ +.container{ + width: 100%; + height: 45px; + background-color: #222; +} +.container a,.layui-nav .layui-nav-item a{ + color: #fff; +} +.container .logo a{ + background-color: rgba(0,0,0,0); +} +.container .logo a{ + float: left; + font-size: 18px; + padding-left: 20px; + line-height: 45px; + width: 200px; +} +.container .right{ + background-color:rgba(0,0,0,0); + float: right; + +} +.container .left_open{ + height: 45px; + float: left; + margin-left: 10px; +} +.container .left_open i{ + display: block; + background: rgba(255,255,255,0.1); + width: 32px; + height: 32px; + line-height: 32px; + border-radius: 3px; + text-align: center; + margin-top: 7px; + cursor: pointer; +} +.container .left_open i:hover{ + background: rgba(255,255,255,0.3); +} + +.container .left{ + background-color:rgba(0,0,0,0); + float: left; + +} +.container .layui-nav-item{ + line-height: 45px; +} +.container .layui-nav-more{ + top: 20px; +} +.container .layui-nav-child{ + top: 50px; +} +.container .layui-nav-child i{ + margin-right: 10px; +} +.layui-nav .layui-nav-item a{ + cursor: pointer; +} +.layui-nav .layui-nav-child a{ + color: #333; + cursor: pointer; +} +.left-nav{ + position: absolute; + top: 45px; + bottom: 0px; + /*bottom: 42px;*/ + left: 0; + z-index: 2; + padding-top: 10px; + background-color: #EEEEEE; + width: 220px; + max-width: 220px; + overflow: auto; + overflow-x:hidden; + overflow: hidden; + + /*width: 0px;*/ +} +#side-nav{ + width: 220px; +} + +.left-nav #nav li:hover > a{ + /*color: blue;*/ +} +.left-nav #nav .current{ + background-color: rgba(0, 0, 0, 0.3); +} +.left-nav #nav li a{ + font-size: 14px; + padding: 10px 15px 10px 15px; + display: block; + cursor: pointer; + border-left: 4px solid transparent; + transition: all 0.3s; +} +.left-nav a:hover{ + background: #009688 !important; + color: #fff; + border-color: #04564e !important; +} +.left-nav a.active{ + background: #009688 !important; + color: #fff; + border-color: #04564e !important; +} +.left-nav #nav li a cite{ + font-size: 14px; +} + +.left-nav #nav li .sub-menu{ + display: none; +} +.left-nav #nav li .opened{ + display: block; +} +.left-nav #nav li .opened:hover{ + /*background: #fff ;*/ +} +.left-nav #nav li .opened .current{ + +} +.left-nav #nav li .sub-menu li:hover{ + /*color: blue;*/ + /*background: #fff ;*/ +} +.left-nav #nav li .sub-menu li a{ + padding: 12px 15px 12px 30px; + font-size: 14px; + cursor: pointer; +} +.left-nav #nav li .sub-menu li .sub-menu li a{ + padding-left: 45px; +} +/*.left-nav #nav li .sub-menu li a:hover{ + color: #148cf1; +}*/ +.left-nav #nav li .sub-menu li a i{ + font-size: 12px; +} +.left-nav #nav li a i{ + padding-right: 10px; + line-height: 14px; +} +.left-nav #nav li .nav_right{ + float: right; + font-size: 16px; +} +.x-slide_left { + width: 17px; + height: 61px; + background: url(../images/icon.png) 0 0 no-repeat; + position: absolute; + top: 200px; + left: 220px; + cursor: pointer; + z-index: 3; +} +.page-content{ + position: absolute; + top: 45px; + right: 0; + /*bottom: 42px;*/ + bottom: 0px; + left: 220px; + overflow: hidden; + z-index: 1; +} +.page-content-bg{ + position: absolute; + top: 45px; + right: 0; + /*bottom: 42px;*/ + bottom: 0px; + left: 220px; + background: rgba(0,0,0,0.5); + overflow: hidden; + z-index: 100; + display: none; +} + +.page-content .tab{ + height: 100%; + width: 100%; + /*background: #EFEEF0;*/ + margin: 0px; +} +.page-content .layui-tab-title{ + /*padding-top: 5px;*/ + height: 35px; + background: #EFEEF0 ; + position: relative; + z-index: 100; +} +.page-content .layui-tab-title li.home i{ + padding-right: 5px; +} +.page-content .layui-tab-title li.home .layui-tab-close{ + display: none; +} +.page-content .layui-tab-title li{ + line-height: 35px; +} +.page-content .layui-tab-title .layui-this:after{ + height: 36px; +} +.page-content .layui-tab-title li .layui-tab-close{ + border-radius: 50%; +} +.page-content .layui-tab-title .layui-this{ + background: #fff ; +} +.page-content .layui-tab-bar{ + height:34px; + line-height: 35px; +} +.page-content .layui-tab-content{ + position: absolute; + top: 36px; + bottom: 0px; + width: 100%; + padding: 0px; + overflow: hidden; +} +.page-content .layui-tab-content .layui-tab-item{ + width: 100%; + height: 100%; + +} +.page-content .layui-tab-content .layui-tab-item iframe{ + width: 100%; + height: 100%; + +} +.x-admin-carousel,.layui-carousel,.x-admin-carousel>[carousel-item]>* { + background-color:#fff +} + +.x-admin-backlog .x-admin-backlog-body { + display:block; + padding:10px 15px; + background-color:#f8f8f8; + color:#999; + border-radius:2px; + transition:all .3s; + -webkit-transition:all .3s +} +.x-admin-backlog-body h3 { + padding-bottom:10px; + font-size:12px +} +.x-admin-backlog-body p cite { + font-style:normal; + font-size:30px; + font-weight:300; + color:#009688 +} +.x-admin-backlog-body:hover { + background-color:#CFCFCF; + color:#888 +} + +table th, table td { + word-break: break-all; +} + +/*404页面样式*/ +.fly-panel { + margin-bottom: 15px; + border-radius: 2px; + /*background-color: #fff;*/ + box-shadow: 0 1px 2px 0 rgba(0,0,0,.05); +} +.fly-none { + min-height: 600px; + text-align: center; + padding-top: 50px; + color: #999; +} +.fly-none .layui-icon { + line-height: 300px; + font-size: 300px; + color: #393D49; +} +.fly-none p { + margin-top: 50px; + padding: 0 15px; + font-size: 20px; + color: #999; + font-weight: 300; +} +#tab_right{ + display: none; + width: 80px; + position: absolute; + top: 35px; + left: 0px; +} +#tab_right dl{ + top: 0px; +} +#tab_show{ + position: absolute; + top: 36px; + bottom: 0px; + width: 100%; + background:rgb(255, 255, 255,0); + padding: 0px; + overflow: hidden; + display: none; +} + + +@media screen and (max-width: 768px){ + .fast-add{ + display: none; + } + .layui-nav .to-index{ + display: none; + } + .container .logo a{ + width: 140px; + } + .container .left_open { + /*float: right;*/ + } + .left-nav{ + width: 60px; + } + .left-nav #nav li a i{ + font-size: 18px; + } + .left-nav cite,.left-nav .nav_right{ + display: none; + } + .page-content{ + left: 60px; + } + .page-content .layui-tab-content .layui-tab-item{ + -webkit-overflow-scrolling: touch; + overflow-y: scroll; + } + .x-so input.layui-input{ + width: 100%; + margin: 10px; + } +} + +/*精细版样式*/ + +.x-admin-sm{ + font-size: 12px; +} +.x-admin-sm body{ + font-size: 12px; +} +/*登录页面样式*/ +.x-admin-sm .login input[type=submit],.x-admin-sm .login input[type=button]{ + font-size: 14px; +} +.x-admin-sm .login input[type=text], +.x-admin-sm .login input[type=file], +.x-admin-sm .login input[type=password], +.x-admin-sm .login input[type=email], .x-admin-sm select { + font-size: 12px; +} +.x-admin-sm .login .message{ + font-size: 14px; +} + +.x-admin-sm .layui-table td, .x-admin-sm .layui-table th{ + font-size: 12px; +} +.x-admin-sm .layui-elem-field legend{ + font-size: 18px; +} + +.x-admin-sm .x-admin-backlog-body p cite{ + font-size: 24px; +} +.x-admin-sm .left-nav #nav li a cite{ + font-size: 12px; +} +.x-admin-sm .iconfont{ + font-size: 14px; +} +.x-admin-sm .layui-tab-title li{ + font-size: 12px; +} +.x-admin-sm .layui-icon{ + font-size: 14px; +} +.x-admin-sm .layui-nav *{ + font-size: 12px; +} +.x-admin-sm .layui-breadcrumb>*{ + font-size: 12px; +} +.x-admin-sm .layui-btn,.x-admin-sm .layui-btn-xs,.x-admin-sm .layui-btn-sm{ + font-size: 12px; +} + +.x-admin-sm .layui-laydate{ + font-size: 12px; +} +.x-admin-sm .layui-btn{ + height: 30px; + line-height: 30px; + padding: 0 10px; +} + +.x-admin-sm .layui-btn-lg{ + height: 38px; + line-height: 38px; + padding: 0 18px; + font-size: 14px; +} +.x-admin-sm .layui-layer-title,.x-admin-sm .layui-layer-dialog .layui-layer-content{ + font-size: 12px; +} +.x-admin-sm .layui-input,.x-admin-sm .layui-select,.x-admin-sm .layui-textarea{ + height: 30px; +} + +.x-admin-sm .layui-form-pane .layui-form-label{ + height: 30px; + line-height: 14px; +} +.x-admin-sm .layui-form-checkbox span{ + font-size: 12px; +} +.x-admin-sm .fly-none .layui-icon { + line-height: 300px; + font-size: 300px; + color: #393D49; +} + diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 00000000..f1b312cd Binary files /dev/null and b/static/favicon.ico differ diff --git a/static/fonts/iconfont.eot b/static/fonts/iconfont.eot new file mode 100755 index 00000000..df5334af Binary files /dev/null and b/static/fonts/iconfont.eot differ diff --git a/static/fonts/iconfont.svg b/static/fonts/iconfont.svg new file mode 100755 index 00000000..f6f082eb --- /dev/null +++ b/static/fonts/iconfont.svg @@ -0,0 +1,477 @@ + + + + + +Created by iconfont + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/fonts/iconfont.ttf b/static/fonts/iconfont.ttf new file mode 100755 index 00000000..ccf533ec Binary files /dev/null and b/static/fonts/iconfont.ttf differ diff --git a/static/fonts/iconfont.woff b/static/fonts/iconfont.woff new file mode 100755 index 00000000..d06756ca Binary files /dev/null and b/static/fonts/iconfont.woff differ diff --git a/static/images/bg.png b/static/images/bg.png new file mode 100755 index 00000000..c518e9d6 Binary files /dev/null and b/static/images/bg.png differ diff --git a/static/images/logo.png b/static/images/logo.png new file mode 100644 index 00000000..34df17e4 Binary files /dev/null and b/static/images/logo.png differ diff --git a/static/index.html b/static/index.html new file mode 100644 index 00000000..6bbf2c82 --- /dev/null +++ b/static/index.html @@ -0,0 +1,10 @@ + + + + + + + +

      This is cf web framework!

      + + diff --git a/static/js/echarts.min.js b/static/js/echarts.min.js new file mode 100644 index 00000000..fbad5eb8 --- /dev/null +++ b/static/js/echarts.min.js @@ -0,0 +1 @@ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.echarts={})}(this,function(t){"use strict";function e(t,e){"createCanvas"===t&&(nw=null),ew[t]=e}function i(t){if(null==t||"object"!=typeof t)return t;var e=t,n=Y_.call(t);if("[object Array]"===n){if(!O(t)){e=[];for(var o=0,a=t.length;o=0){var o="touchend"!==n?e.targetTouches[0]:e.changedTouches[0];o&&st(t,o,e,i)}else st(t,e,e,i),e.zrDelta=e.wheelDelta?e.wheelDelta/120:-(e.detail||0)/3;var a=e.button;return null==e.which&&void 0!==a&&gw.test(e.type)&&(e.which=1&a?1:2&a?3:4&a?2:0),e}function ht(t,e,i){pw?t.addEventListener(e,i):t.attachEvent("on"+e,i)}function ct(t,e,i){pw?t.removeEventListener(e,i):t.detachEvent("on"+e,i)}function dt(t){return 2===t.which||3===t.which}function ft(t){var e=t[1][0]-t[0][0],i=t[1][1]-t[0][1];return Math.sqrt(e*e+i*i)}function pt(t){return[(t[0][0]+t[1][0])/2,(t[0][1]+t[1][1])/2]}function gt(t,e,i){return{type:t,event:i,target:e.target,topTarget:e.topTarget,cancelBubble:!1,offsetX:i.zrX,offsetY:i.zrY,gestureEvent:i.gestureEvent,pinchX:i.pinchX,pinchY:i.pinchY,pinchScale:i.pinchScale,wheelDelta:i.zrDelta,zrByTouch:i.zrByTouch,which:i.which,stop:mt}}function mt(t){mw(this.event)}function vt(){}function yt(t,e,i){if(t[t.rectHover?"rectContain":"contain"](e,i)){for(var n,o=t;o;){if(o.clipPath&&!o.clipPath.contain(e,i))return!1;o.silent&&(n=!0),o=o.parent}return!n||xw}return!1}function xt(){var t=new bw(6);return _t(t),t}function _t(t){return t[0]=1,t[1]=0,t[2]=0,t[3]=1,t[4]=0,t[5]=0,t}function wt(t,e){return t[0]=e[0],t[1]=e[1],t[2]=e[2],t[3]=e[3],t[4]=e[4],t[5]=e[5],t}function bt(t,e,i){var n=e[0]*i[0]+e[2]*i[1],o=e[1]*i[0]+e[3]*i[1],a=e[0]*i[2]+e[2]*i[3],r=e[1]*i[2]+e[3]*i[3],s=e[0]*i[4]+e[2]*i[5]+e[4],l=e[1]*i[4]+e[3]*i[5]+e[5];return t[0]=n,t[1]=o,t[2]=a,t[3]=r,t[4]=s,t[5]=l,t}function St(t,e,i){return t[0]=e[0],t[1]=e[1],t[2]=e[2],t[3]=e[3],t[4]=e[4]+i[0],t[5]=e[5]+i[1],t}function Mt(t,e,i){var n=e[0],o=e[2],a=e[4],r=e[1],s=e[3],l=e[5],u=Math.sin(i),h=Math.cos(i);return t[0]=n*h+r*u,t[1]=-n*u+r*h,t[2]=o*h+s*u,t[3]=-o*u+h*s,t[4]=h*a+u*l,t[5]=h*l-u*a,t}function It(t,e,i){var n=i[0],o=i[1];return t[0]=e[0]*n,t[1]=e[1]*o,t[2]=e[2]*n,t[3]=e[3]*o,t[4]=e[4]*n,t[5]=e[5]*o,t}function Tt(t,e){var i=e[0],n=e[2],o=e[4],a=e[1],r=e[3],s=e[5],l=i*r-a*n;return l?(l=1/l,t[0]=r*l,t[1]=-a*l,t[2]=-n*l,t[3]=i*l,t[4]=(n*s-r*o)*l,t[5]=(a*o-i*s)*l,t):null}function At(t){var e=xt();return wt(e,t),e}function Dt(t){return t>Iw||t<-Iw}function Ct(t){this._target=t.target,this._life=t.life||1e3,this._delay=t.delay||0,this._initialized=!1,this.loop=null!=t.loop&&t.loop,this.gap=t.gap||0,this.easing=t.easing||"Linear",this.onframe=t.onframe,this.ondestroy=t.ondestroy,this.onrestart=t.onrestart,this._pausedTime=0,this._paused=!1}function Lt(t){return(t=Math.round(t))<0?0:t>255?255:t}function kt(t){return(t=Math.round(t))<0?0:t>360?360:t}function Pt(t){return t<0?0:t>1?1:t}function Nt(t){return Lt(t.length&&"%"===t.charAt(t.length-1)?parseFloat(t)/100*255:parseInt(t,10))}function Ot(t){return Pt(t.length&&"%"===t.charAt(t.length-1)?parseFloat(t)/100:parseFloat(t))}function Et(t,e,i){return i<0?i+=1:i>1&&(i-=1),6*i<1?t+(e-t)*i*6:2*i<1?e:3*i<2?t+(e-t)*(2/3-i)*6:t}function Rt(t,e,i){return t+(e-t)*i}function zt(t,e,i,n,o){return t[0]=e,t[1]=i,t[2]=n,t[3]=o,t}function Bt(t,e){return t[0]=e[0],t[1]=e[1],t[2]=e[2],t[3]=e[3],t}function Vt(t,e){Vw&&Bt(Vw,e),Vw=Bw.put(t,Vw||e.slice())}function Gt(t,e){if(t){e=e||[];var i=Bw.get(t);if(i)return Bt(e,i);var n=(t+="").replace(/ /g,"").toLowerCase();if(n in zw)return Bt(e,zw[n]),Vt(t,e),e;if("#"!==n.charAt(0)){var o=n.indexOf("("),a=n.indexOf(")");if(-1!==o&&a+1===n.length){var r=n.substr(0,o),s=n.substr(o+1,a-(o+1)).split(","),l=1;switch(r){case"rgba":if(4!==s.length)return void zt(e,0,0,0,1);l=Ot(s.pop());case"rgb":return 3!==s.length?void zt(e,0,0,0,1):(zt(e,Nt(s[0]),Nt(s[1]),Nt(s[2]),l),Vt(t,e),e);case"hsla":return 4!==s.length?void zt(e,0,0,0,1):(s[3]=Ot(s[3]),Ft(s,e),Vt(t,e),e);case"hsl":return 3!==s.length?void zt(e,0,0,0,1):(Ft(s,e),Vt(t,e),e);default:return}}zt(e,0,0,0,1)}else{if(4===n.length)return(u=parseInt(n.substr(1),16))>=0&&u<=4095?(zt(e,(3840&u)>>4|(3840&u)>>8,240&u|(240&u)>>4,15&u|(15&u)<<4,1),Vt(t,e),e):void zt(e,0,0,0,1);if(7===n.length){var u=parseInt(n.substr(1),16);return u>=0&&u<=16777215?(zt(e,(16711680&u)>>16,(65280&u)>>8,255&u,1),Vt(t,e),e):void zt(e,0,0,0,1)}}}}function Ft(t,e){var i=(parseFloat(t[0])%360+360)%360/360,n=Ot(t[1]),o=Ot(t[2]),a=o<=.5?o*(n+1):o+n-o*n,r=2*o-a;return e=e||[],zt(e,Lt(255*Et(r,a,i+1/3)),Lt(255*Et(r,a,i)),Lt(255*Et(r,a,i-1/3)),1),4===t.length&&(e[3]=t[3]),e}function Wt(t){if(t){var e,i,n=t[0]/255,o=t[1]/255,a=t[2]/255,r=Math.min(n,o,a),s=Math.max(n,o,a),l=s-r,u=(s+r)/2;if(0===l)e=0,i=0;else{i=u<.5?l/(s+r):l/(2-s-r);var h=((s-n)/6+l/2)/l,c=((s-o)/6+l/2)/l,d=((s-a)/6+l/2)/l;n===s?e=d-c:o===s?e=1/3+h-d:a===s&&(e=2/3+c-h),e<0&&(e+=1),e>1&&(e-=1)}var f=[360*e,i,u];return null!=t[3]&&f.push(t[3]),f}}function Ht(t,e){var i=Gt(t);if(i){for(var n=0;n<3;n++)i[n]=e<0?i[n]*(1-e)|0:(255-i[n])*e+i[n]|0,i[n]>255?i[n]=255:t[n]<0&&(i[n]=0);return qt(i,4===i.length?"rgba":"rgb")}}function Zt(t){var e=Gt(t);if(e)return((1<<24)+(e[0]<<16)+(e[1]<<8)+ +e[2]).toString(16).slice(1)}function Ut(t,e,i){if(e&&e.length&&t>=0&&t<=1){i=i||[];var n=t*(e.length-1),o=Math.floor(n),a=Math.ceil(n),r=e[o],s=e[a],l=n-o;return i[0]=Lt(Rt(r[0],s[0],l)),i[1]=Lt(Rt(r[1],s[1],l)),i[2]=Lt(Rt(r[2],s[2],l)),i[3]=Pt(Rt(r[3],s[3],l)),i}}function Xt(t,e,i){if(e&&e.length&&t>=0&&t<=1){var n=t*(e.length-1),o=Math.floor(n),a=Math.ceil(n),r=Gt(e[o]),s=Gt(e[a]),l=n-o,u=qt([Lt(Rt(r[0],s[0],l)),Lt(Rt(r[1],s[1],l)),Lt(Rt(r[2],s[2],l)),Pt(Rt(r[3],s[3],l))],"rgba");return i?{color:u,leftIndex:o,rightIndex:a,value:n}:u}}function jt(t,e,i,n){if(t=Gt(t))return t=Wt(t),null!=e&&(t[0]=kt(e)),null!=i&&(t[1]=Ot(i)),null!=n&&(t[2]=Ot(n)),qt(Ft(t),"rgba")}function Yt(t,e){if((t=Gt(t))&&null!=e)return t[3]=Pt(e),qt(t,"rgba")}function qt(t,e){if(t&&t.length){var i=t[0]+","+t[1]+","+t[2];return"rgba"!==e&&"hsva"!==e&&"hsla"!==e||(i+=","+t[3]),e+"("+i+")"}}function Kt(t,e){return t[e]}function $t(t,e,i){t[e]=i}function Jt(t,e,i){return(e-t)*i+t}function Qt(t,e,i){return i>.5?e:t}function te(t,e,i,n,o){var a=t.length;if(1===o)for(s=0;so)t.length=o;else for(r=n;r=0&&!(m[i]<=e);i--);i=Math.min(i,u-2)}else{for(i=L;ie);i++);i=Math.min(i-1,u-2)}L=i,k=e;var n=m[i+1]-m[i];if(0!==n)if(I=(e-m[i])/n,l)if(A=v[i],T=v[0===i?i:i-1],D=v[i>u-2?u-1:i+1],C=v[i>u-3?u-1:i+2],d)ne(T,A,D,C,I,I*I,I*I*I,r(t,o),g);else{if(f)a=ne(T,A,D,C,I,I*I,I*I*I,P,1),a=re(P);else{if(p)return Qt(A,D,I);a=oe(T,A,D,C,I,I*I,I*I*I)}s(t,o,a)}else if(d)te(v[i],v[i+1],I,r(t,o),g);else{var a;if(f)te(v[i],v[i+1],I,P,1),a=re(P);else{if(p)return Qt(v[i],v[i+1],I);a=Jt(v[i],v[i+1],I)}s(t,o,a)}},ondestroy:i});return e&&"spline"!==e&&(N.easing=e),N}}}function ue(t,e,i,n,o,a,r,s){_(n)?(a=o,o=n,n=0):x(o)?(a=o,o="linear",n=0):x(n)?(a=n,n=0):x(i)?(a=i,i=500):i||(i=500),t.stopAnimation(),he(t,"",t,e,i,n,s);var l=t.animators.slice(),u=l.length;u||a&&a();for(var h=0;h0&&t.animate(e,!1).when(null==o?500:o,s).delay(a||0)}function ce(t,e,i,n){if(e){var o={};o[e]={},o[e][i]=n,t.attr(o)}else t.attr(i,n)}function de(t,e,i,n){i<0&&(t+=i,i=-i),n<0&&(e+=n,n=-n),this.x=t,this.y=e,this.width=i,this.height=n}function fe(t){for(var e=0;t>=eb;)e|=1&t,t>>=1;return t+e}function pe(t,e,i,n){var o=e+1;if(o===i)return 1;if(n(t[o++],t[e])<0){for(;o=0;)o++;return o-e}function ge(t,e,i){for(i--;e>>1])<0?l=a:s=a+1;var u=n-s;switch(u){case 3:t[s+3]=t[s+2];case 2:t[s+2]=t[s+1];case 1:t[s+1]=t[s];break;default:for(;u>0;)t[s+u]=t[s+u-1],u--}t[s]=r}}function ve(t,e,i,n,o,a){var r=0,s=0,l=1;if(a(t,e[i+o])>0){for(s=n-o;l0;)r=l,(l=1+(l<<1))<=0&&(l=s);l>s&&(l=s),r+=o,l+=o}else{for(s=o+1;ls&&(l=s);var u=r;r=o-l,l=o-u}for(r++;r>>1);a(t,e[i+h])>0?r=h+1:l=h}return l}function ye(t,e,i,n,o,a){var r=0,s=0,l=1;if(a(t,e[i+o])<0){for(s=o+1;ls&&(l=s);var u=r;r=o-l,l=o-u}else{for(s=n-o;l=0;)r=l,(l=1+(l<<1))<=0&&(l=s);l>s&&(l=s),r+=o,l+=o}for(r++;r>>1);a(t,e[i+h])<0?l=h:r=h+1}return l}function xe(t,e){function i(i){var s=a[i],u=r[i],h=a[i+1],c=r[i+1];r[i]=u+c,i===l-3&&(a[i+1]=a[i+2],r[i+1]=r[i+2]),l--;var d=ye(t[h],t,s,u,0,e);s+=d,0!==(u-=d)&&0!==(c=ve(t[s+u-1],t,h,c,c-1,e))&&(u<=c?n(s,u,h,c):o(s,u,h,c))}function n(i,n,o,a){var r=0;for(r=0;r=ib||f>=ib);if(p)break;g<0&&(g=0),g+=2}if((s=g)<1&&(s=1),1===n){for(r=0;r=0;r--)t[f+r]=t[d+r];if(0===n){v=!0;break}}if(t[c--]=u[h--],1==--a){v=!0;break}if(0!=(m=a-ve(t[l],u,0,a,a-1,e))){for(a-=m,f=(c-=m)+1,d=(h-=m)+1,r=0;r=ib||m>=ib);if(v)break;p<0&&(p=0),p+=2}if((s=p)<1&&(s=1),1===a){for(f=(c-=n)+1,d=(l-=n)+1,r=n-1;r>=0;r--)t[f+r]=t[d+r];t[c]=u[h]}else{if(0===a)throw new Error;for(d=c-(a-1),r=0;r=0;r--)t[f+r]=t[d+r];t[c]=u[h]}else for(d=c-(a-1),r=0;r1;){var t=l-2;if(t>=1&&r[t-1]<=r[t]+r[t+1]||t>=2&&r[t-2]<=r[t]+r[t-1])r[t-1]r[t+1])break;i(t)}},this.forceMergeRuns=function(){for(;l>1;){var t=l-2;t>0&&r[t-1]s&&(l=s),me(t,i,i+l,i+a,e),a=l}r.pushRun(i,a),r.mergeRuns(),o-=a,i+=a}while(0!==o);r.forceMergeRuns()}}function we(t,e){return t.zlevel===e.zlevel?t.z===e.z?t.z2-e.z2:t.z-e.z:t.zlevel-e.zlevel}function be(t,e,i){var n=null==e.x?0:e.x,o=null==e.x2?1:e.x2,a=null==e.y?0:e.y,r=null==e.y2?0:e.y2;return e.global||(n=n*i.width+i.x,o=o*i.width+i.x,a=a*i.height+i.y,r=r*i.height+i.y),n=isNaN(n)?0:n,o=isNaN(o)?1:o,a=isNaN(a)?0:a,r=isNaN(r)?0:r,t.createLinearGradient(n,a,o,r)}function Se(t,e,i){var n=i.width,o=i.height,a=Math.min(n,o),r=null==e.x?.5:e.x,s=null==e.y?.5:e.y,l=null==e.r?.5:e.r;return e.global||(r=r*n+i.x,s=s*o+i.y,l*=a),t.createRadialGradient(r,s,0,r,s,l)}function Me(){return!1}function Ie(t,e,i){var n=iw(),o=e.getWidth(),a=e.getHeight(),r=n.style;return r&&(r.position="absolute",r.left=0,r.top=0,r.width=o+"px",r.height=a+"px",n.setAttribute("data-zr-dom-id",t)),n.width=o*i,n.height=a*i,n}function Te(t){if("string"==typeof t){var e=mb.get(t);return e&&e.image}return t}function Ae(t,e,i,n,o){if(t){if("string"==typeof t){if(e&&e.__zrImageSrc===t||!i)return e;var a=mb.get(t),r={hostEl:i,cb:n,cbPayload:o};return a?!Ce(e=a.image)&&a.pending.push(r):((e=new Image).onload=e.onerror=De,mb.put(t,e.__cachedImgObj={image:e,pending:[r]}),e.src=e.__zrImageSrc=t),e}return t}return e}function De(){var t=this.__cachedImgObj;this.onload=this.onerror=this.__cachedImgObj=null;for(var e=0;exb&&(yb=0,vb={}),yb++,vb[i]=o,o}function ke(t,e,i,n,o,a,r,s){return r?Ne(t,e,i,n,o,a,r,s):Pe(t,e,i,n,o,a,s)}function Pe(t,e,i,n,o,a,r){var s=He(t,e,o,a,r),l=Le(t,e);o&&(l+=o[1]+o[3]);var u=s.outerHeight,h=new de(Oe(0,l,i),Ee(0,u,n),l,u);return h.lineHeight=s.lineHeight,h}function Ne(t,e,i,n,o,a,r,s){var l=Ze(t,{rich:r,truncate:s,font:e,textAlign:i,textPadding:o,textLineHeight:a}),u=l.outerWidth,h=l.outerHeight;return new de(Oe(0,u,i),Ee(0,h,n),u,h)}function Oe(t,e,i){return"right"===i?t-=e:"center"===i&&(t-=e/2),t}function Ee(t,e,i){return"middle"===i?t-=e/2:"bottom"===i&&(t-=e),t}function Re(t,e,i){var n=e.x,o=e.y,a=e.height,r=e.width,s=a/2,l="left",u="top";switch(t){case"left":n-=i,o+=s,l="right",u="middle";break;case"right":n+=i+r,o+=s,u="middle";break;case"top":n+=r/2,o-=i,l="center",u="bottom";break;case"bottom":n+=r/2,o+=a+i,l="center";break;case"inside":n+=r/2,o+=s,l="center",u="middle";break;case"insideLeft":n+=i,o+=s,u="middle";break;case"insideRight":n+=r-i,o+=s,l="right",u="middle";break;case"insideTop":n+=r/2,o+=i,l="center";break;case"insideBottom":n+=r/2,o+=a-i,l="center",u="bottom";break;case"insideTopLeft":n+=i,o+=i;break;case"insideTopRight":n+=r-i,o+=i,l="right";break;case"insideBottomLeft":n+=i,o+=a-i,u="bottom";break;case"insideBottomRight":n+=r-i,o+=a-i,l="right",u="bottom"}return{x:n,y:o,textAlign:l,textVerticalAlign:u}}function ze(t,e,i,n,o){if(!e)return"";var a=(t+"").split("\n");o=Be(e,i,n,o);for(var r=0,s=a.length;r=r;l++)s-=r;var u=Le(i,e);return u>s&&(i="",u=0),s=t-u,n.ellipsis=i,n.ellipsisWidth=u,n.contentWidth=s,n.containerWidth=t,n}function Ve(t,e){var i=e.containerWidth,n=e.font,o=e.contentWidth;if(!i)return"";var a=Le(t,n);if(a<=i)return t;for(var r=0;;r++){if(a<=o||r>=e.maxIterations){t+=e.ellipsis;break}var s=0===r?Ge(t,o,e.ascCharWidth,e.cnCharWidth):a>0?Math.floor(t.length*o/a):0;a=Le(t=t.substr(0,s),n)}return""===t&&(t=e.placeholder),t}function Ge(t,e,i,n){for(var o=0,a=0,r=t.length;au)t="",r=[];else if(null!=h)for(var c=Be(h-(i?i[1]+i[3]:0),e,o.ellipsis,{minChar:o.minChar,placeholder:o.placeholder}),d=0,f=r.length;do&&Ue(i,t.substring(o,a)),Ue(i,n[2],n[1]),o=_b.lastIndex}of)return{lines:[],width:0,height:0};k.textWidth=Le(k.text,_);var b=y.textWidth,S=null==b||"auto"===b;if("string"==typeof b&&"%"===b.charAt(b.length-1))k.percentWidth=b,u.push(k),b=0;else{if(S){b=k.textWidth;var M=y.textBackgroundColor,I=M&&M.image;I&&Ce(I=Te(I))&&(b=Math.max(b,I.width*w/I.height))}var T=x?x[1]+x[3]:0;b+=T;var C=null!=d?d-m:null;null!=C&&Cl&&(i*=l/(c=i+n),n*=l/c),o+a>l&&(o*=l/(c=o+a),a*=l/c),n+o>u&&(n*=u/(c=n+o),o*=u/c),i+a>u&&(i*=u/(c=i+a),a*=u/c),t.moveTo(r+i,s),t.lineTo(r+l-n,s),0!==n&&t.arc(r+l-n,s+n,n,-Math.PI/2,0),t.lineTo(r+l,s+u-o),0!==o&&t.arc(r+l-o,s+u-o,o,0,Math.PI/2),t.lineTo(r+a,s+u),0!==a&&t.arc(r+a,s+u-a,a,Math.PI/2,Math.PI),t.lineTo(r,s+i),0!==i&&t.arc(r+i,s+i,i,Math.PI,1.5*Math.PI)}function Ye(t){return qe(t),d(t.rich,qe),t}function qe(t){if(t){t.font=Xe(t);var e=t.textAlign;"middle"===e&&(e="center"),t.textAlign=null==e||Mb[e]?e:"left";var i=t.textVerticalAlign||t.textBaseline;"center"===i&&(i="middle"),t.textVerticalAlign=null==i||Ib[i]?i:"top",t.textPadding&&(t.textPadding=L(t.textPadding))}}function Ke(t,e,i,n,o,a){n.rich?Je(t,e,i,n,o,a):$e(t,e,i,n,o,a)}function $e(t,e,i,n,o,a){var r,s=ii(n),l=!1,u=e.__attrCachedBy===rb.PLAIN_TEXT;a!==sb?(a&&(r=a.style,l=!s&&u&&r),e.__attrCachedBy=s?rb.NONE:rb.PLAIN_TEXT):u&&(e.__attrCachedBy=rb.NONE);var h=n.font||Sb;l&&h===(r.font||Sb)||(e.font=h);var c=t.__computedFont;t.__styleFont!==h&&(t.__styleFont=h,c=t.__computedFont=e.font);var d=n.textPadding,f=n.textLineHeight,p=t.__textCotentBlock;p&&!t.__dirtyText||(p=t.__textCotentBlock=He(i,c,d,f,n.truncate));var g=p.outerHeight,m=p.lines,v=p.lineHeight,y=ai(g,n,o),x=y.baseX,_=y.baseY,w=y.textAlign||"left",b=y.textVerticalAlign;ti(e,n,o,x,_);var S=Ee(_,g,b),M=x,I=S;if(s||d){var T=Le(i,c);d&&(T+=d[1]+d[3]);var A=Oe(x,T,w);s&&ni(t,e,n,A,S,T,g),d&&(M=hi(x,w,d),I+=d[0])}e.textAlign=w,e.textBaseline="middle",e.globalAlpha=n.opacity||1;for(B=0;B=0&&"right"===(_=b[C]).textAlign;)ei(t,e,_,n,M,v,D,"right"),I-=_.width,D-=_.width,C--;for(A+=(a-(A-m)-(y-D)-I)/2;T<=C;)ei(t,e,_=b[T],n,M,v,A+_.width/2,"center"),A+=_.width,T++;v+=M}}function ti(t,e,i,n,o){if(i&&e.textRotation){var a=e.textOrigin;"center"===a?(n=i.width/2+i.x,o=i.height/2+i.y):a&&(n=a[0]+i.x,o=a[1]+i.y),t.translate(n,o),t.rotate(-e.textRotation),t.translate(-n,-o)}}function ei(t,e,i,n,o,a,r,s){var l=n.rich[i.styleName]||{};l.text=i.text;var u=i.textVerticalAlign,h=a+o/2;"top"===u?h=a+i.height/2:"bottom"===u&&(h=a+o-i.height/2),!i.isLineHolder&&ii(l)&&ni(t,e,l,"right"===s?r-i.width:"center"===s?r-i.width/2:r,h-i.height/2,i.width,i.height);var c=i.textPadding;c&&(r=hi(r,s,c),h-=i.height/2-c[2]-i.textHeight/2),ri(e,"shadowBlur",D(l.textShadowBlur,n.textShadowBlur,0)),ri(e,"shadowColor",l.textShadowColor||n.textShadowColor||"transparent"),ri(e,"shadowOffsetX",D(l.textShadowOffsetX,n.textShadowOffsetX,0)),ri(e,"shadowOffsetY",D(l.textShadowOffsetY,n.textShadowOffsetY,0)),ri(e,"textAlign",s),ri(e,"textBaseline","middle"),ri(e,"font",i.font||Sb);var d=si(l.textStroke||n.textStroke,p),f=li(l.textFill||n.textFill),p=A(l.textStrokeWidth,n.textStrokeWidth);d&&(ri(e,"lineWidth",p),ri(e,"strokeStyle",d),e.strokeText(i.text,r,h)),f&&(ri(e,"fillStyle",f),e.fillText(i.text,r,h))}function ii(t){return!!(t.textBackgroundColor||t.textBorderWidth&&t.textBorderColor)}function ni(t,e,i,n,o,a,r){var s=i.textBackgroundColor,l=i.textBorderWidth,u=i.textBorderColor,h=_(s);if(ri(e,"shadowBlur",i.textBoxShadowBlur||0),ri(e,"shadowColor",i.textBoxShadowColor||"transparent"),ri(e,"shadowOffsetX",i.textBoxShadowOffsetX||0),ri(e,"shadowOffsetY",i.textBoxShadowOffsetY||0),h||l&&u){e.beginPath();var c=i.textBorderRadius;c?je(e,{x:n,y:o,width:a,height:r,r:c}):e.rect(n,o,a,r),e.closePath()}if(h)if(ri(e,"fillStyle",s),null!=i.fillOpacity){f=e.globalAlpha;e.globalAlpha=i.fillOpacity*i.opacity,e.fill(),e.globalAlpha=f}else e.fill();else if(w(s)){var d=s.image;(d=Ae(d,null,t,oi,s))&&Ce(d)&&e.drawImage(d,n,o,a,r)}if(l&&u)if(ri(e,"lineWidth",l),ri(e,"strokeStyle",u),null!=i.strokeOpacity){var f=e.globalAlpha;e.globalAlpha=i.strokeOpacity*i.opacity,e.stroke(),e.globalAlpha=f}else e.stroke()}function oi(t,e){e.image=t}function ai(t,e,i){var n=e.x||0,o=e.y||0,a=e.textAlign,r=e.textVerticalAlign;if(i){var s=e.textPosition;if(s instanceof Array)n=i.x+ui(s[0],i.width),o=i.y+ui(s[1],i.height);else{var l=Re(s,i,e.textDistance);n=l.x,o=l.y,a=a||l.textAlign,r=r||l.textVerticalAlign}var u=e.textOffset;u&&(n+=u[0],o+=u[1])}return{baseX:n,baseY:o,textAlign:a,textVerticalAlign:r}}function ri(t,e,i){return t[e]=ab(t,e,i),t[e]}function si(t,e){return null==t||e<=0||"transparent"===t||"none"===t?null:t.image||t.colorStops?"#000":t}function li(t){return null==t||"none"===t?null:t.image||t.colorStops?"#000":t}function ui(t,e){return"string"==typeof t?t.lastIndexOf("%")>=0?parseFloat(t)/100*e:parseFloat(t):t}function hi(t,e,i){return"right"===e?t-i[1]:"center"===e?t+i[3]/2-i[1]/2:t+i[3]}function ci(t,e){return null!=t&&(t||e.textBackgroundColor||e.textBorderWidth&&e.textBorderColor||e.textPadding)}function di(t){t=t||{},Kw.call(this,t);for(var e in t)t.hasOwnProperty(e)&&"style"!==e&&(this[e]=t[e]);this.style=new ub(t.style,this),this._rect=null,this.__clipPaths=[]}function fi(t){di.call(this,t)}function pi(t){return parseInt(t,10)}function gi(t){return!!t&&(!!t.__builtin__||"function"==typeof t.resize&&"function"==typeof t.refresh)}function mi(t,e,i){return Cb.copy(t.getBoundingRect()),t.transform&&Cb.applyTransform(t.transform),Lb.width=e,Lb.height=i,!Cb.intersect(Lb)}function vi(t,e){if(t===e)return!1;if(!t||!e||t.length!==e.length)return!0;for(var i=0;i=i.length&&i.push({option:t})}}),i}function Ni(t){var e=R();Zb(t,function(t,i){var n=t.exist;n&&e.set(n.id,t)}),Zb(t,function(t,i){var n=t.option;k(!n||null==n.id||!e.get(n.id)||e.get(n.id)===t,"id duplicates: "+(n&&n.id)),n&&null!=n.id&&e.set(n.id,t),!t.keyInfo&&(t.keyInfo={})}),Zb(t,function(t,i){var n=t.exist,o=t.option,a=t.keyInfo;if(Ub(o)){if(a.name=null!=o.name?o.name+"":n?n.name:jb+i,n)a.id=n.id;else if(null!=o.id)a.id=o.id+"";else{var r=0;do{a.id="\0"+a.name+"\0"+r++}while(e.get(a.id))}e.set(a.id,t)}})}function Oi(t){var e=t.name;return!(!e||!e.indexOf(jb))}function Ei(t){return Ub(t)&&t.id&&0===(t.id+"").indexOf("\0_ec_\0")}function Ri(t,e){function i(t,e,i){for(var n=0,o=t.length;n-rS&&trS||t<-rS}function tn(t,e,i,n,o){var a=1-o;return a*a*(a*t+3*o*e)+o*o*(o*n+3*a*i)}function en(t,e,i,n,o){var a=1-o;return 3*(((e-t)*a+2*(i-e)*o)*a+(n-i)*o*o)}function nn(t,e,i,n,o,a){var r=n+3*(e-i)-t,s=3*(i-2*e+t),l=3*(e-t),u=t-o,h=s*s-3*r*l,c=s*l-9*r*u,d=l*l-3*s*u,f=0;if(Ji(h)&&Ji(c))Ji(s)?a[0]=0:(M=-l/s)>=0&&M<=1&&(a[f++]=M);else{var p=c*c-4*h*d;if(Ji(p)){var g=c/h,m=-g/2;(M=-s/r+g)>=0&&M<=1&&(a[f++]=M),m>=0&&m<=1&&(a[f++]=m)}else if(p>0){var v=aS(p),y=h*s+1.5*r*(-c+v),x=h*s+1.5*r*(-c-v);(M=(-s-((y=y<0?-oS(-y,uS):oS(y,uS))+(x=x<0?-oS(-x,uS):oS(x,uS))))/(3*r))>=0&&M<=1&&(a[f++]=M)}else{var _=(2*h*s-3*r*c)/(2*aS(h*h*h)),w=Math.acos(_)/3,b=aS(h),S=Math.cos(w),M=(-s-2*b*S)/(3*r),m=(-s+b*(S+lS*Math.sin(w)))/(3*r),I=(-s+b*(S-lS*Math.sin(w)))/(3*r);M>=0&&M<=1&&(a[f++]=M),m>=0&&m<=1&&(a[f++]=m),I>=0&&I<=1&&(a[f++]=I)}}return f}function on(t,e,i,n,o){var a=6*i-12*e+6*t,r=9*e+3*n-3*t-9*i,s=3*e-3*t,l=0;if(Ji(r))Qi(a)&&(c=-s/a)>=0&&c<=1&&(o[l++]=c);else{var u=a*a-4*r*s;if(Ji(u))o[0]=-a/(2*r);else if(u>0){var h=aS(u),c=(-a+h)/(2*r),d=(-a-h)/(2*r);c>=0&&c<=1&&(o[l++]=c),d>=0&&d<=1&&(o[l++]=d)}}return l}function an(t,e,i,n,o,a){var r=(e-t)*o+t,s=(i-e)*o+e,l=(n-i)*o+i,u=(s-r)*o+r,h=(l-s)*o+s,c=(h-u)*o+u;a[0]=t,a[1]=r,a[2]=u,a[3]=c,a[4]=c,a[5]=h,a[6]=l,a[7]=n}function rn(t,e,i,n,o,a,r,s,l,u,h){var c,d,f,p,g,m=.005,v=1/0;hS[0]=l,hS[1]=u;for(var y=0;y<1;y+=.05)cS[0]=tn(t,i,o,r,y),cS[1]=tn(e,n,a,s,y),(p=hw(hS,cS))=0&&p=0&&c<=1&&(o[l++]=c);else{var u=r*r-4*a*s;if(Ji(u))(c=-r/(2*a))>=0&&c<=1&&(o[l++]=c);else if(u>0){var h=aS(u),c=(-r+h)/(2*a),d=(-r-h)/(2*a);c>=0&&c<=1&&(o[l++]=c),d>=0&&d<=1&&(o[l++]=d)}}return l}function hn(t,e,i){var n=t+i-2*e;return 0===n?.5:(t-e)/n}function cn(t,e,i,n,o){var a=(e-t)*n+t,r=(i-e)*n+e,s=(r-a)*n+a;o[0]=t,o[1]=a,o[2]=s,o[3]=s,o[4]=r,o[5]=i}function dn(t,e,i,n,o,a,r,s,l){var u,h=.005,c=1/0;hS[0]=r,hS[1]=s;for(var d=0;d<1;d+=.05)cS[0]=sn(t,i,o,d),cS[1]=sn(e,n,a,d),(m=hw(hS,cS))=0&&m1e-4)return s[0]=t-i,s[1]=e-n,l[0]=t+i,void(l[1]=e+n);if(yS[0]=mS(o)*i+t,yS[1]=gS(o)*n+e,xS[0]=mS(a)*i+t,xS[1]=gS(a)*n+e,u(s,yS,xS),h(l,yS,xS),(o%=vS)<0&&(o+=vS),(a%=vS)<0&&(a+=vS),o>a&&!r?a+=vS:oo&&(_S[0]=mS(f)*i+t,_S[1]=gS(f)*n+e,u(s,_S,s),h(l,_S,l))}function yn(t,e,i,n,o,a,r){if(0===o)return!1;var s=o,l=0,u=t;if(r>e+s&&r>n+s||rt+s&&a>i+s||ae+c&&h>n+c&&h>a+c&&h>s+c||ht+c&&u>i+c&&u>o+c&&u>r+c||ue+u&&l>n+u&&l>a+u||lt+u&&s>i+u&&s>o+u||si||h+uo&&(o+=zS);var d=Math.atan2(l,s);return d<0&&(d+=zS),d>=n&&d<=o||d+zS>=n&&d+zS<=o}function Sn(t,e,i,n,o,a){if(a>e&&a>n||ao?r:0}function Mn(t,e){return Math.abs(t-e)e&&u>n&&u>a&&u>s||u1&&In(),c=tn(e,n,a,s,WS[0]),p>1&&(d=tn(e,n,a,s,WS[1]))),2===p?me&&s>n&&s>a||s=0&&u<=1){for(var h=0,c=sn(e,n,a,u),d=0;di||s<-i)return 0;u=Math.sqrt(i*i-s*s);FS[0]=-u,FS[1]=u;var l=Math.abs(n-o);if(l<1e-4)return 0;if(l%VS<1e-4){n=0,o=VS;p=a?1:-1;return r>=FS[0]+t&&r<=FS[1]+t?p:0}if(a){var u=n;n=wn(o),o=wn(u)}else n=wn(n),o=wn(o);n>o&&(o+=VS);for(var h=0,c=0;c<2;c++){var d=FS[c];if(d+t>r){var f=Math.atan2(s,d),p=a?1:-1;f<0&&(f=VS+f),(f>=n&&f<=o||f+VS>=n&&f+VS<=o)&&(f>Math.PI/2&&f<1.5*Math.PI&&(p=-p),h+=p)}}return h}function Cn(t,e,i,n,o){for(var a=0,r=0,s=0,l=0,u=0,h=0;h1&&(i||(a+=Sn(r,s,l,u,n,o))),1===h&&(l=r=t[h],u=s=t[h+1]),c){case BS.M:r=l=t[h++],s=u=t[h++];break;case BS.L:if(i){if(yn(r,s,t[h],t[h+1],e,n,o))return!0}else a+=Sn(r,s,t[h],t[h+1],n,o)||0;r=t[h++],s=t[h++];break;case BS.C:if(i){if(xn(r,s,t[h++],t[h++],t[h++],t[h++],t[h],t[h+1],e,n,o))return!0}else a+=Tn(r,s,t[h++],t[h++],t[h++],t[h++],t[h],t[h+1],n,o)||0;r=t[h++],s=t[h++];break;case BS.Q:if(i){if(_n(r,s,t[h++],t[h++],t[h],t[h+1],e,n,o))return!0}else a+=An(r,s,t[h++],t[h++],t[h],t[h+1],n,o)||0;r=t[h++],s=t[h++];break;case BS.A:var d=t[h++],f=t[h++],p=t[h++],g=t[h++],m=t[h++],v=t[h++];h+=1;var y=1-t[h++],x=Math.cos(m)*p+d,_=Math.sin(m)*g+f;h>1?a+=Sn(r,s,x,_,n,o):(l=x,u=_);var w=(n-d)*g/p+d;if(i){if(bn(d,f,g,m,m+v,y,e,w,o))return!0}else a+=Dn(d,f,g,m,m+v,y,w,o);r=Math.cos(m+v)*p+d,s=Math.sin(m+v)*g+f;break;case BS.R:l=r=t[h++],u=s=t[h++];var x=l+t[h++],_=u+t[h++];if(i){if(yn(l,u,x,u,e,n,o)||yn(x,u,x,_,e,n,o)||yn(x,_,l,_,e,n,o)||yn(l,_,l,u,e,n,o))return!0}else a+=Sn(x,u,x,_,n,o),a+=Sn(l,_,l,u,n,o);break;case BS.Z:if(i){if(yn(r,s,l,u,e,n,o))return!0}else a+=Sn(r,s,l,u,n,o);r=l,s=u}}return i||Mn(s,u)||(a+=Sn(r,s,l,u,n,o)||0),0!==a}function Ln(t,e,i){return Cn(t,0,!1,e,i)}function kn(t,e,i,n){return Cn(t,e,!0,i,n)}function Pn(t){di.call(this,t),this.path=null}function Nn(t,e,i,n,o,a,r,s,l,u,h){var c=l*(tM/180),d=QS(c)*(t-i)/2+JS(c)*(e-n)/2,f=-1*JS(c)*(t-i)/2+QS(c)*(e-n)/2,p=d*d/(r*r)+f*f/(s*s);p>1&&(r*=$S(p),s*=$S(p));var g=(o===a?-1:1)*$S((r*r*(s*s)-r*r*(f*f)-s*s*(d*d))/(r*r*(f*f)+s*s*(d*d)))||0,m=g*r*f/s,v=g*-s*d/r,y=(t+i)/2+QS(c)*m-JS(c)*v,x=(e+n)/2+JS(c)*m+QS(c)*v,_=nM([1,0],[(d-m)/r,(f-v)/s]),w=[(d-m)/r,(f-v)/s],b=[(-1*d-m)/r,(-1*f-v)/s],S=nM(w,b);iM(w,b)<=-1&&(S=tM),iM(w,b)>=1&&(S=0),0===a&&S>0&&(S-=2*tM),1===a&&S<0&&(S+=2*tM),h.addData(u,y,x,r,s,_,S,c,a)}function On(t){if(!t)return new ES;for(var e,i=0,n=0,o=i,a=n,r=new ES,s=ES.CMD,l=t.match(oM),u=0;u=2){if(o&&"spline"!==o){var a=fM(n,o,i,e.smoothConstraint);t.moveTo(n[0][0],n[0][1]);for(var r=n.length,s=0;s<(i?r:r-1);s++){var l=a[2*s],u=a[2*s+1],h=n[(s+1)%r];t.bezierCurveTo(l[0],l[1],u[0],u[1],h[0],h[1])}}else{"spline"===o&&(n=dM(n,i)),t.moveTo(n[0][0],n[0][1]);for(var s=1,c=n.length;s=0)?(i={textFill:null,textStroke:t.textStroke,textStrokeWidth:t.textStrokeWidth},t.textFill="#fff",null==t.textStroke&&(t.textStroke=a,null==t.textStrokeWidth&&(t.textStrokeWidth=2))):null!=a&&(i={textFill:null},t.textFill=a),i&&(t.insideRollback=i)}}function bo(t){var e=t.insideRollback;e&&(t.textFill=e.textFill,t.textStroke=e.textStroke,t.textStrokeWidth=e.textStrokeWidth,t.insideRollback=null)}function So(t,e){var i=e||e.getModel("textStyle");return P([t.fontStyle||i&&i.getShallow("fontStyle")||"",t.fontWeight||i&&i.getShallow("fontWeight")||"",(t.fontSize||i&&i.getShallow("fontSize")||12)+"px",t.fontFamily||i&&i.getShallow("fontFamily")||"sans-serif"].join(" "))}function Mo(t,e,i,n,o,a){if("function"==typeof o&&(a=o,o=null),n&&n.isAnimationEnabled()){var r=t?"Update":"",s=n.getShallow("animationDuration"+r),l=n.getShallow("animationEasing"+r),u=n.getShallow("animationDelay"+r);"function"==typeof u&&(u=u(o,n.getAnimationDelayParams?n.getAnimationDelayParams(e,o):null)),"function"==typeof s&&(s=s(o)),s>0?e.animateTo(i,s,u||0,l,a,!!a):(e.stopAnimation(),e.attr(i),a&&a())}else e.stopAnimation(),e.attr(i),a&&a()}function Io(t,e,i,n,o){Mo(!0,t,e,i,n,o)}function To(t,e,i,n,o){Mo(!1,t,e,i,n,o)}function Ao(t,e){for(var i=_t([]);t&&t!==e;)bt(i,t.getLocalTransform(),i),t=t.parent;return i}function Do(t,e,i){return e&&!c(e)&&(e=Tw.getLocalTransform(e)),i&&(e=Tt([],e)),Q([],t,e)}function Co(t,e,i){var n=0===e[4]||0===e[5]||0===e[0]?1:Math.abs(2*e[4]/e[0]),o=0===e[4]||0===e[5]||0===e[2]?1:Math.abs(2*e[4]/e[2]),a=["left"===t?-n:"right"===t?n:0,"top"===t?-o:"bottom"===t?o:0];return a=Do(a,e,i),Math.abs(a[0])>Math.abs(a[1])?a[0]>0?"right":"left":a[1]>0?"bottom":"top"}function Lo(t,e,i,n){function o(t){var e={position:F(t.position),rotation:t.rotation};return t.shape&&(e.shape=a({},t.shape)),e}if(t&&e){var r=function(t){var e={};return t.traverse(function(t){!t.isGroup&&t.anid&&(e[t.anid]=t)}),e}(t);e.traverse(function(t){if(!t.isGroup&&t.anid){var e=r[t.anid];if(e){var n=o(t);t.attr(o(e)),Io(t,n,i,t.dataIndex)}}})}}function ko(t,e){return f(t,function(t){var i=t[0];i=LM(i,e.x),i=kM(i,e.x+e.width);var n=t[1];return n=LM(n,e.y),n=kM(n,e.y+e.height),[i,n]})}function Po(t,e,i){var n=(e=a({rectHover:!0},e)).style={strokeNoScale:!0};if(i=i||{x:-1,y:-1,width:2,height:2},t)return 0===t.indexOf("image://")?(n.image=t.slice(8),r(n,i),new fi(e)):Xn(t.replace("path://",""),e,i,"center")}function No(t,e,i){this.parentModel=e,this.ecModel=i,this.option=t}function Oo(t,e,i){for(var n=0;n0){if(t<=e[0])return i[0];if(t>=e[1])return i[1]}else{if(t>=e[0])return i[0];if(t<=e[1])return i[1]}else{if(t===e[0])return i[0];if(t===e[1])return i[1]}return(t-e[0])/o*a+i[0]}function Vo(t,e){switch(t){case"center":case"middle":t="50%";break;case"left":case"top":t="0%";break;case"right":case"bottom":t="100%"}return"string"==typeof t?zo(t).match(/%$/)?parseFloat(t)/100*e:parseFloat(t):null==t?NaN:+t}function Go(t,e,i){return null==e&&(e=10),e=Math.min(Math.max(0,e),20),t=(+t).toFixed(e),i?t:+t}function Fo(t){return t.sort(function(t,e){return t-e}),t}function Wo(t){if(t=+t,isNaN(t))return 0;for(var e=1,i=0;Math.round(t*e)/e!==t;)e*=10,i++;return i}function Ho(t){var e=t.toString(),i=e.indexOf("e");if(i>0){var n=+e.slice(i+1);return n<0?-n:0}var o=e.indexOf(".");return o<0?0:e.length-1-o}function Zo(t,e){var i=Math.log,n=Math.LN10,o=Math.floor(i(t[1]-t[0])/n),a=Math.round(i(Math.abs(e[1]-e[0]))/n),r=Math.min(Math.max(-o+a,0),20);return isFinite(r)?r:20}function Uo(t,e,i){if(!t[e])return 0;var n=p(t,function(t,e){return t+(isNaN(e)?0:e)},0);if(0===n)return 0;for(var o=Math.pow(10,i),a=f(t,function(t){return(isNaN(t)?0:t)/n*o*100}),r=100*o,s=f(a,function(t){return Math.floor(t)}),l=p(s,function(t,e){return t+e},0),u=f(a,function(t,e){return t-s[e]});lh&&(h=u[d],c=d);++s[c],u[c]=0,++l}return s[e]/o}function Xo(t){var e=2*Math.PI;return(t%e+e)%e}function jo(t){return t>-UM&&t=-20?+t.toFixed(n<0?-n:0):t}function Jo(t){function e(t,i,n){return t.interval[n]=0}function ta(t){return isNaN(t)?"-":(t=(t+"").split("."))[0].replace(/(\d{1,3})(?=(?:\d{3})+(?!\d))/g,"$1,")+(t.length>1?"."+t[1]:"")}function ea(t,e){return t=(t||"").toLowerCase().replace(/-(.)/g,function(t,e){return e.toUpperCase()}),e&&t&&(t=t.charAt(0).toUpperCase()+t.slice(1)),t}function ia(t){return null==t?"":(t+"").replace(KM,function(t,e){return $M[e]})}function na(t,e,i){y(e)||(e=[e]);var n=e.length;if(!n)return"";for(var o=e[0].$vars||[],a=0;a':'':{renderMode:o,content:"{marker"+a+"|} ",style:{color:i}}:""}function ra(t,e){return t+="","0000".substr(0,e-t.length)+t}function sa(t,e,i){"week"!==t&&"month"!==t&&"quarter"!==t&&"half-year"!==t&&"year"!==t||(t="MM-dd\nyyyy");var n=Yo(e),o=i?"UTC":"",a=n["get"+o+"FullYear"](),r=n["get"+o+"Month"]()+1,s=n["get"+o+"Date"](),l=n["get"+o+"Hours"](),u=n["get"+o+"Minutes"](),h=n["get"+o+"Seconds"](),c=n["get"+o+"Milliseconds"]();return t=t.replace("MM",ra(r,2)).replace("M",r).replace("yyyy",a).replace("yy",a%100).replace("dd",ra(s,2)).replace("d",s).replace("hh",ra(l,2)).replace("h",l).replace("mm",ra(u,2)).replace("m",u).replace("ss",ra(h,2)).replace("s",h).replace("SSS",ra(c,3))}function la(t){return t?t.charAt(0).toUpperCase()+t.substr(1):t}function ua(t,e,i,n,o){var a=0,r=0;null==n&&(n=1/0),null==o&&(o=1/0);var s=0;e.eachChild(function(l,u){var h,c,d=l.position,f=l.getBoundingRect(),p=e.childAt(u+1),g=p&&p.getBoundingRect();if("horizontal"===t){var m=f.width+(g?-g.x+f.x:0);(h=a+m)>n||l.newline?(a=0,h=m,r+=s+i,s=f.height):s=Math.max(s,f.height)}else{var v=f.height+(g?-g.y+f.y:0);(c=r+v)>o||l.newline?(a+=s+i,r=0,c=v,s=f.width):s=Math.max(s,f.width)}l.newline||(d[0]=a,d[1]=r,"horizontal"===t?a=h+i:r=c+i)})}function ha(t,e,i){var n=e.width,o=e.height,a=Vo(t.x,n),r=Vo(t.y,o),s=Vo(t.x2,n),l=Vo(t.y2,o);return(isNaN(a)||isNaN(parseFloat(t.x)))&&(a=0),(isNaN(s)||isNaN(parseFloat(t.x2)))&&(s=n),(isNaN(r)||isNaN(parseFloat(t.y)))&&(r=0),(isNaN(l)||isNaN(parseFloat(t.y2)))&&(l=o),i=qM(i||0),{width:Math.max(s-a-i[1]-i[3],0),height:Math.max(l-r-i[0]-i[2],0)}}function ca(t,e,i){i=qM(i||0);var n=e.width,o=e.height,a=Vo(t.left,n),r=Vo(t.top,o),s=Vo(t.right,n),l=Vo(t.bottom,o),u=Vo(t.width,n),h=Vo(t.height,o),c=i[2]+i[0],d=i[1]+i[3],f=t.aspect;switch(isNaN(u)&&(u=n-s-d-a),isNaN(h)&&(h=o-l-c-r),null!=f&&(isNaN(u)&&isNaN(h)&&(f>n/o?u=.8*n:h=.8*o),isNaN(u)&&(u=f*h),isNaN(h)&&(h=u/f)),isNaN(a)&&(a=n-s-u-d),isNaN(r)&&(r=o-l-h-c),t.left||t.right){case"center":a=n/2-u/2-i[3];break;case"right":a=n-u-d}switch(t.top||t.bottom){case"middle":case"center":r=o/2-h/2-i[0];break;case"bottom":r=o-h-c}a=a||0,r=r||0,isNaN(u)&&(u=n-d-a-(s||0)),isNaN(h)&&(h=o-c-r-(l||0));var p=new de(a+i[3],r+i[0],u,h);return p.margin=i,p}function da(t,e,i,n,o){var a=!o||!o.hv||o.hv[0],s=!o||!o.hv||o.hv[1],l=o&&o.boundingMode||"all";if(a||s){var u;if("raw"===l)u="group"===t.type?new de(0,0,+e.width||0,+e.height||0):t.getBoundingRect();else if(u=t.getBoundingRect(),t.needLocalTransform()){var h=t.getLocalTransform();(u=u.clone()).applyTransform(h)}e=ca(r({width:u.width,height:u.height},e),i,n);var c=t.position,d=a?e.x-u.x:0,f=s?e.y-u.y:0;t.attr("position","raw"===l?[d,f]:[c[0]+d,c[1]+f])}}function fa(t,e){return null!=t[oI[e][0]]||null!=t[oI[e][1]]&&null!=t[oI[e][2]]}function pa(t,e,i){function n(i,n){var r={},l=0,u={},h=0;if(iI(i,function(e){u[e]=t[e]}),iI(i,function(t){o(e,t)&&(r[t]=u[t]=e[t]),a(r,t)&&l++,a(u,t)&&h++}),s[n])return a(e,i[1])?u[i[2]]=null:a(e,i[2])&&(u[i[1]]=null),u;if(2!==h&&l){if(l>=2)return r;for(var c=0;ce)return t[n];return t[i-1]}function ya(t){var e=t.get("coordinateSystem"),i={coordSysName:e,coordSysDims:[],axisMap:R(),categoryAxisMap:R()},n=fI[e];if(n)return n(t,i,i.axisMap,i.categoryAxisMap),i}function xa(t){return"category"===t.get("type")}function _a(t){this.fromDataset=t.fromDataset,this.data=t.data||(t.sourceFormat===vI?{}:[]),this.sourceFormat=t.sourceFormat||yI,this.seriesLayoutBy=t.seriesLayoutBy||_I,this.dimensionsDefine=t.dimensionsDefine,this.encodeDefine=t.encodeDefine&&R(t.encodeDefine),this.startIndex=t.startIndex||0,this.dimensionsDetectCount=t.dimensionsDetectCount}function wa(t){var e=t.option.source,i=yI;if(S(e))i=xI;else if(y(e)){0===e.length&&(i=gI);for(var n=0,o=e.length;n=e:"max"===i?t<=e:t===e}function Xa(t,e){return t.join(",")===e.join(",")}function ja(t,e){AI(e=e||{},function(e,i){if(null!=e){var n=t[i];if(lI.hasClass(i)){e=Di(e);var o=Pi(n=Di(n),e);t[i]=CI(o,function(t){return t.option&&t.exist?LI(t.exist,t.option,!0):t.exist||t.option})}else t[i]=LI(n,e,!0)}})}function Ya(t){var e=t&&t.itemStyle;if(e)for(var i=0,o=OI.length;i=0;p--){var g=t[p];if(s||(d=g.data.rawIndexOf(g.stackedByDimension,c)),d>=0){var m=g.data.getByRawIndex(g.stackResultDimension,d);if(h>=0&&m>0||h<=0&&m<0){h+=m,f=m;break}}}return n[0]=h,n[1]=f,n});r.hostModel.setData(l),e.data=l})}function rr(t,e){_a.isInstance(t)||(t=_a.seriesDataToSource(t)),this._source=t;var i=this._data=t.data,n=t.sourceFormat;n===xI&&(this._offset=0,this._dimSize=e,this._data=i),a(this,GI[n===gI?n+"_"+t.seriesLayoutBy:n])}function sr(){return this._data.length}function lr(t){return this._data[t]}function ur(t){for(var e=0;ee.outputData.count()&&e.model.getRawData().cloneShallow(e.outputData)}function Mr(t,e){d(t.CHANGABLE_METHODS,function(i){t.wrapMethod(i,v(Ir,e))})}function Ir(t){var e=Tr(t);e&&e.setOutputEnd(this.count())}function Tr(t){var e=(t.ecModel||{}).scheduler,i=e&&e.getPipeline(t.uid);if(i){var n=i.currentTask;if(n){var o=n.agentStubMap;o&&(n=o.get(t.uid))}return n}}function Ar(){this.group=new tb,this.uid=Ro("viewChart"),this.renderTask=gr({plan:Lr,reset:kr}),this.renderTask.context={view:this}}function Dr(t,e){if(t&&(t.trigger(e),"group"===t.type))for(var i=0;i=0?n():c=setTimeout(n,-a),u=o};return d.clear=function(){c&&(clearTimeout(c),c=null)},d.debounceNextCall=function(t){l=t},d}function Nr(t,e,i,n){var o=t[e];if(o){var a=o[iT]||o,r=o[oT];if(o[nT]!==i||r!==n){if(null==i||!n)return t[e]=a;(o=t[e]=Pr(a,i,"debounce"===n))[iT]=a,o[oT]=n,o[nT]=i}return o}}function Or(t,e){var i=t[e];i&&i[iT]&&(t[e]=i[iT])}function Er(t,e,i,n){this.ecInstance=t,this.api=e,this.unfinished;var i=this._dataProcessorHandlers=i.slice(),n=this._visualHandlers=n.slice();this._allHandlers=i.concat(n),this._stageTaskMap=R()}function Rr(t,e,i,n,o){function a(t,e){return t.setDirty&&(!t.dirtyMap||t.dirtyMap.get(e.__pipeline.id))}o=o||{};var r;d(e,function(e,s){if(!o.visualType||o.visualType===e.visualType){var l=t._stageTaskMap.get(e.uid),u=l.seriesTaskMap,h=l.overallTask;if(h){var c,d=h.agentStubMap;d.each(function(t){a(o,t)&&(t.dirty(),c=!0)}),c&&h.dirty(),hT(h,n);var f=t.getPerformArgs(h,o.block);d.each(function(t){t.perform(f)}),r|=h.perform(f)}else u&&u.each(function(s,l){a(o,s)&&s.dirty();var u=t.getPerformArgs(s,o.block);u.skip=!e.performRawSeries&&i.isSeriesFiltered(s.context.model),hT(s,n),r|=s.perform(u)})}}),t.unfinished|=r}function zr(t,e,i,n,o){function a(i){var a=i.uid,s=r.get(a)||r.set(a,gr({plan:Hr,reset:Zr,count:Xr}));s.context={model:i,ecModel:n,api:o,useClearVisual:e.isVisual&&!e.isLayout,plan:e.plan,reset:e.reset,scheduler:t},jr(t,i,s)}var r=i.seriesTaskMap||(i.seriesTaskMap=R()),s=e.seriesType,l=e.getTargetSeries;e.createOnAllSeries?n.eachRawSeries(a):s?n.eachRawSeriesByType(s,a):l&&l(n,o).each(a);var u=t._pipelineMap;r.each(function(t,e){u.get(e)||(t.dispose(),r.removeKey(e))})}function Br(t,e,i,n,o){function a(e){var i=e.uid,n=s.get(i);n||(n=s.set(i,gr({reset:Gr,onDirty:Wr})),r.dirty()),n.context={model:e,overallProgress:h,modifyOutputEnd:c},n.agent=r,n.__block=h,jr(t,e,n)}var r=i.overallTask=i.overallTask||gr({reset:Vr});r.context={ecModel:n,api:o,overallReset:e.overallReset,scheduler:t};var s=r.agentStubMap=r.agentStubMap||R(),l=e.seriesType,u=e.getTargetSeries,h=!0,c=e.modifyOutputEnd;l?n.eachRawSeriesByType(l,a):u?u(n,o).each(a):(h=!1,d(n.getSeries(),a));var f=t._pipelineMap;s.each(function(t,e){f.get(e)||(t.dispose(),r.dirty(),s.removeKey(e))})}function Vr(t){t.overallReset(t.ecModel,t.api,t.payload)}function Gr(t,e){return t.overallProgress&&Fr}function Fr(){this.agent.dirty(),this.getDownstream().dirty()}function Wr(){this.agent&&this.agent.dirty()}function Hr(t){return t.plan&&t.plan(t.model,t.ecModel,t.api,t.payload)}function Zr(t){t.useClearVisual&&t.data.clearAllVisual();var e=t.resetDefines=Di(t.reset(t.model,t.ecModel,t.api,t.payload));return e.length>1?f(e,function(t,e){return Ur(e)}):cT}function Ur(t){return function(e,i){var n=i.data,o=i.resetDefines[t];if(o&&o.dataEach)for(var a=e.start;a0?parseInt(n,10)/100:n?parseFloat(n):0;var o=i.getAttribute("stop-color")||"#000000";e.addColorStop(n,o)}i=i.nextSibling}}function Qr(t,e){t&&t.__inheritedStyle&&(e.__inheritedStyle||(e.__inheritedStyle={}),r(e.__inheritedStyle,t.__inheritedStyle))}function ts(t){for(var e=P(t).split(_T),i=[],n=0;n0;a-=2){var r=o[a],s=o[a-1];switch(n=n||xt(),s){case"translate":r=P(r).split(_T),St(n,n,[parseFloat(r[0]),parseFloat(r[1]||0)]);break;case"scale":r=P(r).split(_T),It(n,n,[parseFloat(r[0]),parseFloat(r[1]||r[0])]);break;case"rotate":r=P(r).split(_T),Mt(n,n,parseFloat(r[0]));break;case"skew":r=P(r).split(_T),console.warn("Skew transform is not supported yet");break;case"matrix":r=P(r).split(_T);n[0]=parseFloat(r[0]),n[1]=parseFloat(r[1]),n[2]=parseFloat(r[2]),n[3]=parseFloat(r[3]),n[4]=parseFloat(r[4]),n[5]=parseFloat(r[5])}}e.setLocalTransform(n)}}function os(t){var e=t.getAttribute("style"),i={};if(!e)return i;var n={};TT.lastIndex=0;for(var o;null!=(o=TT.exec(e));)n[o[1]]=o[2];for(var a in ST)ST.hasOwnProperty(a)&&null!=n[a]&&(i[ST[a]]=n[a]);return i}function as(t,e,i){var n=e/t.width,o=i/t.height,a=Math.min(n,o);return{scale:[a,a],position:[-(t.x+t.width/2)*a+e/2,-(t.y+t.height/2)*a+i/2]}}function rs(t,e){return(new $r).parse(t,e)}function ss(t){return function(e,i,n){e=e&&e.toLowerCase(),fw.prototype[t].call(this,e,i,n)}}function ls(){fw.call(this)}function us(t,e,n){function o(t,e){return t.__prio-e.__prio}n=n||{},"string"==typeof e&&(e=JT[e]),this.id,this.group,this._dom=t;var a=this._zr=Ii(t,{renderer:n.renderer||"canvas",devicePixelRatio:n.devicePixelRatio,width:n.width,height:n.height});this._throttledZrFlush=Pr(m(a.flush,a),17),(e=i(e))&&BI(e,!0),this._theme=e,this._chartsViews=[],this._chartsMap={},this._componentsViews=[],this._componentsMap={},this._coordSysMgr=new Fa;var r=this._api=As(this);_e($T,o),_e(YT,o),this._scheduler=new Er(this,r,YT,$T),fw.call(this,this._ecEventProcessor=new Ds),this._messageCenter=new ls,this._initEvents(),this.resize=m(this.resize,this),this._pendingActions=[],a.animation.on("frame",this._onframe,this),vs(a,this),N(this)}function hs(t,e,i){var n,o=this._model,a=this._coordSysMgr.getCoordinateSystems();e=Vi(o,e);for(var r=0;re.get("hoverLayerThreshold")&&!U_.node&&i.traverse(function(t){t.isGroup||(t.useHoverLayer=!0)})}function Is(t,e){var i=t.get("blendMode")||null;e.group.traverse(function(t){t.isGroup||t.style.blend!==i&&t.setStyle("blend",i),t.eachPendingDisplayable&&t.eachPendingDisplayable(function(t){t.setStyle("blend",i)})})}function Ts(t,e){var i=t.get("z"),n=t.get("zlevel");e.group.traverse(function(t){"group"!==t.type&&(null!=i&&(t.z=i),null!=n&&(t.zlevel=n))})}function As(t){var e=t._coordSysMgr;return a(new Ga(t),{getCoordinateSystems:m(e.getCoordinateSystems,e),getComponentByElement:function(e){for(;e;){var i=e.__ecComponentInfo;if(null!=i)return t._model.getComponent(i.mainType,i.index);e=e.parent}}})}function Ds(){this.eventInfo}function Cs(t){function e(t,e){for(var n=0;n65535?dA:pA}function Js(t){var e=t.constructor;return e===Array?t.slice():new e(t)}function Qs(t,e){d(gA.concat(e.__wrappedMethods||[]),function(i){e.hasOwnProperty(i)&&(t[i]=e[i])}),t.__wrappedMethods=e.__wrappedMethods,d(mA,function(n){t[n]=i(e[n])}),t._calculationInfo=a(e._calculationInfo)}function tl(t,e,i,n,o){var a=cA[e.type],r=n-1,s=e.name,l=t[s][r];if(l&&l.length=0?this._indices[t]:-1}function al(t,e){var i=t._idList[e];return null==i&&(i=il(t,t._idDimIdx,e)),null==i&&(i=hA+e),i}function rl(t){return y(t)||(t=[t]),t}function sl(t,e){var i=t.dimensions,n=new vA(f(i,t.getDimensionInfo,t),t.hostModel);Qs(n,t);for(var o=n._storage={},a=t._storage,r=0;r=0?(o[s]=ll(a[s]),n._rawExtent[s]=ul(),n._extent[s]=null):o[s]=a[s])}return n}function ll(t){for(var e=new Array(t.length),i=0;in&&(r=o.interval=n);var s=o.intervalPrecision=Ml(r);return Tl(o.niceTickExtent=[MA(Math.ceil(t[0]/r)*r,s),MA(Math.floor(t[1]/r)*r,s)],t),o}function Ml(t){return Ho(t)+2}function Il(t,e,i){t[e]=Math.max(Math.min(t[e],i[1]),i[0])}function Tl(t,e){!isFinite(t[0])&&(t[0]=e[0]),!isFinite(t[1])&&(t[1]=e[1]),Il(t,0,e),Il(t,1,e),t[0]>t[1]&&(t[0]=t[1])}function Al(t,e,i,n){var o=[];if(!t)return o;e[0]1e4)return[];return e[1]>(o.length?o[o.length-1]:i[1])&&o.push(e[1]),o}function Dl(t){return t.get("stack")||AA+t.seriesIndex}function Cl(t){return t.dim+t.index}function Ll(t){var e=[],i=t.axis;if("category"===i.type){for(var n=i.getBandWidth(),o=0;o=0?"p":"n",b=m;p&&(o[r][_]||(o[r][_]={p:m,n:m}),b=o[r][_][w]);var S,M,I,T;if(g)S=b,M=(A=i.dataToPoint([x,_]))[1]+l,I=A[0]-m,T=u,Math.abs(I)a[1]?(n=a[1],o=a[0]):(n=a[0],o=a[1]);var r=e.toGlobalCoord(e.dataToCoord(0));return ro&&(r=o),r}function Vl(t,e){return VA(t,BA(e))}function Gl(t,e){var i,n,o,a=t.type,r=e.getMin(),s=e.getMax(),l=null!=r,u=null!=s,h=t.getExtent();"ordinal"===a?i=e.getCategories().length:(y(n=e.get("boundaryGap"))||(n=[n||0,n||0]),"boolean"==typeof n[0]&&(n=[0,0]),n[0]=Vo(n[0],1),n[1]=Vo(n[1],1),o=h[1]-h[0]||Math.abs(h[0])),null==r&&(r="ordinal"===a?i?0:NaN:h[0]-n[0]*o),null==s&&(s="ordinal"===a?i?i-1:NaN:h[1]+n[1]*o),"dataMin"===r?r=h[0]:"function"==typeof r&&(r=r({min:h[0],max:h[1]})),"dataMax"===s?s=h[1]:"function"==typeof s&&(s=s({min:h[0],max:h[1]})),(null==r||!isFinite(r))&&(r=NaN),(null==s||!isFinite(s))&&(s=NaN),t.setBlank(I(r)||I(s)||"ordinal"===a&&!t.getOrdinalMeta().categories.length),e.getNeedCrossZero()&&(r>0&&s>0&&!l&&(r=0),r<0&&s<0&&!u&&(s=0));var c=e.ecModel;if(c&&"time"===a){var f,p=kl("bar",c);if(d(p,function(t){f|=t.getBaseAxis()===e.axis}),f){var g=Pl(p),m=Fl(r,s,e,g);r=m.min,s=m.max}}return[r,s]}function Fl(t,e,i,n){var o=i.axis.getExtent(),a=o[1]-o[0],r=Ol(n,i.axis);if(void 0===r)return{min:t,max:e};var s=1/0;d(r,function(t){s=Math.min(t.offset,s)});var l=-1/0;d(r,function(t){l=Math.max(t.offset+t.width,l)}),s=Math.abs(s),l=Math.abs(l);var u=s+l,h=e-t,c=h/(1-(s+l)/a)-h;return e+=c*(l/u),t-=c*(s/u),{min:t,max:e}}function Wl(t,e){var i=Gl(t,e),n=null!=e.getMin(),o=null!=e.getMax(),a=e.get("splitNumber");"log"===t.type&&(t.base=e.get("logBase"));var r=t.type;t.setExtent(i[0],i[1]),t.niceExtent({splitNumber:a,fixMin:n,fixMax:o,minInterval:"interval"===r||"time"===r?e.get("minInterval"):null,maxInterval:"interval"===r||"time"===r?e.get("maxInterval"):null});var s=e.get("interval");null!=s&&t.setInterval&&t.setInterval(s)}function Hl(t,e){if(e=e||t.get("type"))switch(e){case"category":return new SA(t.getOrdinalMeta?t.getOrdinalMeta():t.getCategories(),[1/0,-1/0]);case"value":return new TA;default:return(xl.getClass(e)||TA).create(t)}}function Zl(t){var e=t.scale.getExtent(),i=e[0],n=e[1];return!(i>0&&n>0||i<0&&n<0)}function Ul(t){var e=t.getLabelModel().get("formatter"),i="category"===t.type?t.scale.getExtent()[0]:null;return"string"==typeof e?e=function(e){return function(i){return i=t.scale.getLabel(i),e.replace("{value}",null!=i?i:"")}}(e):"function"==typeof e?function(n,o){return null!=i&&(o=n-i),e(Xl(t,n),o)}:function(e){return t.scale.getLabel(e)}}function Xl(t,e){return"category"===t.type?t.scale.getLabel(e):e}function jl(t){var e=t.model,i=t.scale;if(e.get("axisLabel.show")&&!i.isBlank()){var n,o,a="category"===t.type,r=i.getExtent();o=a?i.count():(n=i.getTicks()).length;var s,l=t.getLabelModel(),u=Ul(t),h=1;o>40&&(h=Math.ceil(o/40));for(var c=0;c>1^-(1&s),l=l>>1^-(1&l),o=s+=o,a=l+=a,n.push([s/i,l/i])}return n}function ou(t){return"category"===t.type?ru(t):uu(t)}function au(t,e){return"category"===t.type?lu(t,e):{ticks:t.scale.getTicks()}}function ru(t){var e=t.getLabelModel(),i=su(t,e);return!e.get("show")||t.scale.isBlank()?{labels:[],labelCategoryInterval:i.labelCategoryInterval}:i}function su(t,e){var i=hu(t,"labels"),n=ql(e),o=cu(i,n);if(o)return o;var a,r;return a=x(n)?vu(t,n):mu(t,r="auto"===n?fu(t):n),du(i,n,{labels:a,labelCategoryInterval:r})}function lu(t,e){var i=hu(t,"ticks"),n=ql(e),o=cu(i,n);if(o)return o;var a,r;if(e.get("show")&&!t.scale.isBlank()||(a=[]),x(n))a=vu(t,n,!0);else if("auto"===n){var s=su(t,t.getLabelModel());r=s.labelCategoryInterval,a=f(s.labels,function(t){return t.tickValue})}else a=mu(t,r=n,!0);return du(i,n,{ticks:a,tickCategoryInterval:r})}function uu(t){var e=t.scale.getTicks(),i=Ul(t);return{labels:f(e,function(e,n){return{formattedLabel:i(e,n),rawLabel:t.scale.getLabel(e),tickValue:e}})}}function hu(t,e){return nD(t)[e]||(nD(t)[e]=[])}function cu(t,e){for(var i=0;i40&&(s=Math.max(1,Math.floor(r/40)));for(var l=a[0],u=t.dataToCoord(l+1)-t.dataToCoord(l),h=Math.abs(u*Math.cos(n)),c=Math.abs(u*Math.sin(n)),d=0,f=0;l<=a[1];l+=s){var p=0,g=0,m=ke(i(l),e.font,"center","top");p=1.3*m.width,g=1.3*m.height,d=Math.max(d,p,7),f=Math.max(f,g,7)}var v=d/h,y=f/c;isNaN(v)&&(v=1/0),isNaN(y)&&(y=1/0);var x=Math.max(0,Math.floor(Math.min(v,y))),_=nD(t.model),w=_.lastAutoInterval,b=_.lastTickCount;return null!=w&&null!=b&&Math.abs(w-x)<=1&&Math.abs(b-r)<=1&&w>x?x=w:(_.lastTickCount=r,_.lastAutoInterval=x),x}function gu(t){var e=t.getLabelModel();return{axisRotate:t.getRotate?t.getRotate():t.isHorizontal&&!t.isHorizontal()?90:0,labelRotate:e.get("rotate")||0,font:e.getFont()}}function mu(t,e,i){function n(t){l.push(i?t:{formattedLabel:o(t),rawLabel:a.getLabel(t),tickValue:t})}var o=Ul(t),a=t.scale,r=a.getExtent(),s=t.getLabelModel(),l=[],u=Math.max((e||0)+1,1),h=r[0],c=a.count();0!==h&&u>1&&c/u>2&&(h=Math.round(Math.ceil(h/u)*u));var d=Kl(t),f=s.get("showMinLabel")||d,p=s.get("showMaxLabel")||d;f&&h!==r[0]&&n(r[0]);for(var g=h;g<=r[1];g+=u)n(g);return p&&g!==r[1]&&n(r[1]),l}function vu(t,e,i){var n=t.scale,o=Ul(t),a=[];return d(n.getTicks(),function(t){var r=n.getLabel(t);e(t,r)&&a.push(i?t:{formattedLabel:o(t),rawLabel:r,tickValue:t})}),a}function yu(t,e){var i=(t[1]-t[0])/e/2;t[0]+=i,t[1]-=i}function xu(t,e,i,n,o){function a(t,e){return h?t>e:t0&&(t.coord-=u/(2*(e+1)))}),s={coord:e[r-1].coord+u},e.push(s)}var h=l[0]>l[1];a(e[0].coord,l[0])&&(o?e[0].coord=l[0]:e.shift()),o&&a(l[0],e[0].coord)&&e.unshift({coord:l[0]}),a(l[1],s.coord)&&(o?s.coord=l[1]:e.pop()),o&&a(s.coord,l[1])&&e.push({coord:l[1]})}}function _u(t,e){var i=t.mapDimension("defaultedLabel",!0),n=i.length;if(1===n)return fr(t,e,i[0]);if(n){for(var o=[],a=0;a0?i=n[0]:n[1]<0&&(i=n[1]),i}function Ou(t,e,i,n){var o=NaN;t.stacked&&(o=i.get(i.getCalculationInfo("stackedOverDimension"),n)),isNaN(o)&&(o=t.valueStart);var a=t.baseDataOffset,r=[];return r[a]=i.get(t.baseDim,n),r[1-a]=o,e.dataToPoint(r)}function Eu(t,e){var i=[];return e.diff(t).add(function(t){i.push({cmd:"+",idx:t})}).update(function(t,e){i.push({cmd:"=",idx:e,idx1:t})}).remove(function(t){i.push({cmd:"-",idx:t})}).execute(),i}function Ru(t){return isNaN(t[0])||isNaN(t[1])}function zu(t,e,i,n,o,a,r,s,l,u,h){return"none"!==u&&u?Bu.apply(this,arguments):Vu.apply(this,arguments)}function Bu(t,e,i,n,o,a,r,s,l,u,h){for(var c=0,d=i,f=0;f=o||d<0)break;if(Ru(p)){if(h){d+=a;continue}break}if(d===i)t[a>0?"moveTo":"lineTo"](p[0],p[1]);else if(l>0){var g=e[c],m="y"===u?1:0,v=(p[m]-g[m])*l;_D(bD,g),bD[m]=g[m]+v,_D(SD,p),SD[m]=p[m]-v,t.bezierCurveTo(bD[0],bD[1],SD[0],SD[1],p[0],p[1])}else t.lineTo(p[0],p[1]);c=d,d+=a}return f}function Vu(t,e,i,n,o,a,r,s,l,u,h){for(var c=0,d=i,f=0;f=o||d<0)break;if(Ru(p)){if(h){d+=a;continue}break}if(d===i)t[a>0?"moveTo":"lineTo"](p[0],p[1]),_D(bD,p);else if(l>0){var g=d+a,m=e[g];if(h)for(;m&&Ru(e[g]);)m=e[g+=a];var v=.5,y=e[c];if(!(m=e[g])||Ru(m))_D(SD,p);else{Ru(m)&&!h&&(m=p),U(wD,m,y);var x,_;if("x"===u||"y"===u){var w="x"===u?0:1;x=Math.abs(p[w]-y[w]),_=Math.abs(p[w]-m[w])}else x=uw(p,y),_=uw(p,m);xD(SD,p,wD,-l*(1-(v=_/(_+x))))}vD(bD,bD,s),yD(bD,bD,r),vD(SD,SD,s),yD(SD,SD,r),t.bezierCurveTo(bD[0],bD[1],SD[0],SD[1],p[0],p[1]),xD(bD,p,wD,l*v)}else t.lineTo(p[0],p[1]);c=d,d+=a}return f}function Gu(t,e){var i=[1/0,1/0],n=[-1/0,-1/0];if(e)for(var o=0;on[0]&&(n[0]=a[0]),a[1]>n[1]&&(n[1]=a[1])}return{min:e?i:n,max:e?n:i}}function Fu(t,e){if(t.length===e.length){for(var i=0;ie[0]?1:-1;e[0]+=n*i,e[1]-=n*i}return e}function Zu(t,e,i){if(!i.valueDim)return[];for(var n=[],o=0,a=e.count();oa[1]&&a.reverse();var r=o.getExtent(),s=Math.PI/180;i&&(a[0]-=.5,a[1]+=.5);var l=new hM({shape:{cx:Go(t.cx,1),cy:Go(t.cy,1),r0:Go(a[0],1),r:Go(a[1],1),startAngle:-r[0]*s,endAngle:-r[1]*s,clockwise:o.inverse}});return e&&(l.shape.endAngle=-r[0]*s,To(l,{shape:{endAngle:-r[1]*s}},n)),l}function ju(t,e,i,n){return"polar"===t.type?Xu(t,e,i,n):Uu(t,e,i,n)}function Yu(t,e,i){for(var n=e.getBaseAxis(),o="x"===n.dim||"radius"===n.dim?0:1,a=[],r=0;r=0;a--){var r=i[a].dimension,s=t.dimensions[r],l=t.getDimensionInfo(s);if("x"===(n=l&&l.coordDim)||"y"===n){o=i[a];break}}if(o){var u=e.getAxis(n),h=f(o.stops,function(t){return{coord:u.toGlobalCoord(u.dataToCoord(t.value)),color:t.color}}),c=h.length,p=o.outerColors.slice();c&&h[0].coord>h[c-1].coord&&(h.reverse(),p.reverse());var g=h[0].coord-10,m=h[c-1].coord+10,v=m-g;if(v<.001)return"transparent";d(h,function(t){t.offset=(t.coord-g)/v}),h.push({offset:c?h[c-1].offset:.5,color:p[1]||"transparent"}),h.unshift({offset:c?h[0].offset:.5,color:p[0]||"transparent"});var y=new TM(0,0,0,0,h,!0);return y[n]=g,y[n+"2"]=m,y}}}function Ku(t,e,i){var n=t.get("showAllSymbol"),o="auto"===n;if(!n||o){var a=i.getAxesByScale("ordinal")[0];if(a&&(!o||!$u(a,e))){var r=e.mapDimension(a.dim),s={};return d(a.getViewLabels(),function(t){s[t.tickValue]=1}),function(t){return!s.hasOwnProperty(e.get(r,t))}}}}function $u(t,e){var i=t.getExtent(),n=Math.abs(i[1]-i[0])/t.scale.count();isNaN(n)&&(n=0);for(var o=e.count(),a=Math.max(1,Math.round(o/5)),r=0;rn)return!1;return!0}function Ju(t){return this._axes[t]}function Qu(t){LD.call(this,t)}function th(t,e){return e.type||(e.data?"category":"value")}function eh(t,e,i){return t.getCoordSysModel()===e}function ih(t,e,i){this._coordsMap={},this._coordsList=[],this._axesMap={},this._axesList=[],this._initCartesian(t,e,i),this.model=t}function nh(t,e,i,n){function o(t){return t.dim+"_"+t.index}i.getAxesOnZeroOf=function(){return a?[a]:[]};var a,r=t[e],s=i.model,l=s.get("axisLine.onZero"),u=s.get("axisLine.onZeroAxisIndex");if(l){if(null!=u)oh(r[u])&&(a=r[u]);else for(var h in r)if(r.hasOwnProperty(h)&&oh(r[h])&&!n[o(r[h])]){a=r[h];break}a&&(n[o(a)]=!0)}}function oh(t){return t&&"category"!==t.type&&"time"!==t.type&&Zl(t)}function ah(t,e){var i=t.getExtent(),n=i[0]+i[1];t.toGlobalCoord="x"===t.dim?function(t){return t+e}:function(t){return n-t+e},t.toLocalCoord="x"===t.dim?function(t){return t-e}:function(t){return n-t+e}}function rh(t,e){return f(VD,function(e){return t.getReferringComponents(e)[0]})}function sh(t){return"cartesian2d"===t.get("coordinateSystem")}function lh(t){var e={componentType:t.mainType,componentIndex:t.componentIndex};return e[t.mainType+"Index"]=t.componentIndex,e}function uh(t,e,i,n){var o,a,r=Xo(i-t.rotation),s=n[0]>n[1],l="start"===e&&!s||"start"!==e&&s;return jo(r-GD/2)?(a=l?"bottom":"top",o="center"):jo(r-1.5*GD)?(a=l?"top":"bottom",o="center"):(a="middle",o=r<1.5*GD&&r>GD/2?l?"left":"right":l?"right":"left"),{rotation:r,textAlign:o,textVerticalAlign:a}}function hh(t){var e=t.get("tooltip");return t.get("silent")||!(t.get("triggerEvent")||e&&e.show)}function ch(t,e,i){if(!Kl(t.axis)){var n=t.get("axisLabel.showMinLabel"),o=t.get("axisLabel.showMaxLabel");e=e||[],i=i||[];var a=e[0],r=e[1],s=e[e.length-1],l=e[e.length-2],u=i[0],h=i[1],c=i[i.length-1],d=i[i.length-2];!1===n?(dh(a),dh(u)):fh(a,r)&&(n?(dh(r),dh(h)):(dh(a),dh(u))),!1===o?(dh(s),dh(c)):fh(l,s)&&(o?(dh(l),dh(d)):(dh(s),dh(c)))}}function dh(t){t&&(t.ignore=!0)}function fh(t,e,i){var n=t&&t.getBoundingRect().clone(),o=e&&e.getBoundingRect().clone();if(n&&o){var a=_t([]);return Mt(a,a,-t.rotation),n.applyTransform(bt([],a,t.getLocalTransform())),o.applyTransform(bt([],a,e.getLocalTransform())),n.intersect(o)}}function ph(t){return"middle"===t||"center"===t}function gh(t,e,i){var n=e.axis;if(e.get("axisTick.show")&&!n.scale.isBlank()){for(var o=e.getModel("axisTick"),a=o.getModel("lineStyle"),s=o.get("length"),l=n.getTicksCoords(),u=[],h=[],c=t._transform,d=[],f=0;f=0||t===e}function Sh(t){var e=Mh(t);if(e){var i=e.axisPointerModel,n=e.axis.scale,o=i.option,a=i.get("status"),r=i.get("value");null!=r&&(r=n.parse(r));var s=Th(i);null==a&&(o.status=s?"show":"hide");var l=n.getExtent().slice();l[0]>l[1]&&l.reverse(),(null==r||r>l[1])&&(r=l[1]),r0?"bottom":"top":o.width>0?"left":"right";l||kh(t.style,d,n,u,a,i,p),fo(t,d)}function Rh(t,e){var i=t.get(tC)||0;return Math.min(i,Math.abs(e.width),Math.abs(e.height))}function zh(t,e,i){var n=t.getData(),o=[],a=n.getLayout("valueAxisHorizontal")?1:0;o[1-a]=n.getLayout("valueAxisStart");var r=new nC({shape:{points:n.getLayout("largePoints")},incremental:!!i,__startPoint:o,__valueIdx:a});e.add(r),Bh(r,t,n)}function Bh(t,e,i){var n=i.getVisual("borderColor")||i.getVisual("color"),o=e.getModel("itemStyle").getItemStyle(["color","borderColor"]);t.useStyle(o),t.style.fill=null,t.style.stroke=n,t.style.lineWidth=i.getLayout("barWidth")}function Vh(t,e,i,n){var o=e.getData(),a=this.dataIndex,r=o.getName(a),s=e.get("selectedOffset");n.dispatchAction({type:"pieToggleSelect",from:t,name:r,seriesId:e.id}),o.each(function(t){Gh(o.getItemGraphicEl(t),o.getItemLayout(t),e.isSelected(o.getName(t)),s,i)})}function Gh(t,e,i,n,o){var a=(e.startAngle+e.endAngle)/2,r=Math.cos(a),s=Math.sin(a),l=i?n:0,u=[r*l,s*l];o?t.animate().when(200,{position:u}).start("bounceOut"):t.attr("position",u)}function Fh(t,e){function i(){a.ignore=a.hoverIgnore,r.ignore=r.hoverIgnore}function n(){a.ignore=a.normalIgnore,r.ignore=r.normalIgnore}tb.call(this);var o=new hM({z2:2}),a=new gM,r=new rM;this.add(o),this.add(a),this.add(r),this.updateData(t,e,!0),this.on("emphasis",i).on("normal",n).on("mouseover",i).on("mouseout",n)}function Wh(t,e,i,n,o,a,r){function s(e,i){for(var n=e;n>=0&&(t[n].y-=i,!(n>0&&t[n].y>t[n-1].y+t[n-1].height));n--);}function l(t,e,i,n,o,a){for(var r=e?Number.MAX_VALUE:0,s=0,l=t.length;s=r&&(d=r-10),!e&&d<=r&&(d=r+10),t[s].x=i+d*a,r=d}}t.sort(function(t,e){return t.y-e.y});for(var u,h=0,c=t.length,d=[],f=[],p=0;pe&&a+1t[a].y+t[a].height)return void s(a,n/2);s(i-1,n/2)}(p,c,-u),h=t[p].y+t[p].height;r-h<0&&s(c-1,h-r);for(p=0;p=i?f.push(t[p]):d.push(t[p]);l(d,!1,e,i,n,o),l(f,!0,e,i,n,o)}function Hh(t,e,i,n,o,a){for(var r=[],s=[],l=0;l3?1.4:o>1?1.2:1.1;hc(this,"zoom","zoomOnMouseWheel",t,{scale:n>0?s:1/s,originX:a,originY:r})}if(i){var l=Math.abs(n);hc(this,"scrollMove","moveOnMouseWheel",t,{scrollDelta:(n>0?1:-1)*(l>3?.4:l>1?.15:.05),originX:a,originY:r})}}}function uc(t){ic(this._zr,"globalPan")||hc(this,"zoom",null,t,{scale:t.pinchScale>1?1.1:1/1.1,originX:t.pinchX,originY:t.pinchY})}function hc(t,e,i,n,o){t.pointerChecker&&t.pointerChecker(n,o.originX,o.originY)&&(mw(n.event),cc(t,e,i,n,o))}function cc(t,e,i,n,o){o.isAvailableBehavior=m(dc,null,i,n),t.trigger(e,o)}function dc(t,e,i){var n=i[t];return!t||n&&(!_(n)||e.event[n+"Key"])}function fc(t,e,i){var n=t.target,o=n.position;o[0]+=e,o[1]+=i,n.dirty()}function pc(t,e,i,n){var o=t.target,a=t.zoomLimit,r=o.position,s=o.scale,l=t.zoom=t.zoom||1;if(l*=e,a){var u=a.min||0,h=a.max||1/0;l=Math.max(Math.min(h,l),u)}var c=l/t.zoom;t.zoom=l,r[0]-=(i-r[0])*(c-1),r[1]-=(n-r[1])*(c-1),s[0]*=c,s[1]*=c,o.dirty()}function gc(t,e,i){var n=e.getComponentByElement(t.topTarget),o=n&&n.coordinateSystem;return n&&n!==i&&!RC[n.mainType]&&o&&o.model!==i}function mc(t,e){var i=t.getItemStyle(),n=t.get("areaColor");return null!=n&&(i.fill=n),i}function vc(t,e,i,n,o){i.off("click"),i.off("mousedown"),e.get("selectedMode")&&(i.on("mousedown",function(){t._mouseDownFlag=!0}),i.on("click",function(a){if(t._mouseDownFlag){t._mouseDownFlag=!1;for(var r=a.target;!r.__regions;)r=r.parent;if(r){var s={type:("geo"===e.mainType?"geo":"map")+"ToggleSelect",batch:f(r.__regions,function(t){return{name:t.name,from:o.uid}})};s[e.mainType+"Id"]=e.id,n.dispatchAction(s),yc(e,i)}}}))}function yc(t,e){e.eachChild(function(e){d(e.__regions,function(i){e.trigger(t.isSelected(i.name)?"emphasis":"normal")})})}function xc(t,e){var i=new tb;this.uid=Ro("ec_map_draw"),this._controller=new oc(t.getZr()),this._controllerHost={target:e?i:null},this.group=i,this._updateGroup=e,this._mouseDownFlag,this._mapName,this._initialized,i.add(this._regionsGroup=new tb),i.add(this._backgroundGroup=new tb)}function _c(t){var e=this[zC];e&&e.recordVersion===this[BC]&&wc(e,t)}function wc(t,e){var i=t.circle,n=t.labelModel,o=t.hoverLabelModel,a=t.emphasisText,r=t.normalText;e?(i.style.extendFrom(mo({},o,{text:o.get("show")?a:null},{isRectText:!0,useInsideStyle:!1},!0)),i.__mapOriginalZ2=i.z2,i.z2+=NM):(mo(i.style,n,{text:n.get("show")?r:null,textPosition:n.getShallow("position")||"bottom"},{isRectText:!0,useInsideStyle:!1}),i.dirty(!1),null!=i.__mapOriginalZ2&&(i.z2=i.__mapOriginalZ2,i.__mapOriginalZ2=null))}function bc(t,e,i){var n=t.getZoom(),o=t.getCenter(),a=e.zoom,r=t.dataToPoint(o);if(null!=e.dx&&null!=e.dy){r[0]-=e.dx,r[1]-=e.dy;o=t.pointToData(r);t.setCenter(o)}if(null!=a){if(i){var s=i.min||0,l=i.max||1/0;a=Math.max(Math.min(n*a,l),s)/n}t.scale[0]*=a,t.scale[1]*=a;var u=t.position,h=(e.originX-u[0])*(a-1),c=(e.originY-u[1])*(a-1);u[0]-=h,u[1]-=c,t.updateTransform();o=t.pointToData(r);t.setCenter(o),t.setZoom(a*n)}return{center:t.getCenter(),zoom:t.getZoom()}}function Sc(){Tw.call(this)}function Mc(t){this.name=t,this.zoomLimit,Tw.call(this),this._roamTransformable=new Sc,this._rawTransformable=new Sc,this._center,this._zoom}function Ic(t,e,i,n){var o=i.seriesModel,a=o?o.coordinateSystem:null;return a===this?a[t](n):null}function Tc(t,e,i,n){Mc.call(this,t),this.map=e;var o=OC.load(e,i);this._nameCoordMap=o.nameCoordMap,this._regionsMap=o.regionsMap,this._invertLongitute=null==n||n,this.regions=o.regions,this._rect=o.boundingRect}function Ac(t,e,i,n){var o=i.geoModel,a=i.seriesModel,r=o?o.coordinateSystem:a?a.coordinateSystem||(a.getReferringComponents("geo")[0]||{}).coordinateSystem:null;return r===this?r[t](n):null}function Dc(t,e){var i=t.get("boundingCoords");if(null!=i){var n=i[0],o=i[1];isNaN(n[0])||isNaN(n[1])||isNaN(o[0])||isNaN(o[1])||this.setBoundingRect(n[0],n[1],o[0]-n[0],o[1]-n[1])}var a,r=this.getBoundingRect(),s=t.get("layoutCenter"),l=t.get("layoutSize"),u=e.getWidth(),h=e.getHeight(),c=r.width/r.height*this.aspectScale,d=!1;s&&l&&(s=[Vo(s[0],u),Vo(s[1],h)],l=Vo(l,Math.min(u,h)),isNaN(s[0])||isNaN(s[1])||isNaN(l)||(d=!0));if(d){var f={};c>1?(f.width=l,f.height=l/c):(f.height=l,f.width=l*c),f.y=s[1]-f.height/2,f.x=s[0]-f.width/2}else(a=t.getBoxLayoutParams()).aspect=c,f=ca(a,{width:u,height:h});this.setViewRect(f.x,f.y,f.width,f.height),this.setCenter(t.get("center")),this.setZoom(t.get("zoom"))}function Cc(t,e){d(e.get("geoCoord"),function(e,i){t.addGeoCoord(i,e)})}function Lc(t,e){var i={};return d(t,function(t){t.each(t.mapDimension("value"),function(e,n){var o="ec-"+t.getName(n);i[o]=i[o]||[],isNaN(e)||i[o].push(e)})}),t[0].map(t[0].mapDimension("value"),function(n,o){for(var a="ec-"+t[0].getName(o),r=0,s=1/0,l=-1/0,u=i[a].length,h=0;h=0;o--){var a=i[o];a.hierNode={defaultAncestor:null,ancestor:a,prelim:0,modifier:0,change:0,shift:0,i:o,thread:null},n.push(a)}}function Wc(t,e){var i=t.isExpand?t.children:[],n=t.parentNode.children,o=t.hierNode.i?n[t.hierNode.i-1]:null;if(i.length){jc(t);var a=(i[0].hierNode.prelim+i[i.length-1].hierNode.prelim)/2;o?(t.hierNode.prelim=o.hierNode.prelim+e(t,o),t.hierNode.modifier=t.hierNode.prelim-a):t.hierNode.prelim=a}else o&&(t.hierNode.prelim=o.hierNode.prelim+e(t,o));t.parentNode.hierNode.defaultAncestor=Yc(t,o,t.parentNode.hierNode.defaultAncestor||n[0],e)}function Hc(t){var e=t.hierNode.prelim+t.parentNode.hierNode.modifier;t.setLayout({x:e},!0),t.hierNode.modifier+=t.parentNode.hierNode.modifier}function Zc(t){return arguments.length?t:Qc}function Uc(t,e){var i={};return t-=Math.PI/2,i.x=e*Math.cos(t),i.y=e*Math.sin(t),i}function Xc(t,e){return ca(t.getBoxLayoutParams(),{width:e.getWidth(),height:e.getHeight()})}function jc(t){for(var e=t.children,i=e.length,n=0,o=0;--i>=0;){var a=e[i];a.hierNode.prelim+=n,a.hierNode.modifier+=n,o+=a.hierNode.change,n+=a.hierNode.shift+o}}function Yc(t,e,i,n){if(e){for(var o=t,a=t,r=a.parentNode.children[0],s=e,l=o.hierNode.modifier,u=a.hierNode.modifier,h=r.hierNode.modifier,c=s.hierNode.modifier;s=qc(s),a=Kc(a),s&&a;){o=qc(o),r=Kc(r),o.hierNode.ancestor=t;var d=s.hierNode.prelim+c-a.hierNode.prelim-u+n(s,a);d>0&&(Jc($c(s,t,i),t,d),u+=d,l+=d),c+=s.hierNode.modifier,u+=a.hierNode.modifier,l+=o.hierNode.modifier,h+=r.hierNode.modifier}s&&!qc(o)&&(o.hierNode.thread=s,o.hierNode.modifier+=c-l),a&&!Kc(r)&&(r.hierNode.thread=a,r.hierNode.modifier+=u-h,i=t)}return i}function qc(t){var e=t.children;return e.length&&t.isExpand?e[e.length-1]:t.hierNode.thread}function Kc(t){var e=t.children;return e.length&&t.isExpand?e[0]:t.hierNode.thread}function $c(t,e,i){return t.hierNode.ancestor.parentNode===e.parentNode?t.hierNode.ancestor:i}function Jc(t,e,i){var n=i/(e.hierNode.i-t.hierNode.i);e.hierNode.change-=n,e.hierNode.shift+=i,e.hierNode.modifier+=i,e.hierNode.prelim+=i,t.hierNode.change+=n}function Qc(t,e){return t.parentNode===e.parentNode?1:2}function td(t,e){var i=t.getItemLayout(e);return i&&!isNaN(i.x)&&!isNaN(i.y)&&"none"!==t.getItemVisual(e,"symbol")}function ed(t,e,i){return i.itemModel=e,i.itemStyle=e.getModel("itemStyle").getItemStyle(),i.hoverItemStyle=e.getModel("emphasis.itemStyle").getItemStyle(),i.lineStyle=e.getModel("lineStyle").getLineStyle(),i.labelModel=e.getModel("label"),i.hoverLabelModel=e.getModel("emphasis.label"),!1===t.isExpand&&0!==t.children.length?i.symbolInnerColor=i.itemStyle.fill:i.symbolInnerColor="#fff",i}function id(t,e,i,n,o,a){var s=!i,l=t.tree.getNodeByDataIndex(e),a=ed(l,l.getModel(),a),u=t.tree.root,h=l.parentNode===u?l:l.parentNode||l,c=t.getItemGraphicEl(h.dataIndex),d=h.getLayout(),f=c?{x:c.position[0],y:c.position[1],rawX:c.__radialOldRawX,rawY:c.__radialOldRawY}:d,p=l.getLayout();s?(i=new wu(t,e,a)).attr("position",[f.x,f.y]):i.updateData(t,e,a),i.__radialOldRawX=i.__radialRawX,i.__radialOldRawY=i.__radialRawY,i.__radialRawX=p.rawX,i.__radialRawY=p.rawY,n.add(i),t.setItemGraphicEl(e,i),Io(i,{position:[p.x,p.y]},o);var g=i.getSymbolPath();if("radial"===a.layout){var m,v,y=u.children[0],x=y.getLayout(),_=y.children.length;if(p.x===x.x&&!0===l.isExpand){var w={};w.x=(y.children[0].getLayout().x+y.children[_-1].getLayout().x)/2,w.y=(y.children[0].getLayout().y+y.children[_-1].getLayout().y)/2,(m=Math.atan2(w.y-x.y,w.x-x.x))<0&&(m=2*Math.PI+m),(v=w.xx.x)||(m-=Math.PI);var b=v?"left":"right";g.setStyle({textPosition:b,textRotation:-m,textOrigin:"center",verticalAlign:"middle"})}if(l.parentNode&&l.parentNode!==u){var S=i.__edge;S||(S=i.__edge=new bM({shape:od(a,f,f),style:r({opacity:0,strokeNoScale:!0},a.lineStyle)})),Io(S,{shape:od(a,d,p),style:{opacity:1}},o),n.add(S)}}function nd(t,e,i,n,o,a){for(var r,s=t.tree.getNodeByDataIndex(e),l=t.tree.root,a=ed(s,s.getModel(),a),u=s.parentNode===l?s:s.parentNode||s;null==(r=u.getLayout());)u=u.parentNode===l?u:u.parentNode||u;Io(i,{position:[r.x+1,r.y+1]},o,function(){n.remove(i),t.setItemGraphicEl(e,null)}),i.fadeOut(null,{keepLabel:!0});var h=i.__edge;h&&Io(h,{shape:od(a,r,r),style:{opacity:0}},o,function(){n.remove(h)})}function od(t,e,i){var n,o,a,r,s,l,u,h,c=t.orient;if("radial"===t.layout){s=e.rawX,u=e.rawY,l=i.rawX,h=i.rawY;var d=Uc(s,u),f=Uc(s,u+(h-u)*t.curvature),p=Uc(l,h+(u-h)*t.curvature),g=Uc(l,h);return{x1:d.x,y1:d.y,x2:g.x,y2:g.y,cpx1:f.x,cpy1:f.y,cpx2:p.x,cpy2:p.y}}return s=e.x,u=e.y,l=i.x,h=i.y,"LR"!==c&&"RL"!==c||(n=s+(l-s)*t.curvature,o=u,a=l+(s-l)*t.curvature,r=h),"TB"!==c&&"BT"!==c||(n=s,o=u+(h-u)*t.curvature,a=l,r=h+(u-h)*t.curvature),{x1:s,y1:u,x2:l,y2:h,cpx1:n,cpy1:o,cpx2:a,cpy2:r}}function ad(t,e,i){for(var n,o=[t],a=[];n=o.pop();)if(a.push(n),n.isExpand){var r=n.children;if(r.length)for(var s=0;s=0;a--)n.push(o[a])}}function sd(t,e){var i=Xc(t,e);t.layoutInfo=i;var n=t.get("layout"),o=0,a=0,r=null;"radial"===n?(o=2*Math.PI,a=Math.min(i.height,i.width)/2,r=Zc(function(t,e){return(t.parentNode===e.parentNode?1:2)/t.depth})):(o=i.width,a=i.height,r=Zc());var s=t.getData().tree.root,l=s.children[0];if(l){Fc(s),ad(l,Wc,r),s.hierNode.modifier=-l.hierNode.prelim,rd(l,Hc);var u=l,h=l,c=l;rd(l,function(t){var e=t.getLayout().x;eh.getLayout().x&&(h=t),t.depth>c.depth&&(c=t)});var d=u===h?1:r(u,h)/2,f=d-u.getLayout().x,p=0,g=0,m=0,v=0;if("radial"===n)p=o/(h.getLayout().x+d+f),g=a/(c.depth-1||1),rd(l,function(t){m=(t.getLayout().x+f)*p,v=(t.depth-1)*g;var e=Uc(m,v);t.setLayout({x:e.x,y:e.y,rawX:m,rawY:v},!0)});else{var y=t.getOrient();"RL"===y||"LR"===y?(g=a/(h.getLayout().x+d+f),p=o/(c.depth-1||1),rd(l,function(t){v=(t.getLayout().x+f)*g,m="LR"===y?(t.depth-1)*p:o-(t.depth-1)*p,t.setLayout({x:m,y:v},!0)})):"TB"!==y&&"BT"!==y||(p=o/(h.getLayout().x+d+f),g=a/(c.depth-1||1),rd(l,function(t){m=(t.getLayout().x+f)*p,v="TB"===y?(t.depth-1)*g:a-(t.depth-1)*g,t.setLayout({x:m,y:v},!0)}))}}}function ld(t,e,i){if(t&&l(e,t.type)>=0){var n=i.getData().tree.root,o=t.targetNode;if("string"==typeof o&&(o=n.getNodeById(o)),o&&n.contains(o))return{node:o};var a=t.targetNodeId;if(null!=a&&(o=n.getNodeById(a)))return{node:o}}}function ud(t){for(var e=[];t;)(t=t.parentNode)&&e.push(t);return e.reverse()}function hd(t,e){return l(ud(t),e)>=0}function cd(t,e){for(var i=[];t;){var n=t.dataIndex;i.push({name:t.name,dataIndex:n,value:e.getRawValue(n)}),t=t.parentNode}return i.reverse(),i}function dd(t){var e=0;d(t.children,function(t){dd(t);var i=t.value;y(i)&&(i=i[0]),e+=i});var i=t.value;y(i)&&(i=i[0]),(null==i||isNaN(i))&&(i=e),i<0&&(i=0),y(t.value)?t.value[0]=i:t.value=i}function fd(t,e){var i=e.get("color");if(i){var n;return d(t=t||[],function(t){var e=new No(t),i=e.get("color");(e.get("itemStyle.color")||i&&"none"!==i)&&(n=!0)}),n||((t[0]||(t[0]={})).color=i.slice()),t}}function pd(t){this.group=new tb,t.add(this.group)}function gd(t,e,i,n,o,a){var r=[[o?t:t-UC,e],[t+i,e],[t+i,e+n],[o?t:t-UC,e+n]];return!a&&r.splice(2,0,[t+i+UC,e+n/2]),!o&&r.push([t,e+n/2]),r}function md(t,e,i){t.eventData={componentType:"series",componentSubType:"treemap",componentIndex:e.componentIndex,seriesIndex:e.componentIndex,seriesName:e.name,seriesType:"treemap",selfType:"breadcrumb",nodeData:{dataIndex:i&&i.dataIndex,name:i&&i.name},treePathInfo:i&&cd(i,e)}}function vd(){var t,e=[],i={};return{add:function(t,n,o,a,r){return _(a)&&(r=a,a=0),!i[t.id]&&(i[t.id]=1,e.push({el:t,target:n,time:o,delay:a,easing:r}),!0)},done:function(e){return t=e,this},start:function(){for(var n=e.length,o=0,a=e.length;o=0;a--)null==i[a]&&(delete n[e[a]],e.pop())}function bd(t,e){var i=t.visual,n=[];w(i)?sL(i,function(t){n.push(t)}):null!=i&&n.push(i);var o={color:1,symbol:1};e||1!==n.length||o.hasOwnProperty(t.type)||(n[1]=n[0]),Ld(t,n)}function Sd(t){return{applyVisual:function(e,i,n){e=this.mapValueToVisual(e),n("color",t(i("color"),e))},_doMap:Dd([0,1])}}function Md(t){var e=this.option.visual;return e[Math.round(Bo(t,[0,1],[0,e.length-1],!0))]||{}}function Id(t){return function(e,i,n){n(t,this.mapValueToVisual(e))}}function Td(t){var e=this.option.visual;return e[this.option.loop&&t!==uL?t%e.length:t]}function Ad(){return this.option.visual[0]}function Dd(t){return{linear:function(e){return Bo(e,t,this.option.visual,!0)},category:Td,piecewise:function(e,i){var n=Cd.call(this,i);return null==n&&(n=Bo(e,t,this.option.visual,!0)),n},fixed:Ad}}function Cd(t){var e=this.option,i=e.pieceList;if(e.hasSpecialVisual){var n=i[hL.findPieceIndex(t,i)];if(n&&n.visual)return n.visual[this.type]}}function Ld(t,e){return t.visual=e,"color"===t.type&&(t.parsedVisual=f(e,function(t){return Gt(t)})),e}function kd(t,e,i){return t?e<=i:e=o.length||t===o[t.depth])&&Pd(t,Vd(r,h,t,e,g,a),i,n,o,a)})}else l=Od(h),t.setVisual("color",l)}}function Nd(t,e,i,n){var o=a({},e);return d(["color","colorAlpha","colorSaturation"],function(a){var r=t.get(a,!0);null==r&&i&&(r=i[a]),null==r&&(r=e[a]),null==r&&(r=n.get(a)),null!=r&&(o[a]=r)}),o}function Od(t){var e=Rd(t,"color");if(e){var i=Rd(t,"colorAlpha"),n=Rd(t,"colorSaturation");return n&&(e=jt(e,null,null,n)),i&&(e=Yt(e,i)),e}}function Ed(t,e){return null!=e?jt(e,null,null,t):null}function Rd(t,e){var i=t[e];if(null!=i&&"none"!==i)return i}function zd(t,e,i,n,o,a){if(a&&a.length){var r=Bd(e,"color")||null!=o.color&&"none"!==o.color&&(Bd(e,"colorAlpha")||Bd(e,"colorSaturation"));if(r){var s=e.get("visualMin"),l=e.get("visualMax"),u=i.dataExtent.slice();null!=s&&su[1]&&(u[1]=l);var h=e.get("colorMappingBy"),c={type:r.name,dataExtent:u,visual:r.range};"color"!==c.type||"index"!==h&&"id"!==h?c.mappingMethod="linear":(c.mappingMethod="category",c.loop=!0);var d=new hL(c);return d.__drColorMappingBy=h,d}}}function Bd(t,e){var i=t.get(e);return fL(i)&&i.length?{name:e,range:i}:null}function Vd(t,e,i,n,o,r){var s=a({},e);if(o){var l=o.type,u="color"===l&&o.__drColorMappingBy,h="index"===u?n:"id"===u?r.mapIdToIndex(i.getId()):i.getValue(t.get("visualDimension"));s[l]=o.mapValueToVisual(h)}return s}function Gd(t,e,i,n){var o,a;if(!t.isRemoved()){var r=t.getLayout();o=r.width,a=r.height;var s=(f=t.getModel()).get(_L),l=f.get(wL)/2,u=Kd(f),h=Math.max(s,u),c=s-l,d=h-l,f=t.getModel();t.setLayout({borderWidth:s,upperHeight:h,upperLabelHeight:u},!0);var p=(o=mL(o-2*c,0))*(a=mL(a-c-d,0)),g=Fd(t,f,p,e,i,n);if(g.length){var m={x:c,y:d,width:o,height:a},v=vL(o,a),y=1/0,x=[];x.area=0;for(var _=0,w=g.length;_=0;l--){var u=o["asc"===n?r-l-1:l].getValue();u/i*es[1]&&(s[1]=e)})}else s=[NaN,NaN];return{sum:n,dataExtent:s}}function Ud(t,e,i){for(var n,o=0,a=1/0,r=0,s=t.length;ro&&(o=n));var l=t.area*t.area,u=e*e*i;return l?mL(u*o/l,l/(u*a)):1/0}function Xd(t,e,i,n,o){var a=e===i.width?0:1,r=1-a,s=["x","y"],l=["width","height"],u=i[s[a]],h=e?t.area/e:0;(o||h>i[l[r]])&&(h=i[l[r]]);for(var c=0,d=t.length;cXM&&(u=XM),a=s}u=0?n+=u:n-=u:p>=0?n-=u:n+=u}return n}function pf(t,e){return t.getVisual("opacity")||t.getModel().get(e)}function gf(t,e,i){var n=t.getGraphicEl(),o=pf(t,e);null!=i&&(null==o&&(o=1),o*=i),n.downplay&&n.downplay(),n.traverse(function(t){if("group"!==t.type){var e=t.lineLabelOriginalOpacity;null!=e&&null==i||(e=o),t.setStyle("opacity",e)}})}function mf(t,e){var i=pf(t,e),n=t.getGraphicEl();n.highlight&&n.highlight(),n.traverse(function(t){"group"!==t.type&&t.setStyle("opacity",i)})}function vf(t){return t instanceof Array||(t=[t,t]),t}function yf(t){var e=t.coordinateSystem;if(!e||"view"===e.type){var i=t.getGraph();i.eachNode(function(t){var e=t.getModel();t.setLayout([+e.get("x"),+e.get("y")])}),xf(i)}}function xf(t){t.eachEdge(function(t){var e=t.getModel().get("lineStyle.curveness")||0,i=F(t.node1.getLayout()),n=F(t.node2.getLayout()),o=[i,n];+e&&o.push([(i[0]+n[0])/2-(i[1]-n[1])*e,(i[1]+n[1])/2-(n[0]-i[0])*e]),t.setLayout(o)})}function _f(t){var e=t.coordinateSystem;if(!e||"view"===e.type){var i=e.getBoundingRect(),n=t.getData(),o=n.graph,a=0,r=n.getSum("value"),s=2*Math.PI/(r||n.count()),l=i.width/2+i.x,u=i.height/2+i.y,h=Math.min(i.width,i.height)/2;o.eachNode(function(t){var e=t.getValue("value");a+=s*(r?e:1)/2,t.setLayout([h*Math.cos(a)+l,h*Math.sin(a)+u]),a+=s*(r?e:1)/2}),n.setLayout({cx:l,cy:u}),o.eachEdge(function(t){var e,i=t.getModel().get("lineStyle.curveness")||0,n=F(t.node1.getLayout()),o=F(t.node2.getLayout()),a=(n[0]+o[0])/2,r=(n[1]+o[1])/2;+i&&(e=[l*(i*=3)+a*(1-i),u*i+r*(1-i)]),t.setLayout([n,o,e])})}}function wf(t,e,i){for(var n=i.rect,o=n.width,a=n.height,r=[n.x+o/2,n.y+a/2],s=null==i.gravity?.1:i.gravity,l=0;l0?-1:i<0?1:e?-1:1}}function Pf(t,e){return Math.min(e[1],Math.max(e[0],t))}function Nf(t,e,i){this._axesMap=R(),this._axesLayout={},this.dimensions=t.dimensions,this._rect,this._model=t,this._init(t,e,i)}function Of(t,e){return ek(ik(t,e[0]),e[1])}function Ef(t,e){var i=e.layoutLength/(e.axisCount-1);return{position:i*t,axisNameAvailableWidth:i,axisLabelShow:!0}}function Rf(t,e){var i,n,o=e.layoutLength,a=e.axisExpandWidth,r=e.axisCount,s=e.axisCollapseWidth,l=e.winInnerIndices,u=s,h=!1;return tmk}function $f(t){var e=t.length-1;return e<0&&(e=0),[t[0],t[e]]}function Jf(t,e,i,n){var o=new tb;return o.add(new yM({name:"main",style:ip(i),silent:!0,draggable:!0,cursor:"move",drift:uk(t,e,o,"nswe"),ondragend:uk(qf,e,{isEnd:!0})})),hk(n,function(i){o.add(new yM({name:i,style:{opacity:0},draggable:!0,silent:!0,invisible:!0,drift:uk(t,e,o,i),ondragend:uk(qf,e,{isEnd:!0})}))}),o}function Qf(t,e,i,n){var o=n.brushStyle.lineWidth||0,a=fk(o,vk),r=i[0][0],s=i[1][0],l=r-o/2,u=s-o/2,h=i[0][1],c=i[1][1],d=h-a+o/2,f=c-a+o/2,p=h-r,g=c-s,m=p+o,v=g+o;ep(t,e,"main",r,s,p,g),n.transformable&&(ep(t,e,"w",l,u,a,v),ep(t,e,"e",d,u,a,v),ep(t,e,"n",l,u,m,a),ep(t,e,"s",l,f,m,a),ep(t,e,"nw",l,u,a,a),ep(t,e,"ne",d,u,a,a),ep(t,e,"sw",l,f,a,a),ep(t,e,"se",d,f,a,a))}function tp(t,e){var i=e.__brushOption,n=i.transformable,o=e.childAt(0);o.useStyle(ip(i)),o.attr({silent:!n,cursor:n?"move":"default"}),hk(["w","e","n","s","se","sw","ne","nw"],function(i){var o=e.childOfName(i),a=ap(t,i);o&&o.attr({silent:!n,invisible:!n,cursor:n?_k[a]+"-resize":null})})}function ep(t,e,i,n,o,a,r){var s=e.childOfName(i);s&&s.setShape(hp(up(t,e,[[n,o],[n+a,o+r]])))}function ip(t){return r({strokeNoScale:!0},t.brushStyle)}function np(t,e,i,n){var o=[dk(t,i),dk(e,n)],a=[fk(t,i),fk(e,n)];return[[o[0],a[0]],[o[1],a[1]]]}function op(t){return Ao(t.group)}function ap(t,e){if(e.length>1)return("e"===(n=[ap(t,(e=e.split(""))[0]),ap(t,e[1])])[0]||"w"===n[0])&&n.reverse(),n.join("");var i={left:"w",right:"e",top:"n",bottom:"s"},n=Co({w:"left",e:"right",n:"top",s:"bottom"}[e],op(t));return i[n]}function rp(t,e,i,n,o,a,r,s){var l=n.__brushOption,u=t(l.range),h=lp(i,a,r);hk(o.split(""),function(t){var e=xk[t];u[e[0]][e[1]]+=h[e[0]]}),l.range=e(np(u[0][0],u[1][0],u[0][1],u[1][1])),Zf(i,n),qf(i,{isEnd:!1})}function sp(t,e,i,n,o){var a=e.__brushOption.range,r=lp(t,i,n);hk(a,function(t){t[0]+=r[0],t[1]+=r[1]}),Zf(t,e),qf(t,{isEnd:!1})}function lp(t,e,i){var n=t.group,o=n.transformCoordToLocal(e,i),a=n.transformCoordToLocal(0,0);return[o[0]-a[0],o[1]-a[1]]}function up(t,e,n){var o=jf(t,e);return o&&!0!==o?o.clipPath(n,t._transform):i(n)}function hp(t){var e=dk(t[0][0],t[1][0]),i=dk(t[0][1],t[1][1]);return{x:e,y:i,width:fk(t[0][0],t[1][0])-e,height:fk(t[0][1],t[1][1])-i}}function cp(t,e,i){if(t._brushType){var n=t._zr,o=t._covers,a=Xf(t,e,i);if(!t._dragging)for(var r=0;r0;a--)Yp(s,l*=.99,r),jp(s,o,i,n,r),tg(s,l,r),jp(s,o,i,n,r)}function Up(t,e){var i=[],n="vertical"===e?"y":"x",o=Zi(t,function(t){return t.getLayout()[n]});return o.keys.sort(function(t,e){return t-e}),d(o.keys,function(t){i.push(o.buckets.get(t))}),i}function Xp(t,e,i,n,o,a,r){var s=[];d(e,function(t){var e=t.length,i=0,l=0;d(t,function(t){i+=t.getLayout().value}),l="vertical"===r?(o-(e-1)*a)/i:(n-(e-1)*a)/i,s.push(l)}),s.sort(function(t,e){return t-e});var l=s[0];d(e,function(t){d(t,function(t,e){var i=t.getLayout().value*l;"vertical"===r?(t.setLayout({x:e},!0),t.setLayout({dx:i},!0)):(t.setLayout({y:e},!0),t.setLayout({dy:i},!0))})}),d(i,function(t){var e=+t.getValue()*l;t.setLayout({dy:e},!0)})}function jp(t,e,i,n,o){d(t,function(t){var a,r,s,l=0,u=t.length;if("vertical"===o){var h;for(t.sort(function(t,e){return t.getLayout().x-e.getLayout().x}),s=0;s0&&(h=a.getLayout().x+r,a.setLayout({x:h},!0)),l=a.getLayout().x+a.getLayout().dx+e;if((r=l-e-n)>0)for(h=a.getLayout().x-r,a.setLayout({x:h},!0),l=h,s=u-2;s>=0;--s)(r=(a=t[s]).getLayout().x+a.getLayout().dx+e-l)>0&&(h=a.getLayout().x-r,a.setLayout({x:h},!0)),l=a.getLayout().x}else{var c;for(t.sort(function(t,e){return t.getLayout().y-e.getLayout().y}),s=0;s0&&(c=a.getLayout().y+r,a.setLayout({y:c},!0)),l=a.getLayout().y+a.getLayout().dy+e;if((r=l-e-i)>0)for(c=a.getLayout().y-r,a.setLayout({y:c},!0),l=c,s=u-2;s>=0;--s)(r=(a=t[s]).getLayout().y+a.getLayout().dy+e-l)>0&&(c=a.getLayout().y-r,a.setLayout({y:c},!0)),l=a.getLayout().y}})}function Yp(t,e,i){d(t.slice().reverse(),function(t){d(t,function(t){if(t.outEdges.length){var n=Qp(t.outEdges,qp,i)/Qp(t.outEdges,Jp,i);if("vertical"===i){var o=t.getLayout().x+(n-$p(t,i))*e;t.setLayout({x:o},!0)}else{var a=t.getLayout().y+(n-$p(t,i))*e;t.setLayout({y:a},!0)}}})})}function qp(t,e){return $p(t.node2,e)*t.getValue()}function Kp(t,e){return $p(t.node1,e)*t.getValue()}function $p(t,e){return"vertical"===e?t.getLayout().x+t.getLayout().dx/2:t.getLayout().y+t.getLayout().dy/2}function Jp(t){return t.getValue()}function Qp(t,e,i){for(var n=0,o=t.length,a=-1;++a0?"P":"N",a=n.getVisual("borderColor"+o)||n.getVisual("color"+o),r=i.getModel(Gk).getItemStyle(Wk);e.useStyle(r),e.style.fill=null,e.style.stroke=a}function fg(t,e,i,n,o){return i>n?-1:i0?t.get(o,e-1)<=n?1:-1:1}function pg(t,e){var i,n=t.getBaseAxis(),o="category"===n.type?n.getBandWidth():(i=n.getExtent(),Math.abs(i[1]-i[0])/e.count()),a=Vo(A(t.get("barMaxWidth"),o),o),r=Vo(A(t.get("barMinWidth"),1),o),s=t.get("barWidth");return null!=s?Vo(s,o):Math.max(Math.min(o/2,a),r)}function gg(t){return y(t)||(t=[+t,+t]),t}function mg(t,e){t.eachChild(function(t){t.attr({z:e.z,zlevel:e.zlevel,style:{stroke:"stroke"===e.brushType?e.color:null,fill:"fill"===e.brushType?e.color:null}})})}function vg(t,e){tb.call(this);var i=new wu(t,e),n=new tb;this.add(i),this.add(n),n.beforeUpdate=function(){this.attr(i.getScale())},this.updateData(t,e)}function yg(t){var e=t.data;e&&e[0]&&e[0][0]&&e[0][0].coord&&(t.data=f(e,function(t){var e={coords:[t[0].coord,t[1].coord]};return t[0].name&&(e.fromName=t[0].name),t[1].name&&(e.toName=t[1].name),o([e,t[0],t[1]])}))}function xg(t,e,i){tb.call(this),this.add(this.createLine(t,e,i)),this._updateEffectSymbol(t,e)}function _g(t,e,i){tb.call(this),this._createPolyline(t,e,i)}function wg(t,e,i){xg.call(this,t,e,i),this._lastFrame=0,this._lastFramePercent=0}function bg(){this.group=new tb}function Sg(t){return t instanceof Array||(t=[t,t]),t}function Mg(){var t=iw();this.canvas=t,this.blurSize=30,this.pointSize=20,this.maxOpacity=1,this.minOpacity=0,this._gradientPixels={}}function Ig(t,e,i){var n=t[1]-t[0],o=(e=f(e,function(e){return{interval:[(e.interval[0]-t[0])/n,(e.interval[1]-t[0])/n]}})).length,a=0;return function(t){for(n=a;n=0;n--){var r=e[n].interval;if(r[0]<=t&&t<=r[1]){a=n;break}}return n>=0&&n=e[0]&&t<=e[1]}}function Ag(t){var e=t.dimensions;return"lng"===e[0]&&"lat"===e[1]}function Dg(t,e,i,n){var o=t.getItemLayout(e),a=i.get("symbolRepeat"),r=i.get("symbolClip"),s=i.get("symbolPosition")||"start",l=(i.get("symbolRotate")||0)*Math.PI/180||0,u=i.get("symbolPatternSize")||2,h=i.isAnimationEnabled(),c={dataIndex:e,layout:o,itemModel:i,symbolType:t.getItemVisual(e,"symbol")||"circle",color:t.getItemVisual(e,"color"),symbolClip:r,symbolRepeat:a,symbolRepeatDirection:i.get("symbolRepeatDirection"),symbolPatternSize:u,rotation:l,animationModel:h?i:null,hoverAnimation:h&&i.get("hoverAnimation"),z2:i.getShallow("z",!0)||0};Cg(i,a,o,n,c),kg(t,e,o,a,r,c.boundingLength,c.pxSign,u,n,c),Pg(i,c.symbolScale,l,n,c);var d=c.symbolSize,f=i.get("symbolOffset");return y(f)&&(f=[Vo(f[0],d[0]),Vo(f[1],d[1])]),Ng(i,d,o,a,r,f,s,c.valueLineWidth,c.boundingLength,c.repeatCutLength,n,c),c}function Cg(t,e,i,n,o){var a,r=n.valueDim,s=t.get("symbolBoundingData"),l=n.coordSys.getOtherAxis(n.coordSys.getBaseAxis()),u=l.toGlobalCoord(l.dataToCoord(0)),h=1-+(i[r.wh]<=0);if(y(s)){var c=[Lg(l,s[0])-u,Lg(l,s[1])-u];c[1]0?1:a<0?-1:0}function Lg(t,e){return t.toGlobalCoord(t.dataToCoord(t.scale.parse(e)))}function kg(t,e,i,n,o,a,r,s,l,u){var h=l.valueDim,c=l.categoryDim,d=Math.abs(i[c.wh]),f=t.getItemVisual(e,"symbolSize");y(f)?f=f.slice():(null==f&&(f="100%"),f=[f,f]),f[c.index]=Vo(f[c.index],d),f[h.index]=Vo(f[h.index],n?d:Math.abs(a)),u.symbolSize=f,(u.symbolScale=[f[0]/s,f[1]/s])[h.index]*=(l.isHorizontal?-1:1)*r}function Pg(t,e,i,n,o){var a=t.get(cP)||0;a&&(fP.attr({scale:e.slice(),rotation:i}),fP.updateTransform(),a/=fP.getLineScale(),a*=e[n.valueDim.index]),o.valueLineWidth=a}function Ng(t,e,i,n,o,r,s,l,u,h,c,d){var f=c.categoryDim,p=c.valueDim,g=d.pxSign,m=Math.max(e[p.index]+l,0),v=m;if(n){var y=Math.abs(u),x=T(t.get("symbolMargin"),"15%")+"",_=!1;x.lastIndexOf("!")===x.length-1&&(_=!0,x=x.slice(0,x.length-1)),x=Vo(x,e[p.index]);var w=Math.max(m+2*x,0),b=_?0:2*x,S=Qo(n),M=S?n:Kg((y+b)/w);w=m+2*(x=(y-M*m)/2/(_?M:M-1)),b=_?0:2*x,S||"fixed"===n||(M=h?Kg((Math.abs(h)+b)/w):0),v=M*w-b,d.repeatTimes=M,d.symbolMargin=x}var I=g*(v/2),A=d.pathPosition=[];A[f.index]=i[f.wh]/2,A[p.index]="start"===s?I:"end"===s?u-I:u/2,r&&(A[0]+=r[0],A[1]+=r[1]);var D=d.bundlePosition=[];D[f.index]=i[f.xy],D[p.index]=i[p.xy];var C=d.barRectShape=a({},i);C[p.wh]=g*Math.max(Math.abs(i[p.wh]),Math.abs(A[p.index]+I)),C[f.wh]=i[f.wh];var L=d.clipShape={};L[f.xy]=-i[f.xy],L[f.wh]=c.ecSize[f.wh],L[p.xy]=0,L[p.wh]=i[p.wh]}function Og(t){var e=t.symbolPatternSize,i=Jl(t.symbolType,-e/2,-e/2,e,e,t.color);return i.attr({culling:!0}),"image"!==i.type&&i.setStyle({strokeNoScale:!0}),i}function Eg(t,e,i,n){function o(t){var e=l.slice(),n=i.pxSign,o=t;return("start"===i.symbolRepeatDirection?n>0:n<0)&&(o=h-1-t),e[u.index]=d*(o-h/2+.5)+l[u.index],{position:e,scale:i.symbolScale.slice(),rotation:i.rotation}}var a=t.__pictorialBundle,r=i.symbolSize,s=i.valueLineWidth,l=i.pathPosition,u=e.valueDim,h=i.repeatTimes||0,c=0,d=r[e.valueDim.index]+s+2*i.symbolMargin;for(jg(t,function(t){t.__pictorialAnimationIndex=c,t.__pictorialRepeatTimes=h,c0)],d=t.__pictorialBarRect;kh(d.style,h,a,n,e.seriesModel,o,c),fo(d,h)}function Kg(t){var e=Math.round(t);return Math.abs(t-e)<1e-4?e:Math.ceil(t)}function $g(t,e,i){this.dimension="single",this.dimensions=["single"],this._axis=null,this._rect,this._init(t,e,i),this.model=t}function Jg(t,e){e=e||{};var i=t.coordinateSystem,n=t.axis,o={},a=n.position,r=n.orient,s=i.getRect(),l=[s.x,s.x+s.width,s.y,s.y+s.height],u={horizontal:{top:l[2],bottom:l[3]},vertical:{left:l[0],right:l[1]}};o.position=["vertical"===r?u.vertical[a]:l[0],"horizontal"===r?u.horizontal[a]:l[3]];var h={horizontal:0,vertical:1};o.rotation=Math.PI/2*h[r];var c={top:-1,bottom:1,right:1,left:-1};o.labelDirection=o.tickDirection=o.nameDirection=c[a],t.get("axisTick.inside")&&(o.tickDirection=-o.tickDirection),T(e.labelInside,t.get("axisLabel.inside"))&&(o.labelDirection=-o.labelDirection);var d=e.rotate;return null==d&&(d=t.get("axisLabel.rotate")),o.labelRotation="top"===a?-d:d,o.z2=1,o}function Qg(t,e,i,n,o){var r=t.axis;if(!r.scale.isBlank()&&r.containData(e))if(t.involveSeries){var s=tm(e,t),l=s.payloadBatch,u=s.snapToValue;l[0]&&null==o.seriesIndex&&a(o,l[0]),!n&&t.snap&&r.containData(u)&&null!=u&&(e=u),i.showPointer(t,e,l,o),i.showTooltip(t,s,u)}else i.showPointer(t,e)}function tm(t,e){var i=e.axis,n=i.dim,o=t,a=[],r=Number.MAX_VALUE,s=-1;return _P(e.seriesModels,function(e,l){var u,h,c=e.getData().mapDimension(n,!0);if(e.getAxisTooltipData){var d=e.getAxisTooltipData(c,t,i);h=d.dataIndices,u=d.nestestValue}else{if(!(h=e.getData().indicesOfNearest(c[0],t,"category"===i.type?.5:null)).length)return;u=e.getData().get(c[0],h[0])}if(null!=u&&isFinite(u)){var f=t-u,p=Math.abs(f);p<=r&&((p=0&&s<0)&&(r=p,s=f,o=u,a.length=0),_P(h,function(t){a.push({seriesIndex:e.seriesIndex,dataIndexInside:t,dataIndex:e.getData().getRawIndex(t)})}))}}),{payloadBatch:a,snapToValue:o}}function em(t,e,i,n){t[e.key]={value:i,payloadBatch:n}}function im(t,e,i,n){var o=i.payloadBatch,a=e.axis,r=a.model,s=e.axisPointerModel;if(e.triggerTooltip&&o.length){var l=e.coordSys.model,u=Ah(l),h=t.map[u];h||(h=t.map[u]={coordSysId:l.id,coordSysIndex:l.componentIndex,coordSysType:l.type,coordSysMainType:l.mainType,dataByAxis:[]},t.list.push(h)),h.dataByAxis.push({axisDim:a.dim,axisIndex:r.componentIndex,axisType:r.type,axisId:r.id,value:n,valueLabelOpt:{precision:s.get("label.precision"),formatter:s.get("label.formatter")},seriesDataIndices:o.slice()})}}function nm(t,e,i){var n=i.axesInfo=[];_P(e,function(e,i){var o=e.axisPointerModel.option,a=t[i];a?(!e.useHandle&&(o.status="show"),o.value=a.value,o.seriesDataIndices=(a.payloadBatch||[]).slice()):!e.useHandle&&(o.status="hide"),"show"===o.status&&n.push({axisDim:e.axis.dim,axisIndex:e.axis.model.componentIndex,value:o.value})})}function om(t,e,i,n){if(!lm(e)&&t.list.length){var o=((t.list[0].dataByAxis[0]||{}).seriesDataIndices||[])[0]||{};n({type:"showTip",escapeConnect:!0,x:e[0],y:e[1],tooltipOption:i.tooltipOption,position:i.position,dataIndexInside:o.dataIndexInside,dataIndex:o.dataIndex,seriesIndex:o.seriesIndex,dataByCoordSys:t.list})}else n({type:"hideTip"})}function am(t,e,i){var n=i.getZr(),o=bP(n).axisPointerLastHighlights||{},a=bP(n).axisPointerLastHighlights={};_P(t,function(t,e){var i=t.axisPointerModel.option;"show"===i.status&&_P(i.seriesDataIndices,function(t){var e=t.seriesIndex+" | "+t.dataIndex;a[e]=t})});var r=[],s=[];d(o,function(t,e){!a[e]&&s.push(t)}),d(a,function(t,e){!o[e]&&r.push(t)}),s.length&&i.dispatchAction({type:"downplay",escapeConnect:!0,batch:s}),r.length&&i.dispatchAction({type:"highlight",escapeConnect:!0,batch:r})}function rm(t,e){for(var i=0;i<(t||[]).length;i++){var n=t[i];if(e.axis.dim===n.axisDim&&e.axis.model.componentIndex===n.axisIndex)return n}}function sm(t){var e=t.axis.model,i={},n=i.axisDim=t.axis.dim;return i.axisIndex=i[n+"AxisIndex"]=e.componentIndex,i.axisName=i[n+"AxisName"]=e.name,i.axisId=i[n+"AxisId"]=e.id,i}function lm(t){return!t||null==t[0]||isNaN(t[0])||null==t[1]||isNaN(t[1])}function um(t,e,i){if(!U_.node){var n=e.getZr();SP(n).records||(SP(n).records={}),hm(n,e),(SP(n).records[t]||(SP(n).records[t]={})).handler=i}}function hm(t,e){function i(i,n){t.on(i,function(i){var o=pm(e);MP(SP(t).records,function(t){t&&n(t,i,o.dispatchAction)}),cm(o.pendings,e)})}SP(t).initialized||(SP(t).initialized=!0,i("click",v(fm,"click")),i("mousemove",v(fm,"mousemove")),i("globalout",dm))}function cm(t,e){var i,n=t.showTip.length,o=t.hideTip.length;n?i=t.showTip[n-1]:o&&(i=t.hideTip[o-1]),i&&(i.dispatchAction=null,e.dispatchAction(i))}function dm(t,e,i){t.handler("leave",null,i)}function fm(t,e,i,n){e.handler(t,i,n)}function pm(t){var e={showTip:[],hideTip:[]},i=function(n){var o=e[n.type];o?o.push(n):(n.dispatchAction=i,t.dispatchAction(n))};return{dispatchAction:i,pendings:e}}function gm(t,e){if(!U_.node){var i=e.getZr();(SP(i).records||{})[t]&&(SP(i).records[t]=null)}}function mm(){}function vm(t,e,i,n){ym(TP(i).lastProp,n)||(TP(i).lastProp=n,e?Io(i,n,t):(i.stopAnimation(),i.attr(n)))}function ym(t,e){if(w(t)&&w(e)){var i=!0;return d(e,function(e,n){i=i&&ym(t[n],e)}),!!i}return t===e}function xm(t,e){t[e.get("label.show")?"show":"hide"]()}function _m(t){return{position:t.position.slice(),rotation:t.rotation||0}}function wm(t,e,i){var n=e.get("z"),o=e.get("zlevel");t&&t.traverse(function(t){"group"!==t.type&&(null!=n&&(t.z=n),null!=o&&(t.zlevel=o),t.silent=i)})}function bm(t){var e,i=t.get("type"),n=t.getModel(i+"Style");return"line"===i?(e=n.getLineStyle()).fill=null:"shadow"===i&&((e=n.getAreaStyle()).stroke=null),e}function Sm(t,e,i,n,o){var a=Im(i.get("value"),e.axis,e.ecModel,i.get("seriesDataIndices"),{precision:i.get("label.precision"),formatter:i.get("label.formatter")}),r=i.getModel("label"),s=qM(r.get("padding")||0),l=r.getFont(),u=ke(a,l),h=o.position,c=u.width+s[1]+s[3],d=u.height+s[0]+s[2],f=o.align;"right"===f&&(h[0]-=c),"center"===f&&(h[0]-=c/2);var p=o.verticalAlign;"bottom"===p&&(h[1]-=d),"middle"===p&&(h[1]-=d/2),Mm(h,c,d,n);var g=r.get("backgroundColor");g&&"auto"!==g||(g=e.get("axisLine.lineStyle.color")),t.label={shape:{x:0,y:0,width:c,height:d,r:r.get("borderRadius")},position:h.slice(),style:{text:a,textFont:l,textFill:r.getTextColor(),textPosition:"inside",fill:g,stroke:r.get("borderColor")||"transparent",lineWidth:r.get("borderWidth")||0,shadowBlur:r.get("shadowBlur"),shadowColor:r.get("shadowColor"),shadowOffsetX:r.get("shadowOffsetX"),shadowOffsetY:r.get("shadowOffsetY")},z2:10}}function Mm(t,e,i,n){var o=n.getWidth(),a=n.getHeight();t[0]=Math.min(t[0]+e,o)-e,t[1]=Math.min(t[1]+i,a)-i,t[0]=Math.max(t[0],0),t[1]=Math.max(t[1],0)}function Im(t,e,i,n,o){t=e.scale.parse(t);var a=e.scale.getLabel(t,{precision:o.precision}),r=o.formatter;if(r){var s={value:Xl(e,t),seriesData:[]};d(n,function(t){var e=i.getSeriesByIndex(t.seriesIndex),n=t.dataIndexInside,o=e&&e.getDataParams(n);o&&s.seriesData.push(o)}),_(r)?a=r.replace("{value}",a):x(r)&&(a=r(s))}return a}function Tm(t,e,i){var n=xt();return Mt(n,n,i.rotation),St(n,n,i.position),Do([t.dataToCoord(e),(i.labelOffset||0)+(i.labelDirection||1)*(i.labelMargin||0)],n)}function Am(t,e,i,n,o,a){var r=FD.innerTextLayout(i.rotation,0,i.labelDirection);i.labelMargin=o.get("label.margin"),Sm(e,n,o,a,{position:Tm(n.axis,t,i),align:r.textAlign,verticalAlign:r.textVerticalAlign})}function Dm(t,e,i){return i=i||0,{x1:t[i],y1:t[1-i],x2:e[i],y2:e[1-i]}}function Cm(t,e,i){return i=i||0,{x:t[i],y:t[1-i],width:e[i],height:e[1-i]}}function Lm(t,e,i,n,o,a){return{cx:t,cy:e,r0:i,r:n,startAngle:o,endAngle:a,clockwise:!0}}function km(t,e){var i={};return i[e.dim+"AxisIndex"]=e.index,t.getCartesian(i)}function Pm(t){return"x"===t.dim?0:1}function Nm(t){return t.isHorizontal()?0:1}function Om(t,e){var i=t.getRect();return[i[kP[e]],i[kP[e]]+i[PP[e]]]}function Em(t,e,i){var n=new yM({shape:{x:t.x-10,y:t.y-10,width:0,height:t.height+20}});return To(n,{shape:{width:t.width+20,height:t.height+20}},e,i),n}function Rm(t,e,i){if(t.count())for(var n,o=e.coordinateSystem,a=e.getLayerSeries(),r=t.mapDimension("single"),s=t.mapDimension("value"),l=f(a,function(e){return f(e.indices,function(e){var i=o.dataToPoint(t.get(r,e));return i[1]=t.get(s,e),i})}),u=zm(l),h=u.y0,c=i/u.max,d=a.length,p=a[0].indices.length,g=0;ga&&(a=u),n.push(u)}for(var h=0;ha&&(a=d)}return r.y0=o,r.max=a,r}function Bm(t){var e=0;d(t.children,function(t){Bm(t);var i=t.value;y(i)&&(i=i[0]),e+=i});var i=t.value;y(i)&&(i=i[0]),(null==i||isNaN(i))&&(i=e),i<0&&(i=0),y(t.value)?t.value[0]=i:t.value=i}function Vm(t,e,i){function n(){r.ignore=r.hoverIgnore}function o(){r.ignore=r.normalIgnore}tb.call(this);var a=new hM({z2:zP});a.seriesIndex=e.seriesIndex;var r=new rM({z2:BP,silent:t.getModel("label").get("silent")});this.add(a),this.add(r),this.updateData(!0,t,"normal",e,i),this.on("emphasis",n).on("normal",o).on("mouseover",n).on("mouseout",o)}function Gm(t,e,i){var n=t.getVisual("color"),o=t.getVisual("visualMeta");o&&0!==o.length||(n=null);var a=t.getModel("itemStyle").get("color");if(a)return a;if(n)return n;if(0===t.depth)return i.option.color[0];var r=i.option.color.length;return a=i.option.color[Fm(t)%r]}function Fm(t){for(var e=t;e.depth>1;)e=e.parentNode;return l(t.getAncestors()[0].children,e)}function Wm(t,e,i){return i!==RP.NONE&&(i===RP.SELF?t===e:i===RP.ANCESTOR?t===e||t.isAncestorOf(e):t===e||t.isDescendantOf(e))}function Hm(t,e,i){e.getData().setItemVisual(t.dataIndex,"color",i)}function Zm(t,e){var i=t.children||[];t.children=Um(i,e),i.length&&d(t.children,function(t){Zm(t,e)})}function Um(t,e){if("function"==typeof e)return t.sort(e);var i="asc"===e;return t.sort(function(t,e){var n=(t.getValue()-e.getValue())*(i?1:-1);return 0===n?(t.dataIndex-e.dataIndex)*(i?-1:1):n})}function Xm(t,e){return e=e||[0,0],f(["x","y"],function(i,n){var o=this.getAxis(i),a=e[n],r=t[n]/2;return"category"===o.type?o.getBandWidth():Math.abs(o.dataToCoord(a-r)-o.dataToCoord(a+r))},this)}function jm(t,e){return e=e||[0,0],f([0,1],function(i){var n=e[i],o=t[i]/2,a=[],r=[];return a[i]=n-o,r[i]=n+o,a[1-i]=r[1-i]=e[1-i],Math.abs(this.dataToPoint(a)[i]-this.dataToPoint(r)[i])},this)}function Ym(t,e){var i=this.getAxis(),n=e instanceof Array?e[0]:e,o=(t instanceof Array?t[0]:t)/2;return"category"===i.type?i.getBandWidth():Math.abs(i.dataToCoord(n-o)-i.dataToCoord(n+o))}function qm(t,e){return f(["Radius","Angle"],function(i,n){var o=this["get"+i+"Axis"](),a=e[n],r=t[n]/2,s="dataTo"+i,l="category"===o.type?o.getBandWidth():Math.abs(o[s](a-r)-o[s](a+r));return"Angle"===i&&(l=l*Math.PI/180),l},this)}function Km(t){var e,i=t.type;if("path"===i){var n=t.shape,o=null!=n.width&&null!=n.height?{x:n.x||0,y:n.y||0,width:n.width,height:n.height}:null,a=lv(n);(e=Xn(a,null,o,n.layout||"center")).__customPathData=a}else"image"===i?(e=new fi({})).__customImagePath=t.style.image:"text"===i?(e=new rM({})).__customText=t.style.text:e=new(0,zM[i.charAt(0).toUpperCase()+i.slice(1)]);return e.__customGraphicType=i,e.name=t.name,e}function $m(t,e,n,o,a,r,s){var l={},u=n.style||{};if(n.shape&&(l.shape=i(n.shape)),n.position&&(l.position=n.position.slice()),n.scale&&(l.scale=n.scale.slice()),n.origin&&(l.origin=n.origin.slice()),n.rotation&&(l.rotation=n.rotation),"image"===t.type&&n.style){h=l.style={};d(["x","y","width","height"],function(e){Jm(e,h,u,t.style,r)})}if("text"===t.type&&n.style){var h=l.style={};d(["x","y"],function(e){Jm(e,h,u,t.style,r)}),!u.hasOwnProperty("textFill")&&u.fill&&(u.textFill=u.fill),!u.hasOwnProperty("textStroke")&&u.stroke&&(u.textStroke=u.stroke)}if("group"!==t.type&&(t.useStyle(u),r)){t.style.opacity=0;var c=u.opacity;null==c&&(c=1),To(t,{style:{opacity:c}},o,e)}r?t.attr(l):Io(t,l,o,e),n.hasOwnProperty("z2")&&t.attr("z2",n.z2||0),n.hasOwnProperty("silent")&&t.attr("silent",n.silent),n.hasOwnProperty("invisible")&&t.attr("invisible",n.invisible),n.hasOwnProperty("ignore")&&t.attr("ignore",n.ignore),n.hasOwnProperty("info")&&t.attr("info",n.info);var f=n.styleEmphasis,p=!1===f;t.__cusHasEmphStl&&null==f||!t.__cusHasEmphStl&&p||(ro(t,f),t.__cusHasEmphStl=!p),s&&po(t,!p)}function Jm(t,e,i,n,o){null==i[t]||o||(e[t]=i[t],i[t]=n[t])}function Qm(t,e,i,n){function o(t){null==t&&(t=h),v&&(c=e.getItemModel(t),d=c.getModel(UP),f=c.getModel(XP),p=e.getItemVisual(t,"color"),v=!1)}var s=t.get("renderItem"),l=t.coordinateSystem,u={};l&&(u=l.prepareCustoms?l.prepareCustoms():YP[l.type](l));var h,c,d,f,p,g=r({getWidth:n.getWidth,getHeight:n.getHeight,getZr:n.getZr,getDevicePixelRatio:n.getDevicePixelRatio,value:function(t,i){return null==i&&(i=h),e.get(e.getDimension(t||0),i)},style:function(i,n){null==n&&(n=h),o(n);var r=c.getModel(HP).getItemStyle();null!=p&&(r.fill=p);var s=e.getItemVisual(n,"opacity");return null!=s&&(r.opacity=s),mo(r,d,null,{autoColor:p,isRectText:!0}),r.text=d.getShallow("show")?A(t.getFormattedLabel(n,"normal"),_u(e,n)):null,i&&a(r,i),r},styleEmphasis:function(i,n){null==n&&(n=h),o(n);var r=c.getModel(ZP).getItemStyle();return mo(r,f,null,{isRectText:!0},!0),r.text=f.getShallow("show")?D(t.getFormattedLabel(n,"emphasis"),t.getFormattedLabel(n,"normal"),_u(e,n)):null,i&&a(r,i),r},visual:function(t,i){return null==i&&(i=h),e.getItemVisual(i,t)},barLayout:function(t){if(l.getBaseAxis)return Ll(r({axis:l.getBaseAxis()},t),n)},currentSeriesIndices:function(){return i.getCurrentSeriesIndices()},font:function(t){return So(t,i)}},u.api||{}),m={context:{},seriesId:t.id,seriesName:t.name,seriesIndex:t.seriesIndex,coordSys:u.coordSys,dataInsideLength:e.count(),encode:tv(t.getData())},v=!0;return function(t,i){return h=t,v=!0,s&&s(r({dataIndexInside:t,dataIndex:e.getRawIndex(t),actionType:i?i.type:null},m),g)}}function tv(t){var e={};return d(t.dimensions,function(i,n){var o=t.getDimensionInfo(i);if(!o.isExtraCoord){var a=o.coordDim;(e[a]=e[a]||[])[o.coordDimIndex]=n}}),e}function ev(t,e,i,n,o,a){return(t=iv(t,e,i,n,o,a,!0))&&a.setItemGraphicEl(e,t),t}function iv(t,e,i,n,o,a,r){var s=!i,l=(i=i||{}).type,u=i.shape,h=i.style;if(t&&(s||null!=l&&l!==t.__customGraphicType||"path"===l&&uv(u)&&lv(u)!==t.__customPathData||"image"===l&&hv(h,"image")&&h.image!==t.__customImagePath||"text"===l&&hv(u,"text")&&h.text!==t.__customText)&&(o.remove(t),t=null),!s){var c=!t;return!t&&(t=Km(i)),$m(t,e,i,n,a,c,r),"group"===l&&nv(t,e,i,n,a),o.add(t),t}}function nv(t,e,i,n,o){var a=i.children,r=a?a.length:0,s=i.$mergeChildren,l="byName"===s||i.diffChildrenByName,u=!1===s;if(r||l||u)if(l)ov({oldChildren:t.children()||[],newChildren:a||[],dataIndex:e,animatableModel:n,group:t,data:o});else{u&&t.removeAll();for(var h=0;hn?t-=l+a:t+=a),null!=r&&(e+u+r>o?e-=u+r:e+=r),[t,e]}function Ov(t,e,i,n,o){var a=i.getOuterSize(),r=a.width,s=a.height;return t=Math.min(t+r,n)-r,e=Math.min(e+s,o)-s,t=Math.max(t,0),e=Math.max(e,0),[t,e]}function Ev(t,e,i){var n=i[0],o=i[1],a=0,r=0,s=e.width,l=e.height;switch(t){case"inside":a=e.x+s/2-n/2,r=e.y+l/2-o/2;break;case"top":a=e.x+s/2-n/2,r=e.y-o-5;break;case"bottom":a=e.x+s/2-n/2,r=e.y+l+5;break;case"left":a=e.x-n-5,r=e.y+l/2-o/2;break;case"right":a=e.x+s+5,r=e.y+l/2-o/2}return[a,r]}function Rv(t){return"center"===t||"middle"===t}function zv(t){return t.get("stack")||"__ec_stack_"+t.seriesIndex}function Bv(t){return t.dim}function Vv(t,e){var i={};d(t,function(t,e){var n=t.getData(),o=t.coordinateSystem.getBaseAxis(),a=o.getExtent(),r="category"===o.type?o.getBandWidth():Math.abs(a[1]-a[0])/n.count(),s=i[Bv(o)]||{bandWidth:r,remainedWidth:r,autoWidthCount:0,categoryGap:"20%",gap:"30%",stacks:{}},l=s.stacks;i[Bv(o)]=s;var u=zv(t);l[u]||s.autoWidthCount++,l[u]=l[u]||{width:0,maxWidth:0};var h=Vo(t.get("barWidth"),r),c=Vo(t.get("barMaxWidth"),r),d=t.get("barGap"),f=t.get("barCategoryGap");h&&!l[u].width&&(h=Math.min(s.remainedWidth,h),l[u].width=h,s.remainedWidth-=h),c&&(l[u].maxWidth=c),null!=d&&(s.gap=d),null!=f&&(s.categoryGap=f)});var n={};return d(i,function(t,e){n[e]={};var i=t.stacks,o=t.bandWidth,a=Vo(t.categoryGap,o),r=Vo(t.gap,1),s=t.remainedWidth,l=t.autoWidthCount,u=(s-a)/(l+(l-1)*r);u=Math.max(u,0),d(i,function(t,e){var i=t.maxWidth;i&&ie[0]&&(e=e.slice().reverse());var n=t.coordToPoint([e[0],i]),o=t.coordToPoint([e[1],i]);return{x1:n[0],y1:n[1],x2:o[0],y2:o[1]}}function jv(t){return t.getRadiusAxis().inverse?0:1}function Yv(t){var e=t[0],i=t[t.length-1];e&&i&&Math.abs(Math.abs(e.coord-i.coord)-360)<1e-4&&t.pop()}function qv(t,e,i){return{position:[t.cx,t.cy],rotation:i/180*Math.PI,labelDirection:-1,tickDirection:-1,nameDirection:1,labelRotate:e.getModel("axisLabel").get("rotate"),z2:1}}function Kv(t,e,i,n,o){var a=e.axis,r=a.dataToCoord(t),s=n.getAngleAxis().getExtent()[0];s=s/180*Math.PI;var l,u,h,c=n.getRadiusAxis().getExtent();if("radius"===a.dim){var d=xt();Mt(d,d,s),St(d,d,[n.cx,n.cy]),l=Do([r,-o],d);var f=e.getModel("axisLabel").get("rotate")||0,p=FD.innerTextLayout(s,f*Math.PI/180,-1);u=p.textAlign,h=p.textVerticalAlign}else{var g=c[1];l=n.coordToPoint([g+o,r]);var m=n.cx,v=n.cy;u=Math.abs(l[0]-m)/g<.3?"center":l[0]>m?"left":"right",h=Math.abs(l[1]-v)/g<.3?"middle":l[1]>v?"top":"bottom"}return{position:l,align:u,verticalAlign:h}}function $v(t,e){e.update="updateView",Es(e,function(e,i){var n={};return i.eachComponent({mainType:"geo",query:e},function(i){i[t](e.name),d(i.coordinateSystem.regions,function(t){n[t.name]=i.isSelected(t.name)||!1})}),{selected:n,name:e.name}})}function Jv(t){var e={};d(t,function(t){e[t]=1}),t.length=0,d(e,function(e,i){t.push(i)})}function Qv(t){if(t)for(var e in t)if(t.hasOwnProperty(e))return!0}function ty(t,e,n){function o(){var t=function(){};return t.prototype.__hidden=t.prototype,new t}var a={};return MN(e,function(e){var r=a[e]=o();MN(t[e],function(t,o){if(hL.isValidType(o)){var a={type:o,visual:t};n&&n(a,e),r[o]=new hL(a),"opacity"===o&&((a=i(a)).type="colorAlpha",r.__hidden.__alphaForOpacity=new hL(a))}})}),a}function ey(t,e,n){var o;d(n,function(t){e.hasOwnProperty(t)&&Qv(e[t])&&(o=!0)}),o&&d(n,function(n){e.hasOwnProperty(n)&&Qv(e[n])?t[n]=i(e[n]):delete t[n]})}function iy(t,e,i,n,o,a){function r(t){return i.getItemVisual(h,t)}function s(t,e){i.setItemVisual(h,t,e)}function l(t,l){h=null==a?t:l;var c=i.getRawDataItem(h);if(!c||!1!==c.visualMap)for(var d=n.call(o,t),f=e[d],p=u[d],g=0,m=p.length;g1)return!1;var h=uy(i-t,o-t,n-e,a-e)/l;return!(h<0||h>1)}function ly(t){return t<=1e-6&&t>=-1e-6}function uy(t,e,i,n){return t*n-e*i}function hy(t,e,i){var n=this._targetInfoList=[],o={},a=dy(e,t);TN(PN,function(t,e){(!i||!i.include||AN(i.include,e)>=0)&&t(a,n,o)})}function cy(t){return t[0]>t[1]&&t.reverse(),t}function dy(t,e){return Vi(t,e,{includeMainTypes:LN})}function fy(t,e,i,n){var o=i.getAxis(["x","y"][t]),a=cy(f([0,1],function(t){return e?o.coordToData(o.toLocalCoord(n[t])):o.toGlobalCoord(o.dataToCoord(n[t]))})),r=[];return r[t]=a,r[1-t]=[NaN,NaN],{values:a,xyMinMax:r}}function py(t,e,i,n){return[e[0]-n[t]*i[0],e[1]-n[t]*i[1]]}function gy(t,e){var i=my(t),n=my(e),o=[i[0]/n[0],i[1]/n[1]];return isNaN(o[0])&&(o[0]=1),isNaN(o[1])&&(o[1]=1),o}function my(t){return t?[t[0][1]-t[0][0],t[1][1]-t[1][0]]:[NaN,NaN]}function vy(t,e,i,n,o){if(o){var a=t.getZr();a[VN]||(a[BN]||(a[BN]=yy),Nr(a,BN,i,e)(t,n))}}function yy(t,e){if(!t.isDisposed()){var i=t.getZr();i[VN]=!0,t.dispatchAction({type:"brushSelect",batch:e}),i[VN]=!1}}function xy(t,e,i,n){for(var o=0,a=e.length;o=0}function Ny(t,e,i){function n(t,e){return l(e.nodes,t)>=0}function o(t,n){var o=!1;return e(function(e){d(i(t,e)||[],function(t){n.records[e.name][t]&&(o=!0)})}),o}function a(t,n){n.nodes.push(t),e(function(e){d(i(t,e)||[],function(t){n.records[e.name][t]=!0})})}return function(i){var r={nodes:[],records:{}};if(e(function(t){r.records[t.name]={}}),!i)return r;a(i,r);var s;do{s=!1,t(function(t){!n(t,r)&&o(t,r)&&(a(t,r),s=!0)})}while(s);return r}}function Oy(t,e,i){var n=[1/0,-1/0];return $N(i,function(t){var i=t.getData();i&&$N(i.mapDimension(e,!0),function(t){var e=i.getApproximateExtent(t);e[0]n[1]&&(n[1]=e[1])})}),n[1]0?0:NaN);var r=i.getMax(!0);return null!=r&&"dataMax"!==r&&"function"!=typeof r?e[1]=r:o&&(e[1]=a>0?a-1:NaN),i.get("scale",!0)||(e[0]>0&&(e[0]=0),e[1]<0&&(e[1]=0)),e}function Ry(t,e){var i=t.getAxisModel(),n=t._percentWindow,o=t._valueWindow;if(n){var a=Zo(o,[0,500]);a=Math.min(a,20);var r=e||0===n[0]&&100===n[1];i.setRange(r?null:+o[0].toFixed(a),r?null:+o[1].toFixed(a))}}function zy(t){var e=t._minMaxSpan={},i=t._dataZoomModel;$N(["min","max"],function(n){e[n+"Span"]=i.get(n+"Span");var o=i.get(n+"ValueSpan");if(null!=o&&(e[n+"ValueSpan"]=o,null!=(o=t.getAxisModel().axis.scale.parse(o)))){var a=t._dataExtent;e[n+"Span"]=Bo(a[0]+o,a,[0,100],!0)}})}function By(t){var e={};return tO(["start","end","startValue","endValue","throttle"],function(i){t.hasOwnProperty(i)&&(e[i]=t[i])}),e}function Vy(t,e){var i=t._rangePropMode,n=t.get("rangeMode");tO([["start","startValue"],["end","endValue"]],function(t,o){var a=null!=e[t[0]],r=null!=e[t[1]];a&&!r?i[o]="percent":!a&&r?i[o]="value":n?i[o]=n[o]:a&&(i[o]="percent")})}function Gy(t){return{x:"y",y:"x",radius:"angle",angle:"radius"}[t]}function Fy(t){return"vertical"===t?"ns-resize":"ew-resize"}function Wy(t,e){var i=Uy(t),n=e.dataZoomId,o=e.coordId;d(i,function(t,i){var a=t.dataZoomInfos;a[n]&&l(e.allCoordIds,o)<0&&(delete a[n],t.count--)}),jy(i);var a=i[o];a||((a=i[o]={coordId:o,dataZoomInfos:{},count:0}).controller=Xy(t,a),a.dispatchAction=v(Yy,t)),!a.dataZoomInfos[n]&&a.count++,a.dataZoomInfos[n]=e;var r=qy(a.dataZoomInfos);a.controller.enable(r.controlType,r.opt),a.controller.setPointerChecker(e.containsPoint),Nr(a,"dispatchAction",e.dataZoomModel.get("throttle",!0),"fixRate")}function Hy(t,e){var i=Uy(t);d(i,function(t){t.controller.dispose();var i=t.dataZoomInfos;i[e]&&(delete i[e],t.count--)}),jy(i)}function Zy(t){return t.type+"\0_"+t.id}function Uy(t){var e=t.getZr();return e[fO]||(e[fO]={})}function Xy(t,e){var i=new oc(t.getZr());return d(["pan","zoom","scrollMove"],function(t){i.on(t,function(i){var n=[];d(e.dataZoomInfos,function(o){if(i.isAvailableBehavior(o.dataZoomModel.option)){var a=(o.getRange||{})[t],r=a&&a(e.controller,i);!o.dataZoomModel.get("disabled",!0)&&r&&n.push({dataZoomId:o.dataZoomId,start:r[0],end:r[1]})}}),n.length&&e.dispatchAction(n)})}),i}function jy(t){d(t,function(e,i){e.count||(e.controller.dispose(),delete t[i])})}function Yy(t,e){t.dispatchAction({type:"dataZoom",batch:e})}function qy(t){var e,i={type_true:2,type_move:1,type_false:0,type_undefined:-1},n=!0;return d(t,function(t){var o=t.dataZoomModel,a=!o.get("disabled",!0)&&(!o.get("zoomLock",!0)||"move");i["type_"+a]>i["type_"+e]&&(e=a),n&=o.get("preventDefaultMouseMove",!0)}),{controlType:e,opt:{zoomOnMouseWheel:!0,moveOnMouseMove:!0,moveOnMouseWheel:!0,preventDefaultMouseMove:!!n}}}function Ky(t){return function(e,i,n,o){var a=this._range,r=a.slice(),s=e.axisModels[0];if(s){var l=t(r,s,e,i,n,o);return QL(l,r,[0,100],"all"),this._range=r,a[0]!==r[0]||a[1]!==r[1]?r:void 0}}}function $y(t,e){return t&&t.hasOwnProperty&&t.hasOwnProperty(e)}function Jy(t,e,i,n){for(var o=e.targetVisuals[n],a=hL.prepareVisualTypes(o),r={color:t.getData().getVisual("color")},s=0,l=a.length;s=0&&(r[a]=+r[a].toFixed(h)),r}function fx(t,e){var n=t.getData(),o=t.coordinateSystem;if(e&&!cx(e)&&!y(e.coord)&&o){var a=o.dimensions,r=px(e,n,o,t);if((e=i(e)).type&&YO[e.type]&&r.baseAxis&&r.valueAxis){var s=XO(a,r.baseAxis.dim),l=XO(a,r.valueAxis.dim);e.coord=YO[e.type](n,r.baseDataDim,r.valueDataDim,s,l),e.value=e.coord[l]}else{for(var u=[null!=e.xAxis?e.xAxis:e.radiusAxis,null!=e.yAxis?e.yAxis:e.angleAxis],h=0;h<2;h++)YO[u[h]]&&(u[h]=yx(n,n.mapDimension(a[h]),u[h]));e.coord=u}}return e}function px(t,e,i,n){var o={};return null!=t.valueIndex||null!=t.valueDim?(o.valueDataDim=null!=t.valueIndex?e.getDimension(t.valueIndex):t.valueDim,o.valueAxis=i.getAxis(gx(n,o.valueDataDim)),o.baseAxis=i.getOtherAxis(o.valueAxis),o.baseDataDim=e.mapDimension(o.baseAxis.dim)):(o.baseAxis=n.getBaseAxis(),o.valueAxis=i.getOtherAxis(o.baseAxis),o.baseDataDim=e.mapDimension(o.baseAxis.dim),o.valueDataDim=e.mapDimension(o.valueAxis.dim)),o}function gx(t,e){var i=t.getData(),n=i.dimensions;e=i.getDimension(e);for(var o=0;o=0)return!0}function Yx(t){for(var e=t.split(/\n+/g),i=[],n=f(Xx(e.shift()).split(pE),function(t){return{name:t,data:[]}}),o=0;o=0&&!i[o][n];o--);if(o<0){var a=t.queryComponents({mainType:"dataZoom",subType:"select",id:n})[0];if(a){var r=a.getPercentRange();i[0][n]={dataZoomId:n,start:r[0],end:r[1]}}}}),i.push(e)}function t_(t){var e=n_(t),i=e[e.length-1];e.length>1&&e.pop();var n={};return gE(i,function(t,i){for(var o=e.length-1;o>=0;o--)if(t=e[o][i]){n[i]=t;break}}),n}function e_(t){t[mE]=null}function i_(t){return n_(t).length}function n_(t){var e=t[mE];return e||(e=t[mE]=[{}]),e}function o_(t,e,i){(this._brushController=new zf(i.getZr())).on("brush",m(this._onBrush,this)).mount(),this._isZoomActive}function a_(t){var e={};return d(["xAxisIndex","yAxisIndex"],function(i){e[i]=t[i],null==e[i]&&(e[i]="all"),(!1===e[i]||"none"===e[i])&&(e[i]=[])}),e}function r_(t,e){t.setIconStatus("back",i_(e)>1?"emphasis":"normal")}function s_(t,e,i,n,o){var a=i._isZoomActive;n&&"takeGlobalCursor"===n.type&&(a="dataZoomSelect"===n.key&&n.dataZoomSelectActive),i._isZoomActive=a,t.setIconStatus("zoom",a?"emphasis":"normal");var r=new hy(a_(t.option),e,{include:["grid"]});i._brushController.setPanels(r.makePanelOpts(o,function(t){return t.xAxisDeclared&&!t.yAxisDeclared?"lineX":!t.xAxisDeclared&&t.yAxisDeclared?"lineY":"rect"})).enableBrush(!!a&&{brushType:"auto",brushStyle:{lineWidth:0,fill:"rgba(0,0,0,0.2)"}})}function l_(t){this.model=t}function u_(t){return SE(t)}function h_(){if(!TE&&AE){TE=!0;var t=AE.styleSheets;t.length<31?AE.createStyleSheet().addRule(".zrvml","behavior:url(#default#VML)"):t[0].addRule(".zrvml","behavior:url(#default#VML)")}}function c_(t){return parseInt(t,10)}function d_(t,e){h_(),this.root=t,this.storage=e;var i=document.createElement("div"),n=document.createElement("div");i.style.cssText="display:inline-block;overflow:hidden;position:relative;width:300px;height:150px;",n.style.cssText="position:absolute;left:0;top:0;",t.appendChild(i),this._vmlRoot=n,this._vmlViewport=i,this.resize();var o=e.delFromStorage,a=e.addToStorage;e.delFromStorage=function(t){o.call(e,t),t&&t.onRemove&&t.onRemove(n)},e.addToStorage=function(t){t.onAdd&&t.onAdd(n),a.call(e,t)},this._firstPaint=!0}function f_(t){return function(){Yw('In IE8.0 VML mode painter not support method "'+t+'"')}}function p_(t){return document.createElementNS(sR,t)}function g_(t){return cR(1e4*t)/1e4}function m_(t){return t-vR}function v_(t,e){var i=e?t.textFill:t.fill;return null!=i&&i!==hR}function y_(t,e){var i=e?t.textStroke:t.stroke;return null!=i&&i!==hR}function x_(t,e){e&&__(t,"transform","matrix("+uR.call(e,",")+")")}function __(t,e,i){(!i||"linear"!==i.type&&"radial"!==i.type)&&t.setAttribute(e,i)}function w_(t,e,i){t.setAttributeNS("http://www.w3.org/1999/xlink",e,i)}function b_(t,e,i,n){if(v_(e,i)){var o=i?e.textFill:e.fill;o="transparent"===o?hR:o,"none"!==t.getAttribute("clip-path")&&o===hR&&(o="rgba(0, 0, 0, 0.002)"),__(t,"fill",o),__(t,"fill-opacity",null!=e.fillOpacity?e.fillOpacity*e.opacity:e.opacity)}else __(t,"fill",hR);if(y_(e,i)){var a=i?e.textStroke:e.stroke;__(t,"stroke",a="transparent"===a?hR:a),__(t,"stroke-width",(i?e.textStrokeWidth:e.lineWidth)/(!i&&e.strokeNoScale?n.getLineScale():1)),__(t,"paint-order",i?"stroke":"fill"),__(t,"stroke-opacity",null!=e.strokeOpacity?e.strokeOpacity:e.opacity),e.lineDash?(__(t,"stroke-dasharray",e.lineDash.join(",")),__(t,"stroke-dashoffset",cR(e.lineDashOffset||0))):__(t,"stroke-dasharray",""),e.lineCap&&__(t,"stroke-linecap",e.lineCap),e.lineJoin&&__(t,"stroke-linejoin",e.lineJoin),e.miterLimit&&__(t,"stroke-miterlimit",e.miterLimit)}else __(t,"stroke",hR)}function S_(t){for(var e=[],i=t.data,n=t.len(),o=0;o=gR||!m_(g)&&(d>-pR&&d<0||d>pR)==!!p;var y=g_(s+u*fR(c)),x=g_(l+h*dR(c));m&&(d=p?gR-1e-4:1e-4-gR,v=!0,9===o&&e.push("M",y,x));var _=g_(s+u*fR(c+d)),w=g_(l+h*dR(c+d));e.push("A",g_(u),g_(h),cR(f*mR),+v,+p,_,w);break;case lR.Z:a="Z";break;case lR.R:var _=g_(i[o++]),w=g_(i[o++]),b=g_(i[o++]),S=g_(i[o++]);e.push("M",_,w,"L",_+b,w,"L",_+b,w+S,"L",_,w+S,"L",_,w)}a&&e.push(a);for(var M=0;M=11),domSupported:"undefined"!=typeof document}}(navigator.userAgent),X_={"[object Function]":1,"[object RegExp]":1,"[object Date]":1,"[object Error]":1,"[object CanvasGradient]":1,"[object CanvasPattern]":1,"[object Image]":1,"[object Canvas]":1},j_={"[object Int8Array]":1,"[object Uint8Array]":1,"[object Uint8ClampedArray]":1,"[object Int16Array]":1,"[object Uint16Array]":1,"[object Int32Array]":1,"[object Uint32Array]":1,"[object Float32Array]":1,"[object Float64Array]":1},Y_=Object.prototype.toString,q_=Array.prototype,K_=q_.forEach,$_=q_.filter,J_=q_.slice,Q_=q_.map,tw=q_.reduce,ew={},iw=function(){return ew.createCanvas()};ew.createCanvas=function(){return document.createElement("canvas")};var nw,ow="__ec_primitive__";E.prototype={constructor:E,get:function(t){return this.data.hasOwnProperty(t)?this.data[t]:null},set:function(t,e){return this.data[t]=e},each:function(t,e){void 0!==e&&(t=m(t,e));for(var i in this.data)this.data.hasOwnProperty(i)&&t(this.data[i],i)},removeKey:function(t){delete this.data[t]}};var aw=(Object.freeze||Object)({$override:e,clone:i,merge:n,mergeAll:o,extend:a,defaults:r,createCanvas:iw,getContext:s,indexOf:l,inherits:u,mixin:h,isArrayLike:c,each:d,map:f,reduce:p,filter:g,find:function(t,e,i){if(t&&e)for(var n=0,o=t.length;n3&&(n=dw.call(n,1));for(var a=e.length,r=0;r4&&(n=dw.call(n,1,n.length-1));for(var a=n[n.length-1],r=e.length,s=0;s1&&n&&n.length>1){var a=ft(n)/ft(o);!isFinite(a)&&(a=1),e.pinchScale=a;var r=pt(n);return e.pinchX=r[0],e.pinchY=r[1],{type:"pinch",target:t[0].target,event:e}}}}},xw="silent";vt.prototype.dispose=function(){};var _w=["click","dblclick","mousewheel","mouseout","mouseup","mousedown","mousemove","contextmenu"],ww=function(t,e,i,n){fw.call(this),this.storage=t,this.painter=e,this.painterRoot=n,i=i||new vt,this.proxy=null,this._hovered={},this._lastTouchMoment,this._lastX,this._lastY,this._gestureMgr,it.call(this),this.setHandlerProxy(i)};ww.prototype={constructor:ww,setHandlerProxy:function(t){this.proxy&&this.proxy.dispose(),t&&(d(_w,function(e){t.on&&t.on(e,this[e],this)},this),t.handler=this),this.proxy=t},mousemove:function(t){var e=t.zrX,i=t.zrY,n=this._hovered,o=n.target;o&&!o.__zr&&(o=(n=this.findHover(n.x,n.y)).target);var a=this._hovered=this.findHover(e,i),r=a.target,s=this.proxy;s.setCursor&&s.setCursor(r?r.cursor:"default"),o&&r!==o&&this.dispatchToElement(n,"mouseout",t),this.dispatchToElement(a,"mousemove",t),r&&r!==o&&this.dispatchToElement(a,"mouseover",t)},mouseout:function(t){this.dispatchToElement(this._hovered,"mouseout",t);var e,i=t.toElement||t.relatedTarget;do{i=i&&i.parentNode}while(i&&9!==i.nodeType&&!(e=i===this.painterRoot));!e&&this.trigger("globalout",{event:t})},resize:function(t){this._hovered={}},dispatch:function(t,e){var i=this[t];i&&i.call(this,e)},dispose:function(){this.proxy.dispose(),this.storage=this.proxy=this.painter=null},setCursorStyle:function(t){var e=this.proxy;e.setCursor&&e.setCursor(t)},dispatchToElement:function(t,e,i){var n=(t=t||{}).target;if(!n||!n.silent){for(var o="on"+e,a=gt(e,t,i);n&&(n[o]&&(a.cancelBubble=n[o].call(n,a)),n.trigger(e,a),n=n.parent,!a.cancelBubble););a.cancelBubble||(this.trigger(e,a),this.painter&&this.painter.eachOtherLayer(function(t){"function"==typeof t[o]&&t[o].call(t,a),t.trigger&&t.trigger(e,a)}))}},findHover:function(t,e,i){for(var n=this.storage.getDisplayList(),o={x:t,y:e},a=n.length-1;a>=0;a--){var r;if(n[a]!==i&&!n[a].ignore&&(r=yt(n[a],t,e))&&(!o.topTarget&&(o.topTarget=n[a]),r!==xw)){o.target=n[a];break}}return o},processGesture:function(t,e){this._gestureMgr||(this._gestureMgr=new vw);var i=this._gestureMgr;"start"===e&&i.clear();var n=i.recognize(t,this.findHover(t.zrX,t.zrY,null).target,this.proxy.dom);if("end"===e&&i.clear(),n){var o=n.type;t.gestureEvent=o,this.dispatchToElement({target:n.target},o,n.event)}}},d(["click","mousedown","mouseup","mousewheel","dblclick","contextmenu"],function(t){ww.prototype[t]=function(e){var i=this.findHover(e.zrX,e.zrY),n=i.target;if("mousedown"===t)this._downEl=n,this._downPoint=[e.zrX,e.zrY],this._upEl=n;else if("mouseup"===t)this._upEl=n;else if("click"===t){if(this._downEl!==this._upEl||!this._downPoint||uw(this._downPoint,[e.zrX,e.zrY])>4)return;this._downPoint=null}this.dispatchToElement(i,t,e)}}),h(ww,fw),h(ww,it);var bw="undefined"==typeof Float32Array?Array:Float32Array,Sw=(Object.freeze||Object)({create:xt,identity:_t,copy:wt,mul:bt,translate:St,rotate:Mt,scale:It,invert:Tt,clone:At}),Mw=_t,Iw=5e-5,Tw=function(t){(t=t||{}).position||(this.position=[0,0]),null==t.rotation&&(this.rotation=0),t.scale||(this.scale=[1,1]),this.origin=this.origin||null},Aw=Tw.prototype;Aw.transform=null,Aw.needLocalTransform=function(){return Dt(this.rotation)||Dt(this.position[0])||Dt(this.position[1])||Dt(this.scale[0]-1)||Dt(this.scale[1]-1)};var Dw=[];Aw.updateTransform=function(){var t=this.parent,e=t&&t.transform,i=this.needLocalTransform(),n=this.transform;if(i||e){n=n||xt(),i?this.getLocalTransform(n):Mw(n),e&&(i?bt(n,t.transform,n):wt(n,t.transform)),this.transform=n;var o=this.globalScaleRatio;if(null!=o&&1!==o){this.getGlobalScale(Dw);var a=Dw[0]<0?-1:1,r=Dw[1]<0?-1:1,s=((Dw[0]-a)*o+a)/Dw[0]||0,l=((Dw[1]-r)*o+r)/Dw[1]||0;n[0]*=s,n[1]*=s,n[2]*=l,n[3]*=l}this.invTransform=this.invTransform||xt(),Tt(this.invTransform,n)}else n&&Mw(n)},Aw.getLocalTransform=function(t){return Tw.getLocalTransform(this,t)},Aw.setTransform=function(t){var e=this.transform,i=t.dpr||1;e?t.setTransform(i*e[0],i*e[1],i*e[2],i*e[3],i*e[4],i*e[5]):t.setTransform(i,0,0,i,0,0)},Aw.restoreTransform=function(t){var e=t.dpr||1;t.setTransform(e,0,0,e,0,0)};var Cw=[],Lw=xt();Aw.setLocalTransform=function(t){if(t){var e=t[0]*t[0]+t[1]*t[1],i=t[2]*t[2]+t[3]*t[3],n=this.position,o=this.scale;Dt(e-1)&&(e=Math.sqrt(e)),Dt(i-1)&&(i=Math.sqrt(i)),t[0]<0&&(e=-e),t[3]<0&&(i=-i),n[0]=t[4],n[1]=t[5],o[0]=e,o[1]=i,this.rotation=Math.atan2(-t[1]/i,t[0]/e)}},Aw.decomposeTransform=function(){if(this.transform){var t=this.parent,e=this.transform;t&&t.transform&&(bt(Cw,t.invTransform,e),e=Cw);var i=this.origin;i&&(i[0]||i[1])&&(Lw[4]=i[0],Lw[5]=i[1],bt(Cw,e,Lw),Cw[4]-=i[0],Cw[5]-=i[1],e=Cw),this.setLocalTransform(e)}},Aw.getGlobalScale=function(t){var e=this.transform;return t=t||[],e?(t[0]=Math.sqrt(e[0]*e[0]+e[1]*e[1]),t[1]=Math.sqrt(e[2]*e[2]+e[3]*e[3]),e[0]<0&&(t[0]=-t[0]),e[3]<0&&(t[1]=-t[1]),t):(t[0]=1,t[1]=1,t)},Aw.transformCoordToLocal=function(t,e){var i=[t,e],n=this.invTransform;return n&&Q(i,i,n),i},Aw.transformCoordToGlobal=function(t,e){var i=[t,e],n=this.transform;return n&&Q(i,i,n),i},Tw.getLocalTransform=function(t,e){Mw(e=e||[]);var i=t.origin,n=t.scale||[1,1],o=t.rotation||0,a=t.position||[0,0];return i&&(e[4]-=i[0],e[5]-=i[1]),It(e,e,n),o&&Mt(e,e,o),i&&(e[4]+=i[0],e[5]+=i[1]),e[4]+=a[0],e[5]+=a[1],e};var kw={linear:function(t){return t},quadraticIn:function(t){return t*t},quadraticOut:function(t){return t*(2-t)},quadraticInOut:function(t){return(t*=2)<1?.5*t*t:-.5*(--t*(t-2)-1)},cubicIn:function(t){return t*t*t},cubicOut:function(t){return--t*t*t+1},cubicInOut:function(t){return(t*=2)<1?.5*t*t*t:.5*((t-=2)*t*t+2)},quarticIn:function(t){return t*t*t*t},quarticOut:function(t){return 1- --t*t*t*t},quarticInOut:function(t){return(t*=2)<1?.5*t*t*t*t:-.5*((t-=2)*t*t*t-2)},quinticIn:function(t){return t*t*t*t*t},quinticOut:function(t){return--t*t*t*t*t+1},quinticInOut:function(t){return(t*=2)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2)},sinusoidalIn:function(t){return 1-Math.cos(t*Math.PI/2)},sinusoidalOut:function(t){return Math.sin(t*Math.PI/2)},sinusoidalInOut:function(t){return.5*(1-Math.cos(Math.PI*t))},exponentialIn:function(t){return 0===t?0:Math.pow(1024,t-1)},exponentialOut:function(t){return 1===t?1:1-Math.pow(2,-10*t)},exponentialInOut:function(t){return 0===t?0:1===t?1:(t*=2)<1?.5*Math.pow(1024,t-1):.5*(2-Math.pow(2,-10*(t-1)))},circularIn:function(t){return 1-Math.sqrt(1-t*t)},circularOut:function(t){return Math.sqrt(1- --t*t)},circularInOut:function(t){return(t*=2)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},elasticIn:function(t){var e,i=.1;return 0===t?0:1===t?1:(!i||i<1?(i=1,e=.1):e=.4*Math.asin(1/i)/(2*Math.PI),-i*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/.4))},elasticOut:function(t){var e,i=.1;return 0===t?0:1===t?1:(!i||i<1?(i=1,e=.1):e=.4*Math.asin(1/i)/(2*Math.PI),i*Math.pow(2,-10*t)*Math.sin((t-e)*(2*Math.PI)/.4)+1)},elasticInOut:function(t){var e,i=.1;return 0===t?0:1===t?1:(!i||i<1?(i=1,e=.1):e=.4*Math.asin(1/i)/(2*Math.PI),(t*=2)<1?i*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/.4)*-.5:i*Math.pow(2,-10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/.4)*.5+1)},backIn:function(t){var e=1.70158;return t*t*((e+1)*t-e)},backOut:function(t){var e=1.70158;return--t*t*((e+1)*t+e)+1},backInOut:function(t){var e=2.5949095;return(t*=2)<1?t*t*((e+1)*t-e)*.5:.5*((t-=2)*t*((e+1)*t+e)+2)},bounceIn:function(t){return 1-kw.bounceOut(1-t)},bounceOut:function(t){return t<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375},bounceInOut:function(t){return t<.5?.5*kw.bounceIn(2*t):.5*kw.bounceOut(2*t-1)+.5}};Ct.prototype={constructor:Ct,step:function(t,e){if(this._initialized||(this._startTime=t+this._delay,this._initialized=!0),this._paused)this._pausedTime+=e;else{var i=(t-this._startTime-this._pausedTime)/this._life;if(!(i<0)){i=Math.min(i,1);var n=this.easing,o="string"==typeof n?kw[n]:n,a="function"==typeof o?o(i):i;return this.fire("frame",a),1===i?this.loop?(this.restart(t),"restart"):(this._needsRemove=!0,"destroy"):null}}},restart:function(t){var e=(t-this._startTime-this._pausedTime)%this._life;this._startTime=t-e+this.gap,this._pausedTime=0,this._needsRemove=!1},fire:function(t,e){this[t="on"+t]&&this[t](this._target,e)},pause:function(){this._paused=!0},resume:function(){this._paused=!1}};var Pw=function(){this.head=null,this.tail=null,this._len=0},Nw=Pw.prototype;Nw.insert=function(t){var e=new Ow(t);return this.insertEntry(e),e},Nw.insertEntry=function(t){this.head?(this.tail.next=t,t.prev=this.tail,t.next=null,this.tail=t):this.head=this.tail=t,this._len++},Nw.remove=function(t){var e=t.prev,i=t.next;e?e.next=i:this.head=i,i?i.prev=e:this.tail=e,t.next=t.prev=null,this._len--},Nw.len=function(){return this._len},Nw.clear=function(){this.head=this.tail=null,this._len=0};var Ow=function(t){this.value=t,this.next,this.prev},Ew=function(t){this._list=new Pw,this._map={},this._maxSize=t||10,this._lastRemovedEntry=null},Rw=Ew.prototype;Rw.put=function(t,e){var i=this._list,n=this._map,o=null;if(null==n[t]){var a=i.len(),r=this._lastRemovedEntry;if(a>=this._maxSize&&a>0){var s=i.head;i.remove(s),delete n[s.key],o=s.value,this._lastRemovedEntry=s}r?r.value=e:r=new Ow(e),r.key=t,i.insertEntry(r),n[t]=r}return o},Rw.get=function(t){var e=this._map[t],i=this._list;if(null!=e)return e!==i.tail&&(i.remove(e),i.insertEntry(e)),e.value},Rw.clear=function(){this._list.clear(),this._map={}};var zw={transparent:[0,0,0,0],aliceblue:[240,248,255,1],antiquewhite:[250,235,215,1],aqua:[0,255,255,1],aquamarine:[127,255,212,1],azure:[240,255,255,1],beige:[245,245,220,1],bisque:[255,228,196,1],black:[0,0,0,1],blanchedalmond:[255,235,205,1],blue:[0,0,255,1],blueviolet:[138,43,226,1],brown:[165,42,42,1],burlywood:[222,184,135,1],cadetblue:[95,158,160,1],chartreuse:[127,255,0,1],chocolate:[210,105,30,1],coral:[255,127,80,1],cornflowerblue:[100,149,237,1],cornsilk:[255,248,220,1],crimson:[220,20,60,1],cyan:[0,255,255,1],darkblue:[0,0,139,1],darkcyan:[0,139,139,1],darkgoldenrod:[184,134,11,1],darkgray:[169,169,169,1],darkgreen:[0,100,0,1],darkgrey:[169,169,169,1],darkkhaki:[189,183,107,1],darkmagenta:[139,0,139,1],darkolivegreen:[85,107,47,1],darkorange:[255,140,0,1],darkorchid:[153,50,204,1],darkred:[139,0,0,1],darksalmon:[233,150,122,1],darkseagreen:[143,188,143,1],darkslateblue:[72,61,139,1],darkslategray:[47,79,79,1],darkslategrey:[47,79,79,1],darkturquoise:[0,206,209,1],darkviolet:[148,0,211,1],deeppink:[255,20,147,1],deepskyblue:[0,191,255,1],dimgray:[105,105,105,1],dimgrey:[105,105,105,1],dodgerblue:[30,144,255,1],firebrick:[178,34,34,1],floralwhite:[255,250,240,1],forestgreen:[34,139,34,1],fuchsia:[255,0,255,1],gainsboro:[220,220,220,1],ghostwhite:[248,248,255,1],gold:[255,215,0,1],goldenrod:[218,165,32,1],gray:[128,128,128,1],green:[0,128,0,1],greenyellow:[173,255,47,1],grey:[128,128,128,1],honeydew:[240,255,240,1],hotpink:[255,105,180,1],indianred:[205,92,92,1],indigo:[75,0,130,1],ivory:[255,255,240,1],khaki:[240,230,140,1],lavender:[230,230,250,1],lavenderblush:[255,240,245,1],lawngreen:[124,252,0,1],lemonchiffon:[255,250,205,1],lightblue:[173,216,230,1],lightcoral:[240,128,128,1],lightcyan:[224,255,255,1],lightgoldenrodyellow:[250,250,210,1],lightgray:[211,211,211,1],lightgreen:[144,238,144,1],lightgrey:[211,211,211,1],lightpink:[255,182,193,1],lightsalmon:[255,160,122,1],lightseagreen:[32,178,170,1],lightskyblue:[135,206,250,1],lightslategray:[119,136,153,1],lightslategrey:[119,136,153,1],lightsteelblue:[176,196,222,1],lightyellow:[255,255,224,1],lime:[0,255,0,1],limegreen:[50,205,50,1],linen:[250,240,230,1],magenta:[255,0,255,1],maroon:[128,0,0,1],mediumaquamarine:[102,205,170,1],mediumblue:[0,0,205,1],mediumorchid:[186,85,211,1],mediumpurple:[147,112,219,1],mediumseagreen:[60,179,113,1],mediumslateblue:[123,104,238,1],mediumspringgreen:[0,250,154,1],mediumturquoise:[72,209,204,1],mediumvioletred:[199,21,133,1],midnightblue:[25,25,112,1],mintcream:[245,255,250,1],mistyrose:[255,228,225,1],moccasin:[255,228,181,1],navajowhite:[255,222,173,1],navy:[0,0,128,1],oldlace:[253,245,230,1],olive:[128,128,0,1],olivedrab:[107,142,35,1],orange:[255,165,0,1],orangered:[255,69,0,1],orchid:[218,112,214,1],palegoldenrod:[238,232,170,1],palegreen:[152,251,152,1],paleturquoise:[175,238,238,1],palevioletred:[219,112,147,1],papayawhip:[255,239,213,1],peachpuff:[255,218,185,1],peru:[205,133,63,1],pink:[255,192,203,1],plum:[221,160,221,1],powderblue:[176,224,230,1],purple:[128,0,128,1],red:[255,0,0,1],rosybrown:[188,143,143,1],royalblue:[65,105,225,1],saddlebrown:[139,69,19,1],salmon:[250,128,114,1],sandybrown:[244,164,96,1],seagreen:[46,139,87,1],seashell:[255,245,238,1],sienna:[160,82,45,1],silver:[192,192,192,1],skyblue:[135,206,235,1],slateblue:[106,90,205,1],slategray:[112,128,144,1],slategrey:[112,128,144,1],snow:[255,250,250,1],springgreen:[0,255,127,1],steelblue:[70,130,180,1],tan:[210,180,140,1],teal:[0,128,128,1],thistle:[216,191,216,1],tomato:[255,99,71,1],turquoise:[64,224,208,1],violet:[238,130,238,1],wheat:[245,222,179,1],white:[255,255,255,1],whitesmoke:[245,245,245,1],yellow:[255,255,0,1],yellowgreen:[154,205,50,1]},Bw=new Ew(20),Vw=null,Gw=Ut,Fw=Xt,Ww=(Object.freeze||Object)({parse:Gt,lift:Ht,toHex:Zt,fastLerp:Ut,fastMapToColor:Gw,lerp:Xt,mapToColor:Fw,modifyHSL:jt,modifyAlpha:Yt,stringify:qt}),Hw=Array.prototype.slice,Zw=function(t,e,i,n){this._tracks={},this._target=t,this._loop=e||!1,this._getter=i||Kt,this._setter=n||$t,this._clipCount=0,this._delay=0,this._doneList=[],this._onframeList=[],this._clipList=[]};Zw.prototype={when:function(t,e){var i=this._tracks;for(var n in e)if(e.hasOwnProperty(n)){if(!i[n]){i[n]=[];var o=this._getter(this._target,n);if(null==o)continue;0!==t&&i[n].push({time:0,value:ae(o)})}i[n].push({time:t,value:e[n]})}return this},during:function(t){return this._onframeList.push(t),this},pause:function(){for(var t=0;t=i.x&&t<=i.x+i.width&&e>=i.y&&e<=i.y+i.height},clone:function(){return new de(this.x,this.y,this.width,this.height)},copy:function(t){this.x=t.x,this.y=t.y,this.width=t.width,this.height=t.height},plain:function(){return{x:this.x,y:this.y,width:this.width,height:this.height}}},de.create=function(t){return new de(t.x,t.y,t.width,t.height)};var tb=function(t){t=t||{},Kw.call(this,t);for(var e in t)t.hasOwnProperty(e)&&(this[e]=t[e]);this._children=[],this.__storage=null,this.__dirty=!0};tb.prototype={constructor:tb,isGroup:!0,type:"group",silent:!1,children:function(){return this._children.slice()},childAt:function(t){return this._children[t]},childOfName:function(t){for(var e=this._children,i=0;i=0&&(i.splice(n,0,t),this._doAdd(t))}return this},_doAdd:function(t){t.parent&&t.parent.remove(t),t.parent=this;var e=this.__storage,i=this.__zr;e&&e!==t.__storage&&(e.addToStorage(t),t instanceof tb&&t.addChildrenToStorage(e)),i&&i.refresh()},remove:function(t){var e=this.__zr,i=this.__storage,n=this._children,o=l(n,t);return o<0?this:(n.splice(o,1),t.parent=null,i&&(i.delFromStorage(t),t instanceof tb&&t.delChildrenFromStorage(i)),e&&e.refresh(),this)},removeAll:function(){var t,e,i=this._children,n=this.__storage;for(e=0;e=0&&(this.delFromStorage(t),this._roots.splice(o,1),t instanceof tb&&t.delChildrenFromStorage(this))}},addToStorage:function(t){return t&&(t.__storage=this,t.dirty(!1)),this},delFromStorage:function(t){return t&&(t.__storage=null),this},dispose:function(){this._renderList=this._roots=null},displayableSortFunc:we};var ob={shadowBlur:1,shadowOffsetX:1,shadowOffsetY:1,textShadowBlur:1,textShadowOffsetX:1,textShadowOffsetY:1,textBoxShadowBlur:1,textBoxShadowOffsetX:1,textBoxShadowOffsetY:1},ab=function(t,e,i){return ob.hasOwnProperty(e)?i*=t.dpr:i},rb={NONE:0,STYLE_BIND:1,PLAIN_TEXT:2},sb=9,lb=[["shadowBlur",0],["shadowOffsetX",0],["shadowOffsetY",0],["shadowColor","#000"],["lineCap","butt"],["lineJoin","miter"],["miterLimit",10]],ub=function(t){this.extendFrom(t,!1)};ub.prototype={constructor:ub,fill:"#000",stroke:null,opacity:1,fillOpacity:null,strokeOpacity:null,lineDash:null,lineDashOffset:0,shadowBlur:0,shadowOffsetX:0,shadowOffsetY:0,lineWidth:1,strokeNoScale:!1,text:null,font:null,textFont:null,fontStyle:null,fontWeight:null,fontSize:null,fontFamily:null,textTag:null,textFill:"#000",textStroke:null,textWidth:null,textHeight:null,textStrokeWidth:0,textLineHeight:null,textPosition:"inside",textRect:null,textOffset:null,textAlign:null,textVerticalAlign:null,textDistance:5,textShadowColor:"transparent",textShadowBlur:0,textShadowOffsetX:0,textShadowOffsetY:0,textBoxShadowColor:"transparent",textBoxShadowBlur:0,textBoxShadowOffsetX:0,textBoxShadowOffsetY:0,transformText:!1,textRotation:0,textOrigin:null,textBackgroundColor:null,textBorderColor:null,textBorderWidth:0,textBorderRadius:0,textPadding:null,rich:null,truncate:null,blend:null,bind:function(t,e,i){var n=this,o=i&&i.style,a=!o||t.__attrCachedBy!==rb.STYLE_BIND;t.__attrCachedBy=rb.STYLE_BIND;for(var r=0;r0},extendFrom:function(t,e){if(t)for(var i in t)!t.hasOwnProperty(i)||!0!==e&&(!1===e?this.hasOwnProperty(i):null==t[i])||(this[i]=t[i])},set:function(t,e){"string"==typeof t?this[t]=e:this.extendFrom(t,!0)},clone:function(){var t=new this.constructor;return t.extendFrom(this,!0),t},getGradient:function(t,e,i){for(var n=("radial"===e.type?Se:be)(t,e,i),o=e.colorStops,a=0;a=0&&i.splice(n,1),t.__hoverMir=null},clearHover:function(t){for(var e=this._hoverElements,i=0;i15)break}s.__drawIndex=m,s.__drawIndex0&&t>n[0]){for(r=0;rt);r++);a=i[n[r]]}if(n.splice(r+1,0,t),i[t]=e,!e.virtual)if(a){var l=a.dom;l.nextSibling?s.insertBefore(e.dom,l.nextSibling):s.appendChild(e.dom)}else s.firstChild?s.insertBefore(e.dom,s.firstChild):s.appendChild(e.dom)}else Yw("Layer of zlevel "+t+" is not valid")},eachLayer:function(t,e){var i,n,o=this._zlevelList;for(n=0;n0?.01:0),this._needsManuallyCompositing),a.__builtin__||Yw("ZLevel "+s+" has been used by unkown layer "+a.id),a!==i&&(a.__used=!0,a.__startIndex!==o&&(a.__dirty=!0),a.__startIndex=o,a.incremental?a.__drawIndex=-1:a.__drawIndex=o,e(o),i=a),r.__dirty&&(a.__dirty=!0,a.incremental&&a.__drawIndex<0&&(a.__drawIndex=o))}e(o),this.eachBuiltinLayer(function(t,e){!t.__used&&t.getElementCount()>0&&(t.__dirty=!0,t.__startIndex=t.__endIndex=t.__drawIndex=0),t.__dirty&&t.__drawIndex<0&&(t.__drawIndex=t.__startIndex)})},clear:function(){return this.eachBuiltinLayer(this._clearLayer),this},_clearLayer:function(t){t.clear()},setBackgroundColor:function(t){this._backgroundColor=t},configLayer:function(t,e){if(e){var i=this._layerConfig;i[t]?n(i[t],e,!0):i[t]=e;for(var o=0;o=0&&this._clips.splice(e,1)},removeAnimator:function(t){for(var e=t.getClips(),i=0;i=0||n&&l(n,r)<0)){var s=e.getShallow(r);null!=s&&(o[t[a][0]]=s)}}return o}},tS=Qb([["lineWidth","width"],["stroke","color"],["opacity"],["shadowBlur"],["shadowOffsetX"],["shadowOffsetY"],["shadowColor"]]),eS={getLineStyle:function(t){var e=tS(this,t),i=this.getLineDash(e.lineWidth);return i&&(e.lineDash=i),e},getLineDash:function(t){null==t&&(t=1);var e=this.get("type"),i=Math.max(t,2),n=4*t;return"solid"===e||null==e?null:"dashed"===e?[n,n]:[i,i]}},iS=Qb([["fill","color"],["shadowBlur"],["shadowOffsetX"],["shadowOffsetY"],["opacity"],["shadowColor"]]),nS={getAreaStyle:function(t,e){return iS(this,t,e)}},oS=Math.pow,aS=Math.sqrt,rS=1e-8,sS=1e-4,lS=aS(3),uS=1/3,hS=V(),cS=V(),dS=V(),fS=Math.min,pS=Math.max,gS=Math.sin,mS=Math.cos,vS=2*Math.PI,yS=V(),xS=V(),_S=V(),wS=[],bS=[],SS={M:1,L:2,C:3,Q:4,A:5,Z:6,R:7},MS=[],IS=[],TS=[],AS=[],DS=Math.min,CS=Math.max,LS=Math.cos,kS=Math.sin,PS=Math.sqrt,NS=Math.abs,OS="undefined"!=typeof Float32Array,ES=function(t){this._saveData=!t,this._saveData&&(this.data=[]),this._ctx=null};ES.prototype={constructor:ES,_xi:0,_yi:0,_x0:0,_y0:0,_ux:0,_uy:0,_len:0,_lineDash:null,_dashOffset:0,_dashIdx:0,_dashSum:0,setScale:function(t,e){this._ux=NS(1/Xw/t)||0,this._uy=NS(1/Xw/e)||0},getContext:function(){return this._ctx},beginPath:function(t){return this._ctx=t,t&&t.beginPath(),t&&(this.dpr=t.dpr),this._saveData&&(this._len=0),this._lineDash&&(this._lineDash=null,this._dashOffset=0),this},moveTo:function(t,e){return this.addData(SS.M,t,e),this._ctx&&this._ctx.moveTo(t,e),this._x0=t,this._y0=e,this._xi=t,this._yi=e,this},lineTo:function(t,e){var i=NS(t-this._xi)>this._ux||NS(e-this._yi)>this._uy||this._len<5;return this.addData(SS.L,t,e),this._ctx&&i&&(this._needsDash()?this._dashedLineTo(t,e):this._ctx.lineTo(t,e)),i&&(this._xi=t,this._yi=e),this},bezierCurveTo:function(t,e,i,n,o,a){return this.addData(SS.C,t,e,i,n,o,a),this._ctx&&(this._needsDash()?this._dashedBezierTo(t,e,i,n,o,a):this._ctx.bezierCurveTo(t,e,i,n,o,a)),this._xi=o,this._yi=a,this},quadraticCurveTo:function(t,e,i,n){return this.addData(SS.Q,t,e,i,n),this._ctx&&(this._needsDash()?this._dashedQuadraticTo(t,e,i,n):this._ctx.quadraticCurveTo(t,e,i,n)),this._xi=i,this._yi=n,this},arc:function(t,e,i,n,o,a){return this.addData(SS.A,t,e,i,i,n,o-n,0,a?0:1),this._ctx&&this._ctx.arc(t,e,i,n,o,a),this._xi=LS(o)*i+t,this._yi=kS(o)*i+e,this},arcTo:function(t,e,i,n,o){return this._ctx&&this._ctx.arcTo(t,e,i,n,o),this},rect:function(t,e,i,n){return this._ctx&&this._ctx.rect(t,e,i,n),this.addData(SS.R,t,e,i,n),this},closePath:function(){this.addData(SS.Z);var t=this._ctx,e=this._x0,i=this._y0;return t&&(this._needsDash()&&this._dashedLineTo(e,i),t.closePath()),this._xi=e,this._yi=i,this},fill:function(t){t&&t.fill(),this.toStatic()},stroke:function(t){t&&t.stroke(),this.toStatic()},setLineDash:function(t){if(t instanceof Array){this._lineDash=t,this._dashIdx=0;for(var e=0,i=0;ie.length&&(this._expandData(),e=this.data);for(var i=0;i0&&f<=t||h<0&&f>=t||0===h&&(c>0&&p<=e||c<0&&p>=e);)f+=h*(i=r[n=this._dashIdx]),p+=c*i,this._dashIdx=(n+1)%g,h>0&&fl||c>0&&pu||s[n%2?"moveTo":"lineTo"](h>=0?DS(f,t):CS(f,t),c>=0?DS(p,e):CS(p,e));h=f-t,c=p-e,this._dashOffset=-PS(h*h+c*c)},_dashedBezierTo:function(t,e,i,n,o,a){var r,s,l,u,h,c=this._dashSum,d=this._dashOffset,f=this._lineDash,p=this._ctx,g=this._xi,m=this._yi,v=tn,y=0,x=this._dashIdx,_=f.length,w=0;for(d<0&&(d=c+d),d%=c,r=0;r<1;r+=.1)s=v(g,t,i,o,r+.1)-v(g,t,i,o,r),l=v(m,e,n,a,r+.1)-v(m,e,n,a,r),y+=PS(s*s+l*l);for(;x<_&&!((w+=f[x])>d);x++);for(r=(w-d)/y;r<=1;)u=v(g,t,i,o,r),h=v(m,e,n,a,r),x%2?p.moveTo(u,h):p.lineTo(u,h),r+=f[x]/y,x=(x+1)%_;x%2!=0&&p.lineTo(o,a),s=o-u,l=a-h,this._dashOffset=-PS(s*s+l*l)},_dashedQuadraticTo:function(t,e,i,n){var o=i,a=n;i=(i+2*t)/3,n=(n+2*e)/3,t=(this._xi+2*t)/3,e=(this._yi+2*e)/3,this._dashedBezierTo(t,e,i,n,o,a)},toStatic:function(){var t=this.data;t instanceof Array&&(t.length=this._len,OS&&(this.data=new Float32Array(t)))},getBoundingRect:function(){MS[0]=MS[1]=TS[0]=TS[1]=Number.MAX_VALUE,IS[0]=IS[1]=AS[0]=AS[1]=-Number.MAX_VALUE;for(var t=this.data,e=0,i=0,n=0,o=0,a=0;al||NS(r-o)>u||c===h-1)&&(t.lineTo(a,r),n=a,o=r);break;case SS.C:t.bezierCurveTo(s[c++],s[c++],s[c++],s[c++],s[c++],s[c++]),n=s[c-2],o=s[c-1];break;case SS.Q:t.quadraticCurveTo(s[c++],s[c++],s[c++],s[c++]),n=s[c-2],o=s[c-1];break;case SS.A:var f=s[c++],p=s[c++],g=s[c++],m=s[c++],v=s[c++],y=s[c++],x=s[c++],_=s[c++],w=g>m?g:m,b=g>m?1:g/m,S=g>m?m/g:1,M=v+y;Math.abs(g-m)>.001?(t.translate(f,p),t.rotate(x),t.scale(b,S),t.arc(0,0,w,v,M,1-_),t.scale(1/b,1/S),t.rotate(-x),t.translate(-f,-p)):t.arc(f,p,w,v,M,1-_),1===c&&(e=LS(v)*g+f,i=kS(v)*m+p),n=LS(M)*g+f,o=kS(M)*m+p;break;case SS.R:e=n=s[c],i=o=s[c+1],t.rect(s[c++],s[c++],s[c++],s[c++]);break;case SS.Z:t.closePath(),n=e,o=i}}}},ES.CMD=SS;var RS=2*Math.PI,zS=2*Math.PI,BS=ES.CMD,VS=2*Math.PI,GS=1e-4,FS=[-1,-1,-1],WS=[-1,-1],HS=fb.prototype.getCanvasPattern,ZS=Math.abs,US=new ES(!0);Pn.prototype={constructor:Pn,type:"path",__dirtyPath:!0,strokeContainThreshold:5,subPixelOptimize:!1,brush:function(t,e){var i=this.style,n=this.path||US,o=i.hasStroke(),a=i.hasFill(),r=i.fill,s=i.stroke,l=a&&!!r.colorStops,u=o&&!!s.colorStops,h=a&&!!r.image,c=o&&!!s.image;if(i.bind(t,this,e),this.setTransform(t),this.__dirty){var d;l&&(d=d||this.getBoundingRect(),this._fillGradient=i.getGradient(t,r,d)),u&&(d=d||this.getBoundingRect(),this._strokeGradient=i.getGradient(t,s,d))}l?t.fillStyle=this._fillGradient:h&&(t.fillStyle=HS.call(r,t)),u?t.strokeStyle=this._strokeGradient:c&&(t.strokeStyle=HS.call(s,t));var f=i.lineDash,p=i.lineDashOffset,g=!!t.setLineDash,m=this.getGlobalScale();if(n.setScale(m[0],m[1]),this.__dirtyPath||f&&!g&&o?(n.beginPath(t),f&&!g&&(n.setLineDash(f),n.setLineDashOffset(p)),this.buildPath(n,this.shape,!1),this.path&&(this.__dirtyPath=!1)):(t.beginPath(),this.path.rebuildPath(t)),a)if(null!=i.fillOpacity){v=t.globalAlpha;t.globalAlpha=i.fillOpacity*i.opacity,n.fill(t),t.globalAlpha=v}else n.fill(t);if(f&&g&&(t.setLineDash(f),t.lineDashOffset=p),o)if(null!=i.strokeOpacity){var v=t.globalAlpha;t.globalAlpha=i.strokeOpacity*i.opacity,n.stroke(t),t.globalAlpha=v}else n.stroke(t);f&&g&&t.setLineDash([]),null!=i.text&&(this.restoreTransform(t),this.drawRectText(t,this.getBoundingRect()))},buildPath:function(t,e,i){},createPathProxy:function(){this.path=new ES},getBoundingRect:function(){var t=this._rect,e=this.style,i=!t;if(i){var n=this.path;n||(n=this.path=new ES),this.__dirtyPath&&(n.beginPath(),this.buildPath(n,this.shape,!1)),t=n.getBoundingRect()}if(this._rect=t,e.hasStroke()){var o=this._rectWithStroke||(this._rectWithStroke=t.clone());if(this.__dirty||i){o.copy(t);var a=e.lineWidth,r=e.strokeNoScale?this.getLineScale():1;e.hasFill()||(a=Math.max(a,this.strokeContainThreshold||4)),r>1e-10&&(o.width+=a/r,o.height+=a/r,o.x-=a/r/2,o.y-=a/r/2)}return o}return t},contain:function(t,e){var i=this.transformCoordToLocal(t,e),n=this.getBoundingRect(),o=this.style;if(t=i[0],e=i[1],n.contain(t,e)){var a=this.path.data;if(o.hasStroke()){var r=o.lineWidth,s=o.strokeNoScale?this.getLineScale():1;if(s>1e-10&&(o.hasFill()||(r=Math.max(r,this.strokeContainThreshold)),kn(a,r/s,t,e)))return!0}if(o.hasFill())return Ln(a,t,e)}return!1},dirty:function(t){null==t&&(t=!0),t&&(this.__dirtyPath=t,this._rect=null),this.__dirty=this.__dirtyText=!0,this.__zr&&this.__zr.refresh(),this.__clipTarget&&this.__clipTarget.dirty()},animateShape:function(t){return this.animate("shape",t)},attrKV:function(t,e){"shape"===t?(this.setShape(e),this.__dirtyPath=!0,this._rect=null):di.prototype.attrKV.call(this,t,e)},setShape:function(t,e){var i=this.shape;if(i){if(w(t))for(var n in t)t.hasOwnProperty(n)&&(i[n]=t[n]);else i[t]=e;this.dirty(!0)}return this},getLineScale:function(){var t=this.transform;return t&&ZS(t[0]-1)>1e-10&&ZS(t[3]-1)>1e-10?Math.sqrt(ZS(t[0]*t[3]-t[2]*t[1])):1}},Pn.extend=function(t){var e=function(e){Pn.call(this,e),t.style&&this.style.extendFrom(t.style,!1);var i=t.shape;if(i){this.shape=this.shape||{};var n=this.shape;for(var o in i)!n.hasOwnProperty(o)&&i.hasOwnProperty(o)&&(n[o]=i[o])}t.init&&t.init.call(this,e)};u(e,Pn);for(var i in t)"style"!==i&&"shape"!==i&&(e.prototype[i]=t[i]);return e},u(Pn,di);var XS=ES.CMD,jS=[[],[],[]],YS=Math.sqrt,qS=Math.atan2,KS=function(t,e){var i,n,o,a,r,s,l=t.data,u=XS.M,h=XS.C,c=XS.L,d=XS.R,f=XS.A,p=XS.Q;for(o=0,a=0;o=11?function(){var e,i=this.__clipPaths,n=this.style;if(i)for(var o=0;oi-2?i-1:c+1],u=t[c>i-3?i-1:c+2]);var p=d*d,g=d*p;n.push([Bn(s[0],f[0],l[0],u[0],d,p,g),Bn(s[1],f[1],l[1],u[1],d,p,g)])}return n},fM=function(t,e,i,n){var o,a,r,s,l=[],u=[],h=[],c=[];if(n){r=[1/0,1/0],s=[-1/0,-1/0];for(var d=0,f=t.length;d=i&&a>=o)return{x:i,y:o,width:n-i,height:a-o}},createIcon:Po,Group:tb,Image:fi,Text:rM,Circle:sM,Sector:hM,Ring:cM,Polygon:pM,Polyline:gM,Rect:yM,Line:_M,BezierCurve:bM,Arc:SM,IncrementalDisplayable:Zn,CompoundPath:MM,LinearGradient:TM,RadialGradient:AM,BoundingRect:de}),BM=["textStyle","color"],VM={getTextColor:function(t){var e=this.ecModel;return this.getShallow("color")||(!t&&e?e.get(BM):null)},getFont:function(){return So({fontStyle:this.getShallow("fontStyle"),fontWeight:this.getShallow("fontWeight"),fontSize:this.getShallow("fontSize"),fontFamily:this.getShallow("fontFamily")},this.ecModel)},getTextRect:function(t){return ke(t,this.getFont(),this.getShallow("align"),this.getShallow("verticalAlign")||this.getShallow("baseline"),this.getShallow("padding"),this.getShallow("lineHeight"),this.getShallow("rich"),this.getShallow("truncateText"))}},GM=Qb([["fill","color"],["stroke","borderColor"],["lineWidth","borderWidth"],["opacity"],["shadowBlur"],["shadowOffsetX"],["shadowOffsetY"],["shadowColor"],["textPosition"],["textAlign"]]),FM={getItemStyle:function(t,e){var i=GM(this,t,e),n=this.getBorderLineDash();return n&&(i.lineDash=n),i},getBorderLineDash:function(){var t=this.get("borderType");return"solid"===t||null==t?null:"dashed"===t?[5,5]:[1,1]}},WM=h,HM=Bi();No.prototype={constructor:No,init:null,mergeOption:function(t){n(this.option,t,!0)},get:function(t,e){return null==t?this.option:Oo(this.option,this.parsePath(t),!e&&Eo(this,t))},getShallow:function(t,e){var i=this.option,n=null==i?i:i[t],o=!e&&Eo(this,t);return null==n&&o&&(n=o.getShallow(t)),n},getModel:function(t,e){var i,n=null==t?this.option:Oo(this.option,t=this.parsePath(t));return e=e||(i=Eo(this,t))&&i.getModel(t),new No(n,e,this.ecModel)},isEmpty:function(){return null==this.option},restoreData:function(){},clone:function(){return new(0,this.constructor)(i(this.option))},setReadOnly:function(t){},parsePath:function(t){return"string"==typeof t&&(t=t.split(".")),t},customizeGetParent:function(t){HM(this).getParent=t},isAnimationEnabled:function(){if(!U_.node){if(null!=this.option.animation)return!!this.option.animation;if(this.parentModel)return this.parentModel.isAnimationEnabled()}}},ji(No),Yi(No),WM(No,eS),WM(No,nS),WM(No,VM),WM(No,FM);var ZM=0,UM=1e-4,XM=9007199254740991,jM=/^(?:(\d{4})(?:[-\/](\d{1,2})(?:[-\/](\d{1,2})(?:[T ](\d{1,2})(?::(\d\d)(?::(\d\d)(?:[.,](\d+))?)?)?(Z|[\+\-]\d\d:?\d\d)?)?)?)?)?$/,YM=(Object.freeze||Object)({linearMap:Bo,parsePercent:Vo,round:Go,asc:Fo,getPrecision:Wo,getPrecisionSafe:Ho,getPixelPrecision:Zo,getPercentWithPrecision:Uo,MAX_SAFE_INTEGER:XM,remRadian:Xo,isRadianAroundZero:jo,parseDate:Yo,quantity:qo,nice:$o,quantile:function(t,e){var i=(t.length-1)*e+1,n=Math.floor(i),o=+t[n-1],a=i-n;return a?o+a*(t[n]-o):o},reformIntervals:Jo,isNumeric:Qo}),qM=L,KM=/([&<>"'])/g,$M={"&":"&","<":"<",">":">",'"':""","'":"'"},JM=["a","b","c","d","e","f","g"],QM=function(t,e){return"{"+t+(null==e?"":e)+"}"},tI=ze,eI=(Object.freeze||Object)({addCommas:ta,toCamelCase:ea,normalizeCssArray:qM,encodeHTML:ia,formatTpl:na,formatTplSimple:oa,getTooltipMarker:aa,formatTime:sa,capitalFirst:la,truncateText:tI,getTextBoundingRect:function(t){return ke(t.text,t.font,t.textAlign,t.textVerticalAlign,t.textPadding,t.textLineHeight,t.rich,t.truncate)},getTextRect:function(t,e,i,n,o,a,r,s){return ke(t,e,i,n,o,s,a,r)}}),iI=d,nI=["left","right","top","bottom","width","height"],oI=[["width","left","right"],["height","top","bottom"]],aI=ua,rI=(v(ua,"vertical"),v(ua,"horizontal"),{getBoxLayoutParams:function(){return{left:this.get("left"),top:this.get("top"),right:this.get("right"),bottom:this.get("bottom"),width:this.get("width"),height:this.get("height")}}}),sI=Bi(),lI=No.extend({type:"component",id:"",name:"",mainType:"",subType:"",componentIndex:0,defaultOption:null,ecModel:null,dependentModels:[],uid:null,layoutMode:null,$constructor:function(t,e,i,n){No.call(this,t,e,i,n),this.uid=Ro("ec_cpt_model")},init:function(t,e,i,n){this.mergeDefaultAndTheme(t,i)},mergeDefaultAndTheme:function(t,e){var i=this.layoutMode,o=i?ga(t):{};n(t,e.getTheme().get(this.mainType)),n(t,this.getDefaultOption()),i&&pa(t,o,i)},mergeOption:function(t,e){n(this.option,t,!0);var i=this.layoutMode;i&&pa(this.option,t,i)},optionUpdated:function(t,e){},getDefaultOption:function(){var t=sI(this);if(!t.defaultOption){for(var e=[],i=this.constructor;i;){var o=i.prototype.defaultOption;o&&e.push(o),i=i.superClass}for(var a={},r=e.length-1;r>=0;r--)a=n(a,e[r],!0);t.defaultOption=a}return t.defaultOption},getReferringComponents:function(t){return this.ecModel.queryComponents({mainType:t,index:this.get(t+"Index",!0),id:this.get(t+"Id",!0)})}});$i(lI,{registerWhenExtend:!0}),function(t){var e={};t.registerSubTypeDefaulter=function(t,i){t=Ui(t),e[t.main]=i},t.determineSubType=function(i,n){var o=n.type;if(!o){var a=Ui(i).main;t.hasSubTypes(i)&&e[a]&&(o=e[a](n))}return o}}(lI),function(t,e){function i(t){var i={},a=[];return d(t,function(r){var s=n(i,r),u=o(s.originalDeps=e(r),t);s.entryCount=u.length,0===s.entryCount&&a.push(r),d(u,function(t){l(s.predecessor,t)<0&&s.predecessor.push(t);var e=n(i,t);l(e.successor,t)<0&&e.successor.push(r)})}),{graph:i,noEntryList:a}}function n(t,e){return t[e]||(t[e]={predecessor:[],successor:[]}),t[e]}function o(t,e){var i=[];return d(t,function(t){l(e,t)>=0&&i.push(t)}),i}t.topologicalTravel=function(t,e,n,o){function a(t){s[t].entryCount--,0===s[t].entryCount&&l.push(t)}if(t.length){var r=i(e),s=r.graph,l=r.noEntryList,u={};for(d(t,function(t){u[t]=!0});l.length;){var h=l.pop(),c=s[h],f=!!u[h];f&&(n.call(o,h,c.originalDeps.slice()),delete u[h]),d(c.successor,f?function(t){u[t]=!0,a(t)}:a)}d(u,function(){throw new Error("Circle dependency may exists")})}}}(lI,function(t){var e=[];return d(lI.getClassesByMainType(t),function(t){e=e.concat(t.prototype.dependencies||[])}),e=f(e,function(t){return Ui(t).main}),"dataset"!==t&&l(e,"dataset")<=0&&e.unshift("dataset"),e}),h(lI,rI);var uI="";"undefined"!=typeof navigator&&(uI=navigator.platform||"");var hI={color:["#c23531","#2f4554","#61a0a8","#d48265","#91c7ae","#749f83","#ca8622","#bda29a","#6e7074","#546570","#c4ccd3"],gradientColor:["#f6efa6","#d88273","#bf444c"],textStyle:{fontFamily:uI.match(/^Win/)?"Microsoft YaHei":"sans-serif",fontSize:12,fontStyle:"normal",fontWeight:"normal"},blendMode:null,animation:"auto",animationDuration:1e3,animationDurationUpdate:300,animationEasing:"exponentialOut",animationEasingUpdate:"cubicOut",animationThreshold:2e3,progressiveThreshold:3e3,progressive:400,hoverLayerThreshold:3e3,useUTC:!1},cI=Bi(),dI={clearColorPalette:function(){cI(this).colorIdx=0,cI(this).colorNameMap={}},getColorFromPalette:function(t,e,i){var n=cI(e=e||this),o=n.colorIdx||0,a=n.colorNameMap=n.colorNameMap||{};if(a.hasOwnProperty(t))return a[t];var r=Di(this.get("color",!0)),s=this.get("colorLayer",!0),l=null!=i&&s?va(s,i):r;if((l=l||r)&&l.length){var u=l[o];return t&&(a[t]=u),n.colorIdx=(o+1)%l.length,u}}},fI={cartesian2d:function(t,e,i,n){var o=t.getReferringComponents("xAxis")[0],a=t.getReferringComponents("yAxis")[0];e.coordSysDims=["x","y"],i.set("x",o),i.set("y",a),xa(o)&&(n.set("x",o),e.firstCategoryDimIndex=0),xa(a)&&(n.set("y",a),e.firstCategoryDimIndex=1)},singleAxis:function(t,e,i,n){var o=t.getReferringComponents("singleAxis")[0];e.coordSysDims=["single"],i.set("single",o),xa(o)&&(n.set("single",o),e.firstCategoryDimIndex=0)},polar:function(t,e,i,n){var o=t.getReferringComponents("polar")[0],a=o.findAxisModel("radiusAxis"),r=o.findAxisModel("angleAxis");e.coordSysDims=["radius","angle"],i.set("radius",a),i.set("angle",r),xa(a)&&(n.set("radius",a),e.firstCategoryDimIndex=0),xa(r)&&(n.set("angle",r),e.firstCategoryDimIndex=1)},geo:function(t,e,i,n){e.coordSysDims=["lng","lat"]},parallel:function(t,e,i,n){var o=t.ecModel,a=o.getComponent("parallel",t.get("parallelIndex")),r=e.coordSysDims=a.dimensions.slice();d(a.parallelAxisIndex,function(t,a){var s=o.getComponent("parallelAxis",t),l=r[a];i.set(l,s),xa(s)&&null==e.firstCategoryDimIndex&&(n.set(l,s),e.firstCategoryDimIndex=a)})}},pI="original",gI="arrayRows",mI="objectRows",vI="keyedColumns",yI="unknown",xI="typedArray",_I="column",wI="row";_a.seriesDataToSource=function(t){return new _a({data:t,sourceFormat:S(t)?xI:pI,fromDataset:!1})},Yi(_a);var bI=Bi(),SI="\0_ec_inner",MI=No.extend({init:function(t,e,i,n){i=i||{},this.option=null,this._theme=new No(i),this._optionManager=n},setOption:function(t,e){k(!(SI in t),"please use chart.getOption()"),this._optionManager.setOption(t,e),this.resetOption(null)},resetOption:function(t){var e=!1,i=this._optionManager;if(!t||"recreate"===t){var n=i.mountOption("recreate"===t);this.option&&"recreate"!==t?(this.restoreData(),this.mergeOption(n)):Ea.call(this,n),e=!0}if("timeline"!==t&&"media"!==t||this.restoreData(),!t||"recreate"===t||"timeline"===t){var o=i.getTimelineOption(this);o&&(this.mergeOption(o),e=!0)}if(!t||"recreate"===t||"media"===t){var a=i.getMediaOption(this,this._api);a.length&&d(a,function(t){this.mergeOption(t,e=!0)},this)}return e},mergeOption:function(t){var e=this.option,o=this._componentsMap,r=[];Sa(this),d(t,function(t,o){null!=t&&(lI.hasClass(o)?o&&r.push(o):e[o]=null==e[o]?i(t):n(e[o],t,!0))}),lI.topologicalTravel(r,lI.getAllClassMainTypes(),function(i,n){var r=Di(t[i]),s=Pi(o.get(i),r);Ni(s),d(s,function(t,e){var n=t.option;w(n)&&(t.keyInfo.mainType=i,t.keyInfo.subType=za(i,n,t.exist))});var l=Ra(o,n);e[i]=[],o.set(i,[]),d(s,function(t,n){var r=t.exist,s=t.option;if(k(w(s)||r,"Empty component definition"),s){var u=lI.getClass(i,t.keyInfo.subType,!0);if(r&&r instanceof u)r.name=t.keyInfo.name,r.mergeOption(s,this),r.optionUpdated(s,!1);else{var h=a({dependentModels:l,componentIndex:n},t.keyInfo);a(r=new u(s,this,this,h),h),r.init(s,this,this,h),r.optionUpdated(null,!0)}}else r.mergeOption({},this),r.optionUpdated({},!1);o.get(i)[n]=r,e[i][n]=r.option},this),"series"===i&&Ba(this,o.get("series"))},this),this._seriesIndicesMap=R(this._seriesIndices=this._seriesIndices||[])},getOption:function(){var t=i(this.option);return d(t,function(e,i){if(lI.hasClass(i)){for(var n=(e=Di(e)).length-1;n>=0;n--)Ei(e[n])&&e.splice(n,1);t[i]=e}}),delete t[SI],t},getTheme:function(){return this._theme},getComponent:function(t,e){var i=this._componentsMap.get(t);if(i)return i[e||0]},queryComponents:function(t){var e=t.mainType;if(!e)return[];var i=t.index,n=t.id,o=t.name,a=this._componentsMap.get(e);if(!a||!a.length)return[];var r;if(null!=i)y(i)||(i=[i]),r=g(f(i,function(t){return a[t]}),function(t){return!!t});else if(null!=n){var s=y(n);r=g(a,function(t){return s&&l(n,t.id)>=0||!s&&t.id===n})}else if(null!=o){var u=y(o);r=g(a,function(t){return u&&l(o,t.name)>=0||!u&&t.name===o})}else r=a.slice();return Va(r,t)},findComponents:function(t){var e=t.query,i=t.mainType,n=function(t){var e=i+"Index",n=i+"Id",o=i+"Name";return!t||null==t[e]&&null==t[n]&&null==t[o]?null:{mainType:i,index:t[e],id:t[n],name:t[o]}}(e);return function(e){return t.filter?g(e,t.filter):e}(Va(n?this.queryComponents(n):this._componentsMap.get(i),t))},eachComponent:function(t,e,i){var n=this._componentsMap;"function"==typeof t?(i=e,e=t,n.each(function(t,n){d(t,function(t,o){e.call(i,n,t,o)})})):_(t)?d(n.get(t),e,i):w(t)&&d(this.findComponents(t),e,i)},getSeriesByName:function(t){return g(this._componentsMap.get("series"),function(e){return e.name===t})},getSeriesByIndex:function(t){return this._componentsMap.get("series")[t]},getSeriesByType:function(t){return g(this._componentsMap.get("series"),function(e){return e.subType===t})},getSeries:function(){return this._componentsMap.get("series").slice()},getSeriesCount:function(){return this._componentsMap.get("series").length},eachSeries:function(t,e){d(this._seriesIndices,function(i){var n=this._componentsMap.get("series")[i];t.call(e,n,i)},this)},eachRawSeries:function(t,e){d(this._componentsMap.get("series"),t,e)},eachSeriesByType:function(t,e,i){d(this._seriesIndices,function(n){var o=this._componentsMap.get("series")[n];o.subType===t&&e.call(i,o,n)},this)},eachRawSeriesByType:function(t,e,i){return d(this.getSeriesByType(t),e,i)},isSeriesFiltered:function(t){return null==this._seriesIndicesMap.get(t.componentIndex)},getCurrentSeriesIndices:function(){return(this._seriesIndices||[]).slice()},filterSeries:function(t,e){Ba(this,g(this._componentsMap.get("series"),t,e))},restoreData:function(t){var e=this._componentsMap;Ba(this,e.get("series"));var i=[];e.each(function(t,e){i.push(e)}),lI.topologicalTravel(i,lI.getAllClassMainTypes(),function(i,n){d(e.get(i),function(e){("series"!==i||!Na(e,t))&&e.restoreData()})})}});h(MI,dI);var II=["getDom","getZr","getWidth","getHeight","getDevicePixelRatio","dispatchAction","isDisposed","on","off","getDataURL","getConnectedDataURL","getModel","getOption","getViewOfComponentModel","getViewOfSeriesModel"],TI={};Fa.prototype={constructor:Fa,create:function(t,e){var i=[];d(TI,function(n,o){var a=n.create(t,e);i=i.concat(a||[])}),this._coordinateSystems=i},update:function(t,e){d(this._coordinateSystems,function(i){i.update&&i.update(t,e)})},getCoordinateSystems:function(){return this._coordinateSystems.slice()}},Fa.register=function(t,e){TI[t]=e},Fa.get=function(t){return TI[t]};var AI=d,DI=i,CI=f,LI=n,kI=/^(min|max)?(.+)$/;Wa.prototype={constructor:Wa,setOption:function(t,e){t&&d(Di(t.series),function(t){t&&t.data&&S(t.data)&&N(t.data)}),t=DI(t,!0);var i=this._optionBackup,n=Ha.call(this,t,e,!i);this._newBaseOption=n.baseOption,i?(ja(i.baseOption,n.baseOption),n.timelineOptions.length&&(i.timelineOptions=n.timelineOptions),n.mediaList.length&&(i.mediaList=n.mediaList),n.mediaDefault&&(i.mediaDefault=n.mediaDefault)):this._optionBackup=n},mountOption:function(t){var e=this._optionBackup;return this._timelineOptions=CI(e.timelineOptions,DI),this._mediaList=CI(e.mediaList,DI),this._mediaDefault=DI(e.mediaDefault),this._currentMediaIndices=[],DI(t?e.baseOption:this._newBaseOption)},getTimelineOption:function(t){var e,i=this._timelineOptions;if(i.length){var n=t.getComponent("timeline");n&&(e=DI(i[n.getCurrentIndex()],!0))}return e},getMediaOption:function(t){var e=this._api.getWidth(),i=this._api.getHeight(),n=this._mediaList,o=this._mediaDefault,a=[],r=[];if(!n.length&&!o)return r;for(var s=0,l=n.length;s=1)&&(t=1),t}var i=this._upstream,n=t&&t.skip;if(this._dirty&&i){var o=this.context;o.data=o.outputData=i.context.outputData}this.__pipeline&&(this.__pipeline.currentTask=this);var a;this._plan&&!n&&(a=this._plan(this.context));var r=e(this._modBy),s=this._modDataCount||0,l=e(t&&t.modBy),u=t&&t.modDataCount||0;r===l&&s===u||(a="reset");var h;(this._dirty||"reset"===a)&&(this._dirty=!1,h=yr(this,n)),this._modBy=l,this._modDataCount=u;var c=t&&t.step;if(this._dueEnd=i?i._outputDueEnd:this._count?this._count(this.context):1/0,this._progress){var d=this._dueIndex,f=Math.min(null!=c?this._dueIndex+c:1/0,this._dueEnd);if(!n&&(h||d=i?null:t1&&a>0?e:t}};return s}();UI.dirty=function(){this._dirty=!0,this._onDirty&&this._onDirty(this.context)},UI.unfinished=function(){return this._progress&&this._dueIndex":"\n",s="richText"===n,l={},u=0,h=this.getData(),c=h.mapDimension("defaultedTooltip",!0),f=c.length,g=this.getRawValue(t),m=y(g),v=h.getItemVisual(t,"color");w(v)&&v.colorStops&&(v=(v.colorStops[0]||{}).color),v=v||"transparent";var x=(f>1||m&&!f?function(i){function o(t,i){var o=h.getDimensionInfo(i);if(o&&!1!==o.otherDims.tooltip){var c=o.type,d="sub"+a.seriesIndex+"at"+u,p=aa({color:v,type:"subItem",renderMode:n,markerId:d}),g="string"==typeof p?p:p.content,m=(r?g+ia(o.displayName||"-")+": ":"")+ia("ordinal"===c?t+"":"time"===c?e?"":sa("yyyy/MM/dd hh:mm:ss",t):ta(t));m&&f.push(m),s&&(l[d]=v,++u)}}var r=p(i,function(t,e,i){var n=h.getDimensionInfo(i);return t|=n&&!1!==n.tooltip&&null!=n.displayName},0),f=[];c.length?d(c,function(e){o(fr(h,t,e),e)}):d(i,o);var g=r?s?"\n":"
      ":"",m=g+f.join(g||", ");return{renderMode:n,content:m,style:l}}(g):o(f?fr(h,t,c[0]):m?g[0]:g)).content,_=a.seriesIndex+"at"+u,b=aa({color:v,type:"item",renderMode:n,markerId:_});l[_]=v,++u;var S=h.getName(t),M=this.name;Oi(this)||(M=""),M=M?ia(M)+(e?": ":r):"";var I="string"==typeof b?b:b.content;return{html:e?I+M+x:M+I+(S?ia(S)+": "+x:x),markers:l}},isAnimationEnabled:function(){if(U_.node)return!1;var t=this.getShallow("animation");return t&&this.getData().count()>this.getShallow("animationThreshold")&&(t=!1),t},restoreData:function(){this.dataTask.dirty()},getColorFromPalette:function(t,e,i){var n=this.ecModel,o=dI.getColorFromPalette.call(this,t,e,i);return o||(o=n.getColorFromPalette(t,e,i)),o},coordDimToDataDim:function(t){return this.getRawData().mapDimension(t,!0)},getProgressive:function(){return this.get("progressive")},getProgressiveThreshold:function(){return this.get("progressiveThreshold")},getAxisTooltipData:null,getTooltipPosition:null,pipeTask:null,preventIncremental:null,pipelineContext:null});h(YI,ZI),h(YI,dI);var qI=function(){this.group=new tb,this.uid=Ro("viewComponent")};qI.prototype={constructor:qI,init:function(t,e){},render:function(t,e,i,n){},dispose:function(){},filterForExposedEvent:null};var KI=qI.prototype;KI.updateView=KI.updateLayout=KI.updateVisual=function(t,e,i,n){},ji(qI),$i(qI,{registerWhenExtend:!0});var $I=function(){var t=Bi();return function(e){var i=t(e),n=e.pipelineContext,o=i.large,a=i.progressiveRender,r=i.large=n.large,s=i.progressiveRender=n.progressiveRender;return!!(o^r||a^s)&&"reset"}},JI=Bi(),QI=$I();Ar.prototype={type:"chart",init:function(t,e){},render:function(t,e,i,n){},highlight:function(t,e,i,n){Cr(t.getData(),n,"emphasis")},downplay:function(t,e,i,n){Cr(t.getData(),n,"normal")},remove:function(t,e){this.group.removeAll()},dispose:function(){},incrementalPrepareRender:null,incrementalRender:null,updateTransform:null,filterForExposedEvent:null};var tT=Ar.prototype;tT.updateView=tT.updateLayout=tT.updateVisual=function(t,e,i,n){this.render(t,e,i,n)},ji(Ar),$i(Ar,{registerWhenExtend:!0}),Ar.markUpdateMethod=function(t,e){JI(t).updateMethod=e};var eT={incrementalPrepareRender:{progress:function(t,e){e.view.incrementalRender(t,e.model,e.ecModel,e.api,e.payload)}},render:{forceFirstProgress:!0,progress:function(t,e){e.view.render(e.model,e.ecModel,e.api,e.payload)}}},iT="\0__throttleOriginMethod",nT="\0__throttleRate",oT="\0__throttleType",aT={createOnAllSeries:!0,performRawSeries:!0,reset:function(t,e){var i=t.getData(),n=(t.visualColorAccessPath||"itemStyle.color").split("."),o=t.get(n)||t.getColorFromPalette(t.name,null,e.getSeriesCount());if(i.setVisual("color",o),!e.isSeriesFiltered(t)){"function"!=typeof o||o instanceof IM||i.each(function(e){i.setItemVisual(e,"color",o(t.getDataParams(e)))});return{dataEach:i.hasItemOption?function(t,e){var i=t.getItemModel(e).get(n,!0);null!=i&&t.setItemVisual(e,"color",i)}:null}}}},rT={toolbox:{brush:{title:{rect:"矩形选择",polygon:"圈选",lineX:"横向选择",lineY:"纵向选择",keep:"保持选择",clear:"清除选择"}},dataView:{title:"数据视图",lang:["数据视图","关闭","刷新"]},dataZoom:{title:{zoom:"区域缩放",back:"区域缩放还原"}},magicType:{title:{line:"切换为折线图",bar:"切换为柱状图",stack:"切换为堆叠",tiled:"切换为平铺"}},restore:{title:"还原"},saveAsImage:{title:"保存为图片",lang:["右键另存为图片"]}},series:{typeNames:{pie:"饼图",bar:"柱状图",line:"折线图",scatter:"散点图",effectScatter:"涟漪散点图",radar:"雷达图",tree:"树图",treemap:"矩形树图",boxplot:"箱型图",candlestick:"K线图",k:"K线图",heatmap:"热力图",map:"地图",parallel:"平行坐标图",lines:"线图",graph:"关系图",sankey:"桑基图",funnel:"漏斗图",gauge:"仪表盘图",pictorialBar:"象形柱图",themeRiver:"主题河流图",sunburst:"旭日图"}},aria:{general:{withTitle:"这是一个关于“{title}”的图表。",withoutTitle:"这是一个图表,"},series:{single:{prefix:"",withName:"图表类型是{seriesType},表示{seriesName}。",withoutName:"图表类型是{seriesType}。"},multiple:{prefix:"它由{seriesCount}个图表系列组成。",withName:"第{seriesId}个系列是一个表示{seriesName}的{seriesType},",withoutName:"第{seriesId}个系列是一个{seriesType},",separator:{middle:";",end:"。"}}},data:{allData:"其数据是——",partialData:"其中,前{displayCnt}项是——",withName:"{name}的数据是{value}",withoutName:"{value}",separator:{middle:",",end:""}}}},sT=function(t,e){function i(t,e){if("string"!=typeof t)return t;var i=t;return d(e,function(t,e){i=i.replace(new RegExp("\\{\\s*"+e+"\\s*\\}","g"),t)}),i}function n(t){var e=a.get(t);if(null==e){for(var i=t.split("."),n=rT.aria,o=0;o1?"series.multiple.prefix":"series.single.prefix"),{seriesCount:r}),e.eachSeries(function(t,e){if(e1?"multiple":"single")+".";a=i(a=n(s?u+"withName":u+"withoutName"),{seriesId:t.seriesIndex,seriesName:t.get("name"),seriesType:o(t.subType)});var c=t.getData();window.data=c,c.count()>l?a+=i(n("data.partialData"),{displayCnt:l}):a+=n("data.allData");for(var d=[],p=0;pi.blockIndex?i.step:null,a=n&&n.modDataCount;return{step:o,modBy:null!=a?Math.ceil(a/o):null,modDataCount:a}}},uT.getPipeline=function(t){return this._pipelineMap.get(t)},uT.updateStreamModes=function(t,e){var i=this._pipelineMap.get(t.uid),n=t.getData().count(),o=i.progressiveEnabled&&e.incrementalPrepareRender&&n>=i.threshold,a=t.get("large")&&n>=t.get("largeThreshold"),r="mod"===t.get("progressiveChunkMode")?n:null;t.pipelineContext=i.context={progressiveRender:o,modDataCount:r,large:a}},uT.restorePipelines=function(t){var e=this,i=e._pipelineMap=R();t.eachSeries(function(t){var n=t.getProgressive(),o=t.uid;i.set(o,{id:o,head:null,tail:null,threshold:t.getProgressiveThreshold(),progressiveEnabled:n&&!(t.preventIncremental&&t.preventIncremental()),blockIndex:-1,step:Math.round(n||700),count:0}),jr(e,t,t.dataTask)})},uT.prepareStageTasks=function(){var t=this._stageTaskMap,e=this.ecInstance.getModel(),i=this.api;d(this._allHandlers,function(n){var o=t.get(n.uid)||t.set(n.uid,[]);n.reset&&zr(this,n,o,e,i),n.overallReset&&Br(this,n,o,e,i)},this)},uT.prepareView=function(t,e,i,n){var o=t.renderTask,a=o.context;a.model=e,a.ecModel=i,a.api=n,o.__block=!t.incrementalPrepareRender,jr(this,e,o)},uT.performDataProcessorTasks=function(t,e){Rr(this,this._dataProcessorHandlers,t,e,{block:!0})},uT.performVisualTasks=function(t,e,i){Rr(this,this._visualHandlers,t,e,i)},uT.performSeriesTasks=function(t){var e;t.eachSeries(function(t){e|=t.dataTask.perform()}),this.unfinished|=e},uT.plan=function(){this._pipelineMap.each(function(t){var e=t.tail;do{if(e.__block){t.blockIndex=e.__idxInPipeline;break}e=e.getUpstream()}while(e)})};var hT=uT.updatePayload=function(t,e){"remain"!==e&&(t.context.payload=e)},cT=Ur(0);Er.wrapStageHandler=function(t,e){return x(t)&&(t={overallReset:t,seriesType:Yr(t)}),t.uid=Ro("stageHandler"),e&&(t.visualType=e),t};var dT,fT={},pT={};qr(fT,MI),qr(pT,Ga),fT.eachSeriesByType=fT.eachRawSeriesByType=function(t){dT=t},fT.eachComponent=function(t){"series"===t.mainType&&t.subType&&(dT=t.subType)};var gT=["#37A2DA","#32C5E9","#67E0E3","#9FE6B8","#FFDB5C","#ff9f7f","#fb7293","#E062AE","#E690D1","#e7bcf3","#9d96f5","#8378EA","#96BFFF"],mT={color:gT,colorLayer:[["#37A2DA","#ffd85c","#fd7b5f"],["#37A2DA","#67E0E3","#FFDB5C","#ff9f7f","#E062AE","#9d96f5"],["#37A2DA","#32C5E9","#9FE6B8","#FFDB5C","#ff9f7f","#fb7293","#e7bcf3","#8378EA","#96BFFF"],gT]},vT=["#dd6b66","#759aa0","#e69d87","#8dc1a9","#ea7e53","#eedd78","#73a373","#73b9bc","#7289ab","#91ca8c","#f49f42"],yT={color:vT,backgroundColor:"#333",tooltip:{axisPointer:{lineStyle:{color:"#eee"},crossStyle:{color:"#eee"}}},legend:{textStyle:{color:"#eee"}},textStyle:{color:"#eee"},title:{textStyle:{color:"#eee"}},toolbox:{iconStyle:{normal:{borderColor:"#eee"}}},dataZoom:{textStyle:{color:"#eee"}},visualMap:{textStyle:{color:"#eee"}},timeline:{lineStyle:{color:"#eee"},itemStyle:{normal:{color:vT[1]}},label:{normal:{textStyle:{color:"#eee"}}},controlStyle:{normal:{color:"#eee",borderColor:"#eee"}}},timeAxis:{axisLine:{lineStyle:{color:"#eee"}},axisTick:{lineStyle:{color:"#eee"}},axisLabel:{textStyle:{color:"#eee"}},splitLine:{lineStyle:{type:"dashed",color:"#aaa"}},splitArea:{areaStyle:{color:"#eee"}}},logAxis:{axisLine:{lineStyle:{color:"#eee"}},axisTick:{lineStyle:{color:"#eee"}},axisLabel:{textStyle:{color:"#eee"}},splitLine:{lineStyle:{type:"dashed",color:"#aaa"}},splitArea:{areaStyle:{color:"#eee"}}},valueAxis:{axisLine:{lineStyle:{color:"#eee"}},axisTick:{lineStyle:{color:"#eee"}},axisLabel:{textStyle:{color:"#eee"}},splitLine:{lineStyle:{type:"dashed",color:"#aaa"}},splitArea:{areaStyle:{color:"#eee"}}},categoryAxis:{axisLine:{lineStyle:{color:"#eee"}},axisTick:{lineStyle:{color:"#eee"}},axisLabel:{textStyle:{color:"#eee"}},splitLine:{lineStyle:{type:"dashed",color:"#aaa"}},splitArea:{areaStyle:{color:"#eee"}}},line:{symbol:"circle"},graph:{color:vT},gauge:{title:{textStyle:{color:"#eee"}}},candlestick:{itemStyle:{normal:{color:"#FD1050",color0:"#0CF49B",borderColor:"#FD1050",borderColor0:"#0CF49B"}}}};yT.categoryAxis.splitLine.show=!1,lI.extend({type:"dataset",defaultOption:{seriesLayoutBy:_I,sourceHeader:null,dimensions:null,source:null},optionUpdated:function(){wa(this)}}),qI.extend({type:"dataset"});var xT=Pn.extend({type:"ellipse",shape:{cx:0,cy:0,rx:0,ry:0},buildPath:function(t,e){var i=.5522848,n=e.cx,o=e.cy,a=e.rx,r=e.ry,s=a*i,l=r*i;t.moveTo(n-a,o),t.bezierCurveTo(n-a,o-l,n-s,o-r,n,o-r),t.bezierCurveTo(n+s,o-r,n+a,o-l,n+a,o),t.bezierCurveTo(n+a,o+l,n+s,o+r,n,o+r),t.bezierCurveTo(n-s,o+r,n-a,o+l,n-a,o),t.closePath()}}),_T=/[\s,]+/;$r.prototype.parse=function(t,e){e=e||{};var i=Kr(t);if(!i)throw new Error("Illegal svg");var n=new tb;this._root=n;var o=i.getAttribute("viewBox")||"",a=parseFloat(i.getAttribute("width")||e.width),r=parseFloat(i.getAttribute("height")||e.height);isNaN(a)&&(a=null),isNaN(r)&&(r=null),es(i,n,null,!0);for(var s=i.firstChild;s;)this._parseNode(s,n),s=s.nextSibling;var l,u;if(o){var h=P(o).split(_T);h.length>=4&&(l={x:parseFloat(h[0]||0),y:parseFloat(h[1]||0),width:parseFloat(h[2]),height:parseFloat(h[3])})}if(l&&null!=a&&null!=r&&(u=as(l,a,r),!e.ignoreViewBox)){var c=n;(n=new tb).add(c),c.scale=u.scale.slice(),c.position=u.position.slice()}return e.ignoreRootClip||null==a||null==r||n.setClipPath(new yM({shape:{x:0,y:0,width:a,height:r}})),{root:n,width:a,height:r,viewBoxRect:l,viewBoxTransform:u}},$r.prototype._parseNode=function(t,e){var i=t.nodeName.toLowerCase();"defs"===i?this._isDefine=!0:"text"===i&&(this._isText=!0);var n;if(this._isDefine){if(r=bT[i]){var o=r.call(this,t),a=t.getAttribute("id");a&&(this._defs[a]=o)}}else{var r=wT[i];r&&(n=r.call(this,t,e),e.add(n))}for(var s=t.firstChild;s;)1===s.nodeType&&this._parseNode(s,n),3===s.nodeType&&this._isText&&this._parseText(s,n),s=s.nextSibling;"defs"===i?this._isDefine=!1:"text"===i&&(this._isText=!1)},$r.prototype._parseText=function(t,e){if(1===t.nodeType){var i=t.getAttribute("dx")||0,n=t.getAttribute("dy")||0;this._textX+=parseFloat(i),this._textY+=parseFloat(n)}var o=new rM({style:{text:t.textContent,transformText:!0},position:[this._textX||0,this._textY||0]});Qr(e,o),es(t,o,this._defs);var a=o.style.fontSize;a&&a<9&&(o.style.fontSize=9,o.scale=o.scale||[1,1],o.scale[0]*=a/9,o.scale[1]*=a/9);var r=o.getBoundingRect();return this._textX+=r.width,e.add(o),o};var wT={g:function(t,e){var i=new tb;return Qr(e,i),es(t,i,this._defs),i},rect:function(t,e){var i=new yM;return Qr(e,i),es(t,i,this._defs),i.setShape({x:parseFloat(t.getAttribute("x")||0),y:parseFloat(t.getAttribute("y")||0),width:parseFloat(t.getAttribute("width")||0),height:parseFloat(t.getAttribute("height")||0)}),i},circle:function(t,e){var i=new sM;return Qr(e,i),es(t,i,this._defs),i.setShape({cx:parseFloat(t.getAttribute("cx")||0),cy:parseFloat(t.getAttribute("cy")||0),r:parseFloat(t.getAttribute("r")||0)}),i},line:function(t,e){var i=new _M;return Qr(e,i),es(t,i,this._defs),i.setShape({x1:parseFloat(t.getAttribute("x1")||0),y1:parseFloat(t.getAttribute("y1")||0),x2:parseFloat(t.getAttribute("x2")||0),y2:parseFloat(t.getAttribute("y2")||0)}),i},ellipse:function(t,e){var i=new xT;return Qr(e,i),es(t,i,this._defs),i.setShape({cx:parseFloat(t.getAttribute("cx")||0),cy:parseFloat(t.getAttribute("cy")||0),rx:parseFloat(t.getAttribute("rx")||0),ry:parseFloat(t.getAttribute("ry")||0)}),i},polygon:function(t,e){var i=t.getAttribute("points");i&&(i=ts(i));var n=new pM({shape:{points:i||[]}});return Qr(e,n),es(t,n,this._defs),n},polyline:function(t,e){var i=new Pn;Qr(e,i),es(t,i,this._defs);var n=t.getAttribute("points");return n&&(n=ts(n)),new gM({shape:{points:n||[]}})},image:function(t,e){var i=new fi;return Qr(e,i),es(t,i,this._defs),i.setStyle({image:t.getAttribute("xlink:href"),x:t.getAttribute("x"),y:t.getAttribute("y"),width:t.getAttribute("width"),height:t.getAttribute("height")}),i},text:function(t,e){var i=t.getAttribute("x")||0,n=t.getAttribute("y")||0,o=t.getAttribute("dx")||0,a=t.getAttribute("dy")||0;this._textX=parseFloat(i)+parseFloat(o),this._textY=parseFloat(n)+parseFloat(a);var r=new tb;return Qr(e,r),es(t,r,this._defs),r},tspan:function(t,e){var i=t.getAttribute("x"),n=t.getAttribute("y");null!=i&&(this._textX=parseFloat(i)),null!=n&&(this._textY=parseFloat(n));var o=t.getAttribute("dx")||0,a=t.getAttribute("dy")||0,r=new tb;return Qr(e,r),es(t,r,this._defs),this._textX+=o,this._textY+=a,r},path:function(t,e){var i=Rn(t.getAttribute("d")||"");return Qr(e,i),es(t,i,this._defs),i}},bT={lineargradient:function(t){var e=parseInt(t.getAttribute("x1")||0,10),i=parseInt(t.getAttribute("y1")||0,10),n=parseInt(t.getAttribute("x2")||10,10),o=parseInt(t.getAttribute("y2")||0,10),a=new TM(e,i,n,o);return Jr(t,a),a},radialgradient:function(t){}},ST={fill:"fill",stroke:"stroke","stroke-width":"lineWidth",opacity:"opacity","fill-opacity":"fillOpacity","stroke-opacity":"strokeOpacity","stroke-dasharray":"lineDash","stroke-dashoffset":"lineDashOffset","stroke-linecap":"lineCap","stroke-linejoin":"lineJoin","stroke-miterlimit":"miterLimit","font-family":"fontFamily","font-size":"fontSize","font-style":"fontStyle","font-weight":"fontWeight","text-align":"textAlign","alignment-baseline":"textBaseline"},MT=/url\(\s*#(.*?)\)/,IT=/(translate|scale|rotate|skewX|skewY|matrix)\(([\-\s0-9\.e,]*)\)/g,TT=/([^\s:;]+)\s*:\s*([^:;]+)/g,AT=R(),DT={registerMap:function(t,e,i){var n;return y(e)?n=e:e.svg?n=[{type:"svg",source:e.svg,specialAreas:e.specialAreas}]:(e.geoJson&&!e.features&&(i=e.specialAreas,e=e.geoJson),n=[{type:"geoJSON",source:e,specialAreas:i}]),d(n,function(t){var e=t.type;"geoJson"===e&&(e=t.type="geoJSON"),(0,CT[e])(t)}),AT.set(t,n)},retrieveMap:function(t){return AT.get(t)}},CT={geoJSON:function(t){var e=t.source;t.geoJSON=_(e)?"undefined"!=typeof JSON&&JSON.parse?JSON.parse(e):new Function("return ("+e+");")():e},svg:function(t){t.svgXML=Kr(t.source)}},LT=k,kT=d,PT=x,NT=w,OT=lI.parseClassType,ET={zrender:"4.0.6"},RT=1e3,zT=1e3,BT=3e3,VT={PROCESSOR:{FILTER:RT,STATISTIC:5e3},VISUAL:{LAYOUT:zT,GLOBAL:2e3,CHART:BT,COMPONENT:4e3,BRUSH:5e3}},GT="__flagInMainProcess",FT="__optionUpdated",WT=/^[a-zA-Z0-9_]+$/;ls.prototype.on=ss("on"),ls.prototype.off=ss("off"),ls.prototype.one=ss("one"),h(ls,fw);var HT=us.prototype;HT._onframe=function(){if(!this._disposed){var t=this._scheduler;if(this[FT]){var e=this[FT].silent;this[GT]=!0,cs(this),ZT.update.call(this),this[GT]=!1,this[FT]=!1,gs.call(this,e),ms.call(this,e)}else if(t.unfinished){var i=1,n=this._model;this._api;t.unfinished=!1;do{var o=+new Date;t.performSeriesTasks(n),t.performDataProcessorTasks(n),fs(this,n),t.performVisualTasks(n),bs(this,this._model,0,"remain"),i-=+new Date-o}while(i>0&&t.unfinished);t.unfinished||this._zr.flush()}}},HT.getDom=function(){return this._dom},HT.getZr=function(){return this._zr},HT.setOption=function(t,e,i){var n;if(NT(e)&&(i=e.lazyUpdate,n=e.silent,e=e.notMerge),this[GT]=!0,!this._model||e){var o=new Wa(this._api),a=this._theme,r=this._model=new MI(null,null,a,o);r.scheduler=this._scheduler,r.init(null,null,a,o)}this._model.setOption(t,qT),i?(this[FT]={silent:n},this[GT]=!1):(cs(this),ZT.update.call(this),this._zr.flush(),this[FT]=!1,this[GT]=!1,gs.call(this,n),ms.call(this,n))},HT.setTheme=function(){console.error("ECharts#setTheme() is DEPRECATED in ECharts 3.0")},HT.getModel=function(){return this._model},HT.getOption=function(){return this._model&&this._model.getOption()},HT.getWidth=function(){return this._zr.getWidth()},HT.getHeight=function(){return this._zr.getHeight()},HT.getDevicePixelRatio=function(){return this._zr.painter.dpr||window.devicePixelRatio||1},HT.getRenderedCanvas=function(t){if(U_.canvasSupported)return(t=t||{}).pixelRatio=t.pixelRatio||1,t.backgroundColor=t.backgroundColor||this._model.get("backgroundColor"),this._zr.painter.getRenderedCanvas(t)},HT.getSvgDataUrl=function(){if(U_.svgSupported){var t=this._zr;return d(t.storage.getDisplayList(),function(t){t.stopAnimation(!0)}),t.painter.pathToDataUrl()}},HT.getDataURL=function(t){var e=(t=t||{}).excludeComponents,i=this._model,n=[],o=this;kT(e,function(t){i.eachComponent({mainType:t},function(t){var e=o._componentsMap[t.__viewId];e.group.ignore||(n.push(e),e.group.ignore=!0)})});var a="svg"===this._zr.painter.getType()?this.getSvgDataUrl():this.getRenderedCanvas(t).toDataURL("image/"+(t&&t.type||"png"));return kT(n,function(t){t.group.ignore=!1}),a},HT.getConnectedDataURL=function(t){if(U_.canvasSupported){var e=this.group,n=Math.min,o=Math.max;if(eA[e]){var a=1/0,r=1/0,s=-1/0,l=-1/0,u=[],h=t&&t.pixelRatio||1;d(tA,function(h,c){if(h.group===e){var d=h.getRenderedCanvas(i(t)),f=h.getDom().getBoundingClientRect();a=n(f.left,a),r=n(f.top,r),s=o(f.right,s),l=o(f.bottom,l),u.push({dom:d,left:f.left,top:f.top})}});var c=(s*=h)-(a*=h),f=(l*=h)-(r*=h),p=iw();p.width=c,p.height=f;var g=Ii(p);return kT(u,function(t){var e=new fi({style:{x:t.left*h-a,y:t.top*h-r,image:t.dom}});g.add(e)}),g.refreshImmediately(),p.toDataURL("image/"+(t&&t.type||"png"))}return this.getDataURL(t)}},HT.convertToPixel=v(hs,"convertToPixel"),HT.convertFromPixel=v(hs,"convertFromPixel"),HT.containPixel=function(t,e){var i;return t=Vi(this._model,t),d(t,function(t,n){n.indexOf("Models")>=0&&d(t,function(t){var o=t.coordinateSystem;if(o&&o.containPoint)i|=!!o.containPoint(e);else if("seriesModels"===n){var a=this._chartsMap[t.__viewId];a&&a.containPoint&&(i|=a.containPoint(e,t))}},this)},this),!!i},HT.getVisual=function(t,e){var i=(t=Vi(this._model,t,{defaultMainType:"series"})).seriesModel.getData(),n=t.hasOwnProperty("dataIndexInside")?t.dataIndexInside:t.hasOwnProperty("dataIndex")?i.indexOfRawIndex(t.dataIndex):null;return null!=n?i.getItemVisual(n,e):i.getVisual(e)},HT.getViewOfComponentModel=function(t){return this._componentsMap[t.__viewId]},HT.getViewOfSeriesModel=function(t){return this._chartsMap[t.__viewId]};var ZT={prepareAndUpdate:function(t){cs(this),ZT.update.call(this,t)},update:function(t){var e=this._model,i=this._api,n=this._zr,o=this._coordSysMgr,a=this._scheduler;if(e){a.restoreData(e,t),a.performSeriesTasks(e),o.create(e,i),a.performDataProcessorTasks(e,t),fs(this,e),o.update(e,i),xs(e),a.performVisualTasks(e,t),_s(this,e,i,t);var r=e.get("backgroundColor")||"transparent";if(U_.canvasSupported)n.setBackgroundColor(r);else{var s=Gt(r);r=qt(s,"rgb"),0===s[3]&&(r="transparent")}Ss(e,i)}},updateTransform:function(t){var e=this._model,i=this,n=this._api;if(e){var o=[];e.eachComponent(function(a,r){var s=i.getViewOfComponentModel(r);if(s&&s.__alive)if(s.updateTransform){var l=s.updateTransform(r,e,n,t);l&&l.update&&o.push(s)}else o.push(s)});var a=R();e.eachSeries(function(o){var r=i._chartsMap[o.__viewId];if(r.updateTransform){var s=r.updateTransform(o,e,n,t);s&&s.update&&a.set(o.uid,1)}else a.set(o.uid,1)}),xs(e),this._scheduler.performVisualTasks(e,t,{setDirty:!0,dirtyMap:a}),bs(i,e,0,t,a),Ss(e,this._api)}},updateView:function(t){var e=this._model;e&&(Ar.markUpdateMethod(t,"updateView"),xs(e),this._scheduler.performVisualTasks(e,t,{setDirty:!0}),_s(this,this._model,this._api,t),Ss(e,this._api))},updateVisual:function(t){ZT.update.call(this,t)},updateLayout:function(t){ZT.update.call(this,t)}};HT.resize=function(t){this._zr.resize(t);var e=this._model;if(this._loadingFX&&this._loadingFX.resize(),e){var i=e.resetOption("media"),n=t&&t.silent;this[GT]=!0,i&&cs(this),ZT.update.call(this),this[GT]=!1,gs.call(this,n),ms.call(this,n)}},HT.showLoading=function(t,e){if(NT(t)&&(e=t,t=""),t=t||"default",this.hideLoading(),QT[t]){var i=QT[t](this._api,e),n=this._zr;this._loadingFX=i,n.add(i)}},HT.hideLoading=function(){this._loadingFX&&this._zr.remove(this._loadingFX),this._loadingFX=null},HT.makeActionFromEvent=function(t){var e=a({},t);return e.type=jT[t.type],e},HT.dispatchAction=function(t,e){NT(e)||(e={silent:!!e}),XT[t.type]&&this._model&&(this[GT]?this._pendingActions.push(t):(ps.call(this,t,e.silent),e.flush?this._zr.flush(!0):!1!==e.flush&&U_.browser.weChat&&this._throttledZrFlush(),gs.call(this,e.silent),ms.call(this,e.silent)))},HT.appendData=function(t){var e=t.seriesIndex;this.getModel().getSeriesByIndex(e).appendData(t),this._scheduler.unfinished=!0},HT.on=ss("on"),HT.off=ss("off"),HT.one=ss("one");var UT=["click","dblclick","mouseover","mouseout","mousemove","mousedown","mouseup","globalout","contextmenu"];HT._initEvents=function(){kT(UT,function(t){var e=function(e){var i,n=this.getModel(),o=e.target;if("globalout"===t)i={};else if(o&&null!=o.dataIndex){var r=o.dataModel||n.getSeriesByIndex(o.seriesIndex);i=r&&r.getDataParams(o.dataIndex,o.dataType,o)||{}}else o&&o.eventData&&(i=a({},o.eventData));if(i){var s=i.componentType,l=i.componentIndex;"markLine"!==s&&"markPoint"!==s&&"markArea"!==s||(s="series",l=i.seriesIndex);var u=s&&null!=l&&n.getComponent(s,l),h=u&&this["series"===u.mainType?"_chartsMap":"_componentsMap"][u.__viewId];i.event=e,i.type=t,this._ecEventProcessor.eventInfo={targetEl:o,packedEvent:i,model:u,view:h},this.trigger(t,i)}};e.zrEventfulCallAtLast=!0,this._zr.on(t,e,this)},this),kT(jT,function(t,e){this._messageCenter.on(e,function(t){this.trigger(e,t)},this)},this)},HT.isDisposed=function(){return this._disposed},HT.clear=function(){this.setOption({series:[]},!0)},HT.dispose=function(){if(!this._disposed){this._disposed=!0,Fi(this.getDom(),oA,"");var t=this._api,e=this._model;kT(this._componentsViews,function(i){i.dispose(e,t)}),kT(this._chartsViews,function(i){i.dispose(e,t)}),this._zr.dispose(),delete tA[this.id]}},h(us,fw),Ds.prototype={constructor:Ds,normalizeQuery:function(t){var e={},i={},n={};if(_(t)){var o=OT(t);e.mainType=o.main||null,e.subType=o.sub||null}else{var a=["Index","Name","Id"],r={name:1,dataIndex:1,dataType:1};d(t,function(t,o){for(var s=!1,l=0;l0&&h===o.length-u.length){var c=o.slice(0,h);"data"!==c&&(e.mainType=c,e[u.toLowerCase()]=t,s=!0)}}r.hasOwnProperty(o)&&(i[o]=t,s=!0),s||(n[o]=t)})}return{cptQuery:e,dataQuery:i,otherQuery:n}},filter:function(t,e,i){function n(t,e,i,n){return null==t[i]||e[n||i]===t[i]}var o=this.eventInfo;if(!o)return!0;var a=o.targetEl,r=o.packedEvent,s=o.model,l=o.view;if(!s||!l)return!0;var u=e.cptQuery,h=e.dataQuery;return n(u,s,"mainType")&&n(u,s,"subType")&&n(u,s,"index","componentIndex")&&n(u,s,"name")&&n(u,s,"id")&&n(h,r,"name")&&n(h,r,"dataIndex")&&n(h,r,"dataType")&&(!l.filterForExposedEvent||l.filterForExposedEvent(t,e.otherQuery,a,r))},afterTrigger:function(){this.eventInfo=null}};var XT={},jT={},YT=[],qT=[],KT=[],$T=[],JT={},QT={},tA={},eA={},iA=new Date-0,nA=new Date-0,oA="_echarts_instance_",aA=Ls;Bs(2e3,aT),Ns(BI),Os(5e3,function(t){var e=R();t.eachSeries(function(t){var i=t.get("stack");if(i){var n=e.get(i)||e.set(i,[]),o=t.getData(),a={stackResultDimension:o.getCalculationInfo("stackResultDimension"),stackedOverDimension:o.getCalculationInfo("stackedOverDimension"),stackedDimension:o.getCalculationInfo("stackedDimension"),stackedByDimension:o.getCalculationInfo("stackedByDimension"),isStackedByIndex:o.getCalculationInfo("isStackedByIndex"),data:o,seriesModel:t};if(!a.stackedDimension||!a.isStackedByIndex&&!a.stackedByDimension)return;n.length&&o.setCalculationInfo("stackedOnSeries",n[n.length-1].seriesModel),n.push(a)}}),e.each(ar)}),Gs("default",function(t,e){r(e=e||{},{text:"loading",color:"#c23531",textColor:"#000",maskColor:"rgba(255, 255, 255, 0.8)",zlevel:0});var i=new yM({style:{fill:e.maskColor},zlevel:e.zlevel,z:1e4}),n=new SM({shape:{startAngle:-lT/2,endAngle:-lT/2+.1,r:10},style:{stroke:e.color,lineCap:"round",lineWidth:5},zlevel:e.zlevel,z:10001}),o=new yM({style:{fill:"none",text:e.text,textPosition:"right",textDistance:10,textFill:e.textColor},zlevel:e.zlevel,z:10001});n.animateShape(!0).when(1e3,{endAngle:3*lT/2}).start("circularInOut"),n.animateShape(!0).when(1e3,{startAngle:3*lT/2}).delay(300).start("circularInOut");var a=new tb;return a.add(n),a.add(o),a.add(i),a.resize=function(){var e=t.getWidth()/2,a=t.getHeight()/2;n.setShape({cx:e,cy:a});var r=n.shape.r;o.setShape({x:e-r,y:a-r,width:2*r,height:2*r}),i.setShape({x:0,y:0,width:t.getWidth(),height:t.getHeight()})},a.resize(),a}),Es({type:"highlight",event:"highlight",update:"highlight"},B),Es({type:"downplay",event:"downplay",update:"downplay"},B),Ps("light",mT),Ps("dark",yT);var rA={};Xs.prototype={constructor:Xs,add:function(t){return this._add=t,this},update:function(t){return this._update=t,this},remove:function(t){return this._remove=t,this},execute:function(){var t=this._old,e=this._new,i={},n=[],o=[];for(js(t,{},n,"_oldKeyGetter",this),js(e,i,o,"_newKeyGetter",this),a=0;ax[1]&&(x[1]=y)}e&&(this._nameList[d]=e[f])}this._rawCount=this._count=l,this._extent={},el(this)},yA._initDataFromProvider=function(t,e){if(!(t>=e)){for(var i,n=this._chunkSize,o=this._rawData,a=this._storage,r=this.dimensions,s=r.length,l=this._dimensionInfos,u=this._nameList,h=this._idList,c=this._rawExtent,d=this._nameRepeatCount={},f=this._chunkCount,p=0;pM[1]&&(M[1]=S)}if(!o.pure){var I=u[v];if(m&&null==I)if(null!=m.name)u[v]=I=m.name;else if(null!=i){var T=r[i],A=a[T][y];if(A){I=A[x];var D=l[T].ordinalMeta;D&&D.categories.length&&(I=D.categories[I])}}var C=null==m?null:m.id;null==C&&null!=I&&(d[I]=d[I]||0,C=I,d[I]>0&&(C+="__ec__"+d[I]),d[I]++),null!=C&&(h[v]=C)}}!o.persistent&&o.clean&&o.clean(),this._rawCount=this._count=e,this._extent={},el(this)}},yA.count=function(){return this._count},yA.getIndices=function(){var t=this._indices;if(t){var e=t.constructor,i=this._count;if(e===Array){n=new e(i);for(o=0;o=0&&e=0&&ea&&(a=s)}return i=[o,a],this._extent[t]=i,i},yA.getApproximateExtent=function(t){return t=this.getDimension(t),this._approximateExtent[t]||this.getDataExtent(t)},yA.setApproximateExtent=function(t,e){e=this.getDimension(e),this._approximateExtent[e]=t.slice()},yA.getCalculationInfo=function(t){return this._calculationInfo[t]},yA.setCalculationInfo=function(t,e){lA(t)?a(this._calculationInfo,t):this._calculationInfo[t]=e},yA.getSum=function(t){var e=0;if(this._storage[t])for(var i=0,n=this.count();i=this._rawCount||t<0)return-1;var e=this._indices,i=e[t];if(null!=i&&it))return a;o=a-1}}return-1},yA.indicesOfNearest=function(t,e,i){var n=[];if(!this._storage[t])return n;null==i&&(i=1/0);for(var o=Number.MAX_VALUE,a=-1,r=0,s=this.count();r=0&&a<0)&&(o=u,a=l,n.length=0),n.push(r))}return n},yA.getRawIndex=nl,yA.getRawDataItem=function(t){if(this._rawData.persistent)return this._rawData.getItem(this.getRawIndex(t));for(var e=[],i=0;i=l&&w<=u||isNaN(w))&&(a[r++]=c),c++;h=!0}else if(2===n){for(var d=this._storage[s],v=this._storage[e[1]],y=t[e[1]][0],x=t[e[1]][1],f=0;f=l&&w<=u||isNaN(w))&&(b>=y&&b<=x||isNaN(b))&&(a[r++]=c),c++}h=!0}}if(!h)if(1===n)for(m=0;m=l&&w<=u||isNaN(w))&&(a[r++]=M)}else for(m=0;mt[I][1])&&(S=!1)}S&&(a[r++]=this.getRawIndex(m))}return rb[1]&&(b[1]=w)}}}return o},yA.downSample=function(t,e,i,n){for(var o=sl(this,[t]),a=o._storage,r=[],s=Math.floor(1/e),l=a[t],u=this.count(),h=this._chunkSize,c=o._rawExtent[t],d=new($s(this))(u),f=0,p=0;pu-p&&(s=u-p,r.length=s);for(var g=0;gc[1]&&(c[1]=x),d[f++]=_}return o._count=f,o._indices=d,o.getRawIndex=ol,o},yA.getItemModel=function(t){var e=this.hostModel;return new No(this.getRawDataItem(t),e,e&&e.ecModel)},yA.diff=function(t){var e=this;return new Xs(t?t.getIndices():[],this.getIndices(),function(e){return al(t,e)},function(t){return al(e,t)})},yA.getVisual=function(t){var e=this._visual;return e&&e[t]},yA.setVisual=function(t,e){if(lA(t))for(var i in t)t.hasOwnProperty(i)&&this.setVisual(i,t[i]);else this._visual=this._visual||{},this._visual[t]=e},yA.setLayout=function(t,e){if(lA(t))for(var i in t)t.hasOwnProperty(i)&&this.setLayout(i,t[i]);else this._layout[t]=e},yA.getLayout=function(t){return this._layout[t]},yA.getItemLayout=function(t){return this._itemLayouts[t]},yA.setItemLayout=function(t,e,i){this._itemLayouts[t]=i?a(this._itemLayouts[t]||{},e):e},yA.clearItemLayouts=function(){this._itemLayouts.length=0},yA.getItemVisual=function(t,e,i){var n=this._itemVisuals[t],o=n&&n[e];return null!=o||i?o:this.getVisual(e)},yA.setItemVisual=function(t,e,i){var n=this._itemVisuals[t]||{},o=this.hasItemVisual;if(this._itemVisuals[t]=n,lA(e))for(var a in e)e.hasOwnProperty(a)&&(n[a]=e[a],o[a]=!0);else n[e]=i,o[e]=!0},yA.clearAllVisual=function(){this._visual={},this._itemVisuals=[],this.hasItemVisual={}};var xA=function(t){t.seriesIndex=this.seriesIndex,t.dataIndex=this.dataIndex,t.dataType=this.dataType};yA.setItemGraphicEl=function(t,e){var i=this.hostModel;e&&(e.dataIndex=t,e.dataType=this.dataType,e.seriesIndex=i&&i.seriesIndex,"group"===e.type&&e.traverse(xA,e)),this._graphicEls[t]=e},yA.getItemGraphicEl=function(t){return this._graphicEls[t]},yA.eachItemGraphicEl=function(t,e){d(this._graphicEls,function(i,n){i&&t&&t.call(e,i,n)})},yA.cloneShallow=function(t){if(!t){var e=f(this.dimensions,this.getDimensionInfo,this);t=new vA(e,this.hostModel)}if(t._storage=this._storage,Qs(t,this),this._indices){var i=this._indices.constructor;t._indices=new i(this._indices)}else t._indices=null;return t.getRawIndex=t._indices?ol:nl,t},yA.wrapMethod=function(t,e){var i=this[t];"function"==typeof i&&(this.__wrappedMethods=this.__wrappedMethods||[],this.__wrappedMethods.push(t),this[t]=function(){var t=i.apply(this,arguments);return e.apply(this,[t].concat(C(arguments)))})},yA.TRANSFERABLE_METHODS=["cloneShallow","downSample","map"],yA.CHANGABLE_METHODS=["filterSelf","selectRange"];var _A=function(t,e){return e=e||{},hl(e.coordDimensions||[],t,{dimsDef:e.dimensionsDefine||t.dimensionsDefine,encodeDef:e.encodeDefine||t.encodeDefine,dimCount:e.dimensionsCount,generateCoord:e.generateCoord,generateCoordCount:e.generateCoordCount})};xl.prototype.parse=function(t){return t},xl.prototype.getSetting=function(t){return this._setting[t]},xl.prototype.contain=function(t){var e=this._extent;return t>=e[0]&&t<=e[1]},xl.prototype.normalize=function(t){var e=this._extent;return e[1]===e[0]?.5:(t-e[0])/(e[1]-e[0])},xl.prototype.scale=function(t){var e=this._extent;return t*(e[1]-e[0])+e[0]},xl.prototype.unionExtent=function(t){var e=this._extent;t[0]e[1]&&(e[1]=t[1])},xl.prototype.unionExtentFromData=function(t,e){this.unionExtent(t.getApproximateExtent(e))},xl.prototype.getExtent=function(){return this._extent.slice()},xl.prototype.setExtent=function(t,e){var i=this._extent;isNaN(t)||(i[0]=t),isNaN(e)||(i[1]=e)},xl.prototype.isBlank=function(){return this._isBlank},xl.prototype.setBlank=function(t){this._isBlank=t},xl.prototype.getLabel=null,ji(xl),$i(xl,{registerWhenExtend:!0}),_l.createByAxisModel=function(t){var e=t.option,i=e.data,n=i&&f(i,bl);return new _l({categories:n,needCollect:!n,deduplication:!1!==e.dedplication})};var wA=_l.prototype;wA.getOrdinal=function(t){return wl(this).get(t)},wA.parseAndCollect=function(t){var e,i=this._needCollect;if("string"!=typeof t&&!i)return t;if(i&&!this._deduplication)return e=this.categories.length,this.categories[e]=t,e;var n=wl(this);return null==(e=n.get(t))&&(i?(e=this.categories.length,this.categories[e]=t,n.set(t,e)):e=NaN),e};var bA=xl.prototype,SA=xl.extend({type:"ordinal",init:function(t,e){t&&!y(t)||(t=new _l({categories:t})),this._ordinalMeta=t,this._extent=e||[0,t.categories.length-1]},parse:function(t){return"string"==typeof t?this._ordinalMeta.getOrdinal(t):Math.round(t)},contain:function(t){return t=this.parse(t),bA.contain.call(this,t)&&null!=this._ordinalMeta.categories[t]},normalize:function(t){return bA.normalize.call(this,this.parse(t))},scale:function(t){return Math.round(bA.scale.call(this,t))},getTicks:function(){for(var t=[],e=this._extent,i=e[0];i<=e[1];)t.push(i),i++;return t},getLabel:function(t){if(!this.isBlank())return this._ordinalMeta.categories[t]},count:function(){return this._extent[1]-this._extent[0]+1},unionExtentFromData:function(t,e){this.unionExtent(t.getApproximateExtent(e))},getOrdinalMeta:function(){return this._ordinalMeta},niceTicks:B,niceExtent:B});SA.create=function(){return new SA};var MA=Go,IA=Go,TA=xl.extend({type:"interval",_interval:0,_intervalPrecision:2,setExtent:function(t,e){var i=this._extent;isNaN(t)||(i[0]=parseFloat(t)),isNaN(e)||(i[1]=parseFloat(e))},unionExtent:function(t){var e=this._extent;t[0]e[1]&&(e[1]=t[1]),TA.prototype.setExtent.call(this,e[0],e[1])},getInterval:function(){return this._interval},setInterval:function(t){this._interval=t,this._niceExtent=this._extent.slice(),this._intervalPrecision=Ml(t)},getTicks:function(){return Al(this._interval,this._extent,this._niceExtent,this._intervalPrecision)},getLabel:function(t,e){if(null==t)return"";var i=e&&e.precision;return null==i?i=Ho(t)||0:"auto"===i&&(i=this._intervalPrecision),t=IA(t,i,!0),ta(t)},niceTicks:function(t,e,i){t=t||5;var n=this._extent,o=n[1]-n[0];if(isFinite(o)){o<0&&(o=-o,n.reverse());var a=Sl(n,t,e,i);this._intervalPrecision=a.intervalPrecision,this._interval=a.interval,this._niceExtent=a.niceTickExtent}},niceExtent:function(t){var e=this._extent;if(e[0]===e[1])if(0!==e[0]){var i=e[0];t.fixMax?e[0]-=i/2:(e[1]+=i/2,e[0]-=i/2)}else e[1]=1;var n=e[1]-e[0];isFinite(n)||(e[0]=0,e[1]=1),this.niceTicks(t.splitNumber,t.minInterval,t.maxInterval);var o=this._interval;t.fixMin||(e[0]=IA(Math.floor(e[0]/o)*o)),t.fixMax||(e[1]=IA(Math.ceil(e[1]/o)*o))}});TA.create=function(){return new TA};var AA="__ec_stack_",DA="undefined"!=typeof Float32Array?Float32Array:Array,CA={seriesType:"bar",plan:$I(),reset:function(t){if(Rl(t)&&zl(t)){var e=t.getData(),i=t.coordinateSystem,n=i.getBaseAxis(),o=i.getOtherAxis(n),a=e.mapDimension(o.dim),r=e.mapDimension(n.dim),s=o.isHorizontal(),l=s?0:1,u=Ol(Pl([t]),n,t).width;return u>.5||(u=.5),{progress:function(t,e){for(var n,h=new DA(2*t.count),c=[],d=[],f=0;null!=(n=t.next());)d[l]=e.get(a,n),d[1-l]=e.get(r,n),c=i.dataToPoint(d,null,c),h[f++]=c[0],h[f++]=c[1];e.setLayout({largePoints:h,barWidth:u,valueAxisStart:Bl(0,o),valueAxisHorizontal:s})}}}}},LA=TA.prototype,kA=Math.ceil,PA=Math.floor,NA=function(t,e,i,n){for(;i>>1;t[o][1]i&&(a=i);var r=EA.length,s=NA(EA,a,0,r),l=EA[Math.min(s,r-1)],u=l[1];"year"===l[0]&&(u*=$o(o/u/t,!0));var h=this.getSetting("useUTC")?0:60*new Date(+n[0]||+n[1]).getTimezoneOffset()*1e3,c=[Math.round(kA((n[0]-h)/u)*u+h),Math.round(PA((n[1]-h)/u)*u+h)];Tl(c,n),this._stepLvl=l,this._interval=u,this._niceExtent=c},parse:function(t){return+Yo(t)}});d(["contain","normalize"],function(t){OA.prototype[t]=function(e){return LA[t].call(this,this.parse(e))}});var EA=[["hh:mm:ss",1e3],["hh:mm:ss",5e3],["hh:mm:ss",1e4],["hh:mm:ss",15e3],["hh:mm:ss",3e4],["hh:mm\nMM-dd",6e4],["hh:mm\nMM-dd",3e5],["hh:mm\nMM-dd",6e5],["hh:mm\nMM-dd",9e5],["hh:mm\nMM-dd",18e5],["hh:mm\nMM-dd",36e5],["hh:mm\nMM-dd",72e5],["hh:mm\nMM-dd",216e5],["hh:mm\nMM-dd",432e5],["MM-dd\nyyyy",864e5],["MM-dd\nyyyy",1728e5],["MM-dd\nyyyy",2592e5],["MM-dd\nyyyy",3456e5],["MM-dd\nyyyy",432e6],["MM-dd\nyyyy",5184e5],["week",6048e5],["MM-dd\nyyyy",864e6],["week",12096e5],["week",18144e5],["month",26784e5],["week",36288e5],["month",53568e5],["week",6048e6],["quarter",8208e6],["month",107136e5],["month",13392e6],["half-year",16416e6],["month",214272e5],["month",26784e6],["year",32832e6]];OA.create=function(t){return new OA({useUTC:t.ecModel.get("useUTC")})};var RA=xl.prototype,zA=TA.prototype,BA=Ho,VA=Go,GA=Math.floor,FA=Math.ceil,WA=Math.pow,HA=Math.log,ZA=xl.extend({type:"log",base:10,$constructor:function(){xl.apply(this,arguments),this._originalScale=new TA},getTicks:function(){var t=this._originalScale,e=this._extent,i=t.getExtent();return f(zA.getTicks.call(this),function(n){var o=Go(WA(this.base,n));return o=n===e[0]&&t.__fixMin?Vl(o,i[0]):o,o=n===e[1]&&t.__fixMax?Vl(o,i[1]):o},this)},getLabel:zA.getLabel,scale:function(t){return t=RA.scale.call(this,t),WA(this.base,t)},setExtent:function(t,e){var i=this.base;t=HA(t)/HA(i),e=HA(e)/HA(i),zA.setExtent.call(this,t,e)},getExtent:function(){var t=this.base,e=RA.getExtent.call(this);e[0]=WA(t,e[0]),e[1]=WA(t,e[1]);var i=this._originalScale,n=i.getExtent();return i.__fixMin&&(e[0]=Vl(e[0],n[0])),i.__fixMax&&(e[1]=Vl(e[1],n[1])),e},unionExtent:function(t){this._originalScale.unionExtent(t);var e=this.base;t[0]=HA(t[0])/HA(e),t[1]=HA(t[1])/HA(e),RA.unionExtent.call(this,t)},unionExtentFromData:function(t,e){this.unionExtent(t.getApproximateExtent(e))},niceTicks:function(t){t=t||10;var e=this._extent,i=e[1]-e[0];if(!(i===1/0||i<=0)){var n=qo(i);for(t/i*n<=.5&&(n*=10);!isNaN(n)&&Math.abs(n)<1&&Math.abs(n)>0;)n*=10;var o=[Go(FA(e[0]/n)*n),Go(GA(e[1]/n)*n)];this._interval=n,this._niceExtent=o}},niceExtent:function(t){zA.niceExtent.call(this,t);var e=this._originalScale;e.__fixMin=t.fixMin,e.__fixMax=t.fixMax}});d(["contain","normalize"],function(t){ZA.prototype[t]=function(e){return e=HA(e)/HA(this.base),RA[t].call(this,e)}}),ZA.create=function(){return new ZA};var UA={getMin:function(t){var e=this.option,i=t||null==e.rangeStart?e.min:e.rangeStart;return this.axis&&null!=i&&"dataMin"!==i&&"function"!=typeof i&&!I(i)&&(i=this.axis.scale.parse(i)),i},getMax:function(t){var e=this.option,i=t||null==e.rangeEnd?e.max:e.rangeEnd;return this.axis&&null!=i&&"dataMax"!==i&&"function"!=typeof i&&!I(i)&&(i=this.axis.scale.parse(i)),i},getNeedCrossZero:function(){var t=this.option;return null==t.rangeStart&&null==t.rangeEnd&&!t.scale},getCoordSysModel:B,setRange:function(t,e){this.option.rangeStart=t,this.option.rangeEnd=e},resetRange:function(){this.option.rangeStart=this.option.rangeEnd=null}},XA=Un({type:"triangle",shape:{cx:0,cy:0,width:0,height:0},buildPath:function(t,e){var i=e.cx,n=e.cy,o=e.width/2,a=e.height/2;t.moveTo(i,n-a),t.lineTo(i+o,n+a),t.lineTo(i-o,n+a),t.closePath()}}),jA=Un({type:"diamond",shape:{cx:0,cy:0,width:0,height:0},buildPath:function(t,e){var i=e.cx,n=e.cy,o=e.width/2,a=e.height/2;t.moveTo(i,n-a),t.lineTo(i+o,n),t.lineTo(i,n+a),t.lineTo(i-o,n),t.closePath()}}),YA=Un({type:"pin",shape:{x:0,y:0,width:0,height:0},buildPath:function(t,e){var i=e.x,n=e.y,o=e.width/5*3,a=Math.max(o,e.height),r=o/2,s=r*r/(a-r),l=n-a+r+s,u=Math.asin(s/r),h=Math.cos(u)*r,c=Math.sin(u),d=Math.cos(u),f=.6*r,p=.7*r;t.moveTo(i-h,l+s),t.arc(i,l,r,Math.PI-u,2*Math.PI+u),t.bezierCurveTo(i+h-c*f,l+s+d*f,i,n-p,i,n),t.bezierCurveTo(i,n-p,i-h+c*f,l+s+d*f,i-h,l+s),t.closePath()}}),qA=Un({type:"arrow",shape:{x:0,y:0,width:0,height:0},buildPath:function(t,e){var i=e.height,n=e.width,o=e.x,a=e.y,r=n/3*2;t.moveTo(o,a),t.lineTo(o+r,a+i),t.lineTo(o,a+i/4*3),t.lineTo(o-r,a+i),t.lineTo(o,a),t.closePath()}}),KA={line:function(t,e,i,n,o){o.x1=t,o.y1=e+n/2,o.x2=t+i,o.y2=e+n/2},rect:function(t,e,i,n,o){o.x=t,o.y=e,o.width=i,o.height=n},roundRect:function(t,e,i,n,o){o.x=t,o.y=e,o.width=i,o.height=n,o.r=Math.min(i,n)/4},square:function(t,e,i,n,o){var a=Math.min(i,n);o.x=t,o.y=e,o.width=a,o.height=a},circle:function(t,e,i,n,o){o.cx=t+i/2,o.cy=e+n/2,o.r=Math.min(i,n)/2},diamond:function(t,e,i,n,o){o.cx=t+i/2,o.cy=e+n/2,o.width=i,o.height=n},pin:function(t,e,i,n,o){o.x=t+i/2,o.y=e+n/2,o.width=i,o.height=n},arrow:function(t,e,i,n,o){o.x=t+i/2,o.y=e+n/2,o.width=i,o.height=n},triangle:function(t,e,i,n,o){o.cx=t+i/2,o.cy=e+n/2,o.width=i,o.height=n}},$A={};d({line:_M,rect:yM,roundRect:yM,square:yM,circle:sM,diamond:jA,pin:YA,arrow:qA,triangle:XA},function(t,e){$A[e]=new t});var JA=Un({type:"symbol",shape:{symbolType:"",x:0,y:0,width:0,height:0},beforeBrush:function(){var t=this.style;"pin"===this.shape.symbolType&&"inside"===t.textPosition&&(t.textPosition=["50%","40%"],t.textAlign="center",t.textVerticalAlign="middle")},buildPath:function(t,e,i){var n=e.symbolType,o=$A[n];"none"!==e.symbolType&&(o||(o=$A[n="rect"]),KA[n](e.x,e.y,e.width,e.height,o.shape),o.buildPath(t,o.shape,i))}}),QA={isDimensionStacked:pl,enableDataStack:fl,getStackedDimension:gl},tD=(Object.freeze||Object)({createList:function(t){return ml(t.getSource(),t)},getLayoutRect:ca,dataStack:QA,createScale:function(t,e){var i=e;No.isInstance(e)||h(i=new No(e),UA);var n=Hl(i);return n.setExtent(t[0],t[1]),Wl(n,i),n},mixinAxisModelCommonMethods:function(t){h(t,UA)},completeDimensions:hl,createDimensions:_A,createSymbol:Jl}),eD=1e-8;eu.prototype={constructor:eu,properties:null,getBoundingRect:function(){var t=this._rect;if(t)return t;for(var e=Number.MAX_VALUE,i=[e,e],n=[-e,-e],o=[],a=[],r=this.geometries,s=0;s0}),function(t){var e=t.properties,i=t.geometry,n=i.coordinates,o=[];"Polygon"===i.type&&o.push({type:"polygon",exterior:n[0],interiors:n.slice(1)}),"MultiPolygon"===i.type&&d(n,function(t){t[0]&&o.push({type:"polygon",exterior:t[0],interiors:t.slice(1)})});var a=new eu(e.name,o,e.cp);return a.properties=e,a})},nD=Bi(),oD=[0,1],aD=function(t,e,i){this.dim=t,this.scale=e,this._extent=i||[0,0],this.inverse=!1,this.onBand=!1};aD.prototype={constructor:aD,contain:function(t){var e=this._extent,i=Math.min(e[0],e[1]),n=Math.max(e[0],e[1]);return t>=i&&t<=n},containData:function(t){return this.contain(this.dataToCoord(t))},getExtent:function(){return this._extent.slice()},getPixelPrecision:function(t){return Zo(t||this.scale.getExtent(),this._extent)},setExtent:function(t,e){var i=this._extent;i[0]=t,i[1]=e},dataToCoord:function(t,e){var i=this._extent,n=this.scale;return t=n.normalize(t),this.onBand&&"ordinal"===n.type&&yu(i=i.slice(),n.count()),Bo(t,oD,i,e)},coordToData:function(t,e){var i=this._extent,n=this.scale;this.onBand&&"ordinal"===n.type&&yu(i=i.slice(),n.count());var o=Bo(t,i,oD,e);return this.scale.scale(o)},pointToData:function(t,e){},getTicksCoords:function(t){var e=(t=t||{}).tickModel||this.getTickModel(),i=au(this,e),n=f(i.ticks,function(t){return{coord:this.dataToCoord(t),tickValue:t}},this),o=e.get("alignWithLabel");return xu(this,n,i.tickCategoryInterval,o,t.clamp),n},getViewLabels:function(){return ou(this).labels},getLabelModel:function(){return this.model.getModel("axisLabel")},getTickModel:function(){return this.model.getModel("axisTick")},getBandWidth:function(){var t=this._extent,e=this.scale.getExtent(),i=e[1]-e[0]+(this.onBand?1:0);0===i&&(i=1);var n=Math.abs(t[1]-t[0]);return Math.abs(n)/i},isHorizontal:null,getRotate:null,calculateCategoryInterval:function(){return pu(this)}};var rD=iD,sD={};d(["map","each","filter","indexOf","inherits","reduce","filter","bind","curry","isArray","isString","isObject","isFunction","extend","defaults","clone","merge"],function(t){sD[t]=aw[t]});var lD={};d(["extendShape","extendPath","makePath","makeImage","mergePath","resizePath","createIcon","setHoverStyle","setLabelStyle","setTextStyle","setText","getFont","updateProps","initProps","getTransform","clipPointsByRect","clipRectByRect","Group","Image","Text","Circle","Sector","Ring","Polygon","Polyline","Rect","Line","BezierCurve","Arc","IncrementalDisplayable","CompoundPath","LinearGradient","RadialGradient","BoundingRect"],function(t){lD[t]=zM[t]}),YI.extend({type:"series.line",dependencies:["grid","polar"],getInitialData:function(t,e){return ml(this.getSource(),this)},defaultOption:{zlevel:0,z:2,coordinateSystem:"cartesian2d",legendHoverLink:!0,hoverAnimation:!0,clipOverflow:!0,label:{position:"top"},lineStyle:{width:2,type:"solid"},step:!1,smooth:!1,smoothMonotone:null,symbol:"emptyCircle",symbolSize:4,symbolRotate:null,showSymbol:!0,showAllSymbol:"auto",connectNulls:!1,sampling:"none",animationEasing:"linear",progressive:0,hoverLayerThreshold:1/0}});var uD=wu.prototype,hD=wu.getSymbolSize=function(t,e){var i=t.getItemVisual(e,"symbolSize");return i instanceof Array?i.slice():[+i,+i]};uD._createSymbol=function(t,e,i,n,o){this.removeAll();var a=Jl(t,-1,-1,2,2,e.getItemVisual(i,"color"),o);a.attr({z2:100,culling:!0,scale:bu(n)}),a.drift=Su,this._symbolType=t,this.add(a)},uD.stopSymbolAnimation=function(t){this.childAt(0).stopAnimation(t)},uD.getSymbolPath=function(){return this.childAt(0)},uD.getScale=function(){return this.childAt(0).scale},uD.highlight=function(){this.childAt(0).trigger("emphasis")},uD.downplay=function(){this.childAt(0).trigger("normal")},uD.setZ=function(t,e){var i=this.childAt(0);i.zlevel=t,i.z=e},uD.setDraggable=function(t){var e=this.childAt(0);e.draggable=t,e.cursor=t?"move":"pointer"},uD.updateData=function(t,e,i){this.silent=!1;var n=t.getItemVisual(e,"symbol")||"circle",o=t.hostModel,a=hD(t,e),r=n!==this._symbolType;if(r){var s=t.getItemVisual(e,"symbolKeepAspect");this._createSymbol(n,t,e,a,s)}else(l=this.childAt(0)).silent=!1,Io(l,{scale:bu(a)},o,e);if(this._updateCommon(t,e,a,i),r){var l=this.childAt(0),u=i&&i.fadeIn,h={scale:l.scale.slice()};u&&(h.style={opacity:l.style.opacity}),l.scale=[0,0],u&&(l.style.opacity=0),To(l,h,o,e)}this._seriesModel=o};var cD=["itemStyle"],dD=["emphasis","itemStyle"],fD=["label"],pD=["emphasis","label"];uD._updateCommon=function(t,e,i,n){var o=this.childAt(0),r=t.hostModel,s=t.getItemVisual(e,"color");"image"!==o.type&&o.useStyle({strokeNoScale:!0});var l=n&&n.itemStyle,u=n&&n.hoverItemStyle,h=n&&n.symbolRotate,c=n&&n.symbolOffset,d=n&&n.labelModel,f=n&&n.hoverLabelModel,p=n&&n.hoverAnimation,g=n&&n.cursorStyle;if(!n||t.hasItemOption){var m=n&&n.itemModel?n.itemModel:t.getItemModel(e);l=m.getModel(cD).getItemStyle(["color"]),u=m.getModel(dD).getItemStyle(),h=m.getShallow("symbolRotate"),c=m.getShallow("symbolOffset"),d=m.getModel(fD),f=m.getModel(pD),p=m.getShallow("hoverAnimation"),g=m.getShallow("cursor")}else u=a({},u);var v=o.style;o.attr("rotation",(h||0)*Math.PI/180||0),c&&o.attr("position",[Vo(c[0],i[0]),Vo(c[1],i[1])]),g&&o.attr("cursor",g),o.setColor(s,n&&n.symbolInnerColor),o.setStyle(l);var y=t.getItemVisual(e,"opacity");null!=y&&(v.opacity=y);var x=t.getItemVisual(e,"liftZ"),_=o.__z2Origin;null!=x?null==_&&(o.__z2Origin=o.z2,o.z2+=x):null!=_&&(o.z2=_,o.__z2Origin=null);var w=n&&n.useNameLabel;go(v,u,d,f,{labelFetcher:r,labelDataIndex:e,defaultText:function(e,i){return w?t.getName(e):_u(t,e)},isRectText:!0,autoColor:s}),o.off("mouseover").off("mouseout").off("emphasis").off("normal"),o.hoverStyle=u,fo(o),o.__symbolOriginalScale=bu(i),p&&r.isAnimationEnabled()&&o.on("mouseover",Mu).on("mouseout",Iu).on("emphasis",Tu).on("normal",Au)},uD.fadeOut=function(t,e){var i=this.childAt(0);this.silent=i.silent=!0,!(e&&e.keepLabel)&&(i.style.text=null),Io(i,{style:{opacity:0},scale:[0,0]},this._seriesModel,this.dataIndex,t)},u(wu,tb);var gD=Du.prototype;gD.updateData=function(t,e){e=Lu(e);var i=this.group,n=t.hostModel,o=this._data,a=this._symbolCtor,r=ku(t);o||i.removeAll(),t.diff(o).add(function(n){var o=t.getItemLayout(n);if(Cu(t,o,n,e)){var s=new a(t,n,r);s.attr("position",o),t.setItemGraphicEl(n,s),i.add(s)}}).update(function(s,l){var u=o.getItemGraphicEl(l),h=t.getItemLayout(s);Cu(t,h,s,e)?(u?(u.updateData(t,s,r),Io(u,{position:h},n)):(u=new a(t,s)).attr("position",h),i.add(u),t.setItemGraphicEl(s,u)):i.remove(u)}).remove(function(t){var e=o.getItemGraphicEl(t);e&&e.fadeOut(function(){i.remove(e)})}).execute(),this._data=t},gD.isPersistent=function(){return!0},gD.updateLayout=function(){var t=this._data;t&&t.eachItemGraphicEl(function(e,i){var n=t.getItemLayout(i);e.attr("position",n)})},gD.incrementalPrepareUpdate=function(t){this._seriesScope=ku(t),this._data=null,this.group.removeAll()},gD.incrementalUpdate=function(t,e,i){i=Lu(i);for(var n=t.start;n0&&Ru(i[o-1]);o--);for(;n0&&Ru(i[a-1]);a--);for(;o=0){var r=o.getItemGraphicEl(a);if(!r){var s=o.getItemLayout(a);if(!s)return;(r=new wu(o,a)).position=s,r.setZ(t.get("zlevel"),t.get("z")),r.ignore=isNaN(s[0])||isNaN(s[1]),r.__temp=!0,o.setItemGraphicEl(a,r),r.stopSymbolAnimation(!0),this.group.add(r)}r.highlight()}else Ar.prototype.highlight.call(this,t,e,i,n)},downplay:function(t,e,i,n){var o=t.getData(),a=zi(o,n);if(null!=a&&a>=0){var r=o.getItemGraphicEl(a);r&&(r.__temp?(o.setItemGraphicEl(a,null),this.group.remove(r)):r.downplay())}else Ar.prototype.downplay.call(this,t,e,i,n)},_newPolyline:function(t){var e=this._polyline;return e&&this._lineGroup.remove(e),e=new MD({shape:{points:t},silent:!0,z2:10}),this._lineGroup.add(e),this._polyline=e,e},_newPolygon:function(t,e){var i=this._polygon;return i&&this._lineGroup.remove(i),i=new ID({shape:{points:t,stackedOnPoints:e},silent:!0}),this._lineGroup.add(i),this._polygon=i,i},_updateAnimation:function(t,e,i,n,o,a){var r=this._polyline,s=this._polygon,l=t.hostModel,u=mD(this._data,t,this._stackedOnPoints,e,this._coordSys,i,this._valueOrigin,a),h=u.current,c=u.stackedOnCurrent,d=u.next,f=u.stackedOnNext;o&&(h=Yu(u.current,i,o),c=Yu(u.stackedOnCurrent,i,o),d=Yu(u.next,i,o),f=Yu(u.stackedOnNext,i,o)),r.shape.__points=u.current,r.shape.points=h,Io(r,{shape:{points:d}},l),s&&(s.setShape({points:h,stackedOnPoints:c}),Io(s,{shape:{points:d,stackedOnPoints:f}},l));for(var p=[],g=u.status,m=0;me&&(e=t[i]);return isFinite(e)?e:NaN},min:function(t){for(var e=1/0,i=0;ie[1]&&e.reverse(),e},getOtherAxis:function(){this.grid.getOtherAxis()},pointToData:function(t,e){return this.coordToData(this.toLocalCoord(t["x"===this.dim?0:1]),e)},toLocalCoord:null,toGlobalCoord:null},u(kD,aD);var PD={show:!0,zlevel:0,z:0,inverse:!1,name:"",nameLocation:"end",nameRotate:null,nameTruncate:{maxWidth:null,ellipsis:"...",placeholder:"."},nameTextStyle:{},nameGap:15,silent:!1,triggerEvent:!1,tooltip:{show:!1},axisPointer:{},axisLine:{show:!0,onZero:!0,onZeroAxisIndex:null,lineStyle:{color:"#333",width:1,type:"solid"},symbol:["none","none"],symbolSize:[10,15]},axisTick:{show:!0,inside:!1,length:5,lineStyle:{width:1}},axisLabel:{show:!0,inside:!1,rotate:0,showMinLabel:null,showMaxLabel:null,margin:8,fontSize:12},splitLine:{show:!0,lineStyle:{color:["#ccc"],width:1,type:"solid"}},splitArea:{show:!1,areaStyle:{color:["rgba(250,250,250,0.3)","rgba(200,200,200,0.3)"]}}},ND={};ND.categoryAxis=n({boundaryGap:!0,deduplication:null,splitLine:{show:!1},axisTick:{alignWithLabel:!1,interval:"auto"},axisLabel:{interval:"auto"}},PD),ND.valueAxis=n({boundaryGap:[0,0],splitNumber:5},PD),ND.timeAxis=r({scale:!0,min:"dataMin",max:"dataMax"},ND.valueAxis),ND.logAxis=r({scale:!0,logBase:10},ND.valueAxis);var OD=["value","category","time","log"],ED=function(t,e,i,a){d(OD,function(r){e.extend({type:t+"Axis."+r,mergeDefaultAndTheme:function(e,o){var a=this.layoutMode,s=a?ga(e):{};n(e,o.getTheme().get(r+"Axis")),n(e,this.getDefaultOption()),e.type=i(t,e),a&&pa(e,s,a)},optionUpdated:function(){"category"===this.option.type&&(this.__ordinalMeta=_l.createByAxisModel(this))},getCategories:function(t){var e=this.option;if("category"===e.type)return t?e.data:this.__ordinalMeta.categories},getOrdinalMeta:function(){return this.__ordinalMeta},defaultOption:o([{},ND[r+"Axis"],a],!0)})}),lI.registerSubTypeDefaulter(t+"Axis",v(i,t))},RD=lI.extend({type:"cartesian2dAxis",axis:null,init:function(){RD.superApply(this,"init",arguments),this.resetRange()},mergeOption:function(){RD.superApply(this,"mergeOption",arguments),this.resetRange()},restoreData:function(){RD.superApply(this,"restoreData",arguments),this.resetRange()},getCoordSysModel:function(){return this.ecModel.queryComponents({mainType:"grid",index:this.option.gridIndex,id:this.option.gridId})[0]}});n(RD.prototype,UA);var zD={offset:0};ED("x",RD,th,zD),ED("y",RD,th,zD),lI.extend({type:"grid",dependencies:["xAxis","yAxis"],layoutMode:"box",coordinateSystem:null,defaultOption:{show:!1,zlevel:0,z:0,left:"10%",top:60,right:"10%",bottom:60,containLabel:!1,backgroundColor:"rgba(0,0,0,0)",borderWidth:1,borderColor:"#ccc"}});var BD=ih.prototype;BD.type="grid",BD.axisPointerEnabled=!0,BD.getRect=function(){return this._rect},BD.update=function(t,e){var i=this._axesMap;this._updateScale(t,this.model),d(i.x,function(t){Wl(t.scale,t.model)}),d(i.y,function(t){Wl(t.scale,t.model)});var n={};d(i.x,function(t){nh(i,"y",t,n)}),d(i.y,function(t){nh(i,"x",t,n)}),this.resize(this.model,e)},BD.resize=function(t,e,i){function n(){d(a,function(t){var e=t.isHorizontal(),i=e?[0,o.width]:[0,o.height],n=t.inverse?1:0;t.setExtent(i[n],i[1-n]),ah(t,e?o.x:o.y)})}var o=ca(t.getBoxLayoutParams(),{width:e.getWidth(),height:e.getHeight()});this._rect=o;var a=this._axesList;n(),!i&&t.get("containLabel")&&(d(a,function(t){if(!t.model.get("axisLabel.inside")){var e=jl(t);if(e){var i=t.isHorizontal()?"height":"width",n=t.model.get("axisLabel.margin");o[i]-=e[i]+n,"top"===t.position?o.y+=e.height+n:"left"===t.position&&(o.x+=e.width+n)}}}),n())},BD.getAxis=function(t,e){var i=this._axesMap[t];if(null!=i){if(null==e)for(var n in i)if(i.hasOwnProperty(n))return i[n];return i[e]}},BD.getAxes=function(){return this._axesList.slice()},BD.getCartesian=function(t,e){if(null!=t&&null!=e){var i="x"+t+"y"+e;return this._coordsMap[i]}w(t)&&(e=t.yAxisIndex,t=t.xAxisIndex);for(var n=0,o=this._coordsList;nu[1]?-1:1,c=["start"===o?u[0]-h*l:"end"===o?u[1]+h*l:(u[0]+u[1])/2,ph(o)?t.labelOffset+r*l:0],d=e.get("nameRotate");null!=d&&(d=d*GD/180);var f;ph(o)?n=HD(t.rotation,null!=d?d:t.rotation,r):(n=uh(t,o,d||0,u),null!=(f=t.axisNameAvailableWidth)&&(f=Math.abs(f/Math.sin(n.rotation)),!isFinite(f)&&(f=null)));var p=s.getFont(),g=e.get("nameTruncate",!0)||{},m=g.ellipsis,v=T(t.nameTruncateMaxWidth,g.maxWidth,f),y=null!=m&&null!=v?tI(i,v,p,m,{minChar:2,placeholder:g.placeholder}):i,x=e.get("tooltip",!0),_=e.mainType,w={componentType:_,name:i,$vars:["name"]};w[_+"Index"]=e.componentIndex;var b=new rM({anid:"name",__fullText:i,__truncatedText:y,position:c,rotation:n.rotation,silent:hh(e),z2:1,tooltip:x&&x.show?a({content:i,formatter:function(){return i},formatterParams:w},x):null});mo(b.style,s,{text:y,textFont:p,textFill:s.getTextColor()||e.get("axisLine.lineStyle.color"),textAlign:n.textAlign,textVerticalAlign:n.textVerticalAlign}),e.get("triggerEvent")&&(b.eventData=lh(e),b.eventData.targetType="axisName",b.eventData.name=i),this._dumbGroup.add(b),b.updateTransform(),this.group.add(b),b.decomposeTransform()}}},HD=FD.innerTextLayout=function(t,e,i){var n,o,a=Xo(e-t);return jo(a)?(o=i>0?"top":"bottom",n="center"):jo(a-GD)?(o=i>0?"bottom":"top",n="center"):(o="middle",n=a>0&&a0?"right":"left":i>0?"left":"right"),{rotation:a,textAlign:n,textVerticalAlign:o}},ZD=d,UD=v,XD=Ws({type:"axis",_axisPointer:null,axisPointerClass:null,render:function(t,e,i,n){this.axisPointerClass&&Sh(t),XD.superApply(this,"render",arguments),Dh(this,t,0,i,0,!0)},updateAxisPointer:function(t,e,i,n,o){Dh(this,t,0,i,0,!1)},remove:function(t,e){var i=this._axisPointer;i&&i.remove(e),XD.superApply(this,"remove",arguments)},dispose:function(t,e){Ch(this,e),XD.superApply(this,"dispose",arguments)}}),jD=[];XD.registerAxisPointerClass=function(t,e){jD[t]=e},XD.getAxisPointerClass=function(t){return t&&jD[t]};var YD=["axisLine","axisTickLabel","axisName"],qD=["splitArea","splitLine"],KD=XD.extend({type:"cartesianAxis",axisPointerClass:"CartesianAxisPointer",render:function(t,e,i,n){this.group.removeAll();var o=this._axisGroup;if(this._axisGroup=new tb,this.group.add(this._axisGroup),t.get("show")){var a=t.getCoordSysModel(),r=Lh(a,t),s=new FD(t,r);d(YD,s.add,s),this._axisGroup.add(s.getGroup()),d(qD,function(e){t.get(e+".show")&&this["_"+e](t,a)},this),Lo(o,this._axisGroup,t),KD.superCall(this,"render",t,e,i,n)}},remove:function(){this._splitAreaColors=null},_splitLine:function(t,e){var i=t.axis;if(!i.scale.isBlank()){var n=t.getModel("splitLine"),o=n.getModel("lineStyle"),a=o.get("color");a=y(a)?a:[a];for(var s=e.coordinateSystem.getRect(),l=i.isHorizontal(),u=0,h=i.getTicksCoords({tickModel:n}),c=[],d=[],f=o.getLineStyle(),p=0;p1){var c;"string"==typeof o?c=DD[o]:"function"==typeof o&&(c=o),c&&t.setData(n.downSample(n.mapDimension(s.dim),1/h,c,CD))}}}}}("line"));var $D=YI.extend({type:"series.__base_bar__",getInitialData:function(t,e){return ml(this.getSource(),this)},getMarkerPosition:function(t){var e=this.coordinateSystem;if(e){var i=e.dataToPoint(e.clampData(t)),n=this.getData(),o=n.getLayout("offset"),a=n.getLayout("size");return i[e.getBaseAxis().isHorizontal()?0:1]+=o+a/2,i}return[NaN,NaN]},defaultOption:{zlevel:0,z:2,coordinateSystem:"cartesian2d",legendHoverLink:!0,barMinHeight:0,barMinAngle:0,large:!1,largeThreshold:400,progressive:3e3,progressiveChunkMode:"mod",itemStyle:{},emphasis:{}}});$D.extend({type:"series.bar",dependencies:["grid","polar"],brushSelector:"rect",getProgressive:function(){return!!this.get("large")&&this.get("progressive")},getProgressiveThreshold:function(){var t=this.get("progressiveThreshold"),e=this.get("largeThreshold");return e>t&&(t=e),t}});var JD=Qb([["fill","color"],["stroke","borderColor"],["lineWidth","borderWidth"],["stroke","barBorderColor"],["lineWidth","barBorderWidth"],["opacity"],["shadowBlur"],["shadowOffsetX"],["shadowOffsetY"],["shadowColor"]]),QD={getBarItemStyle:function(t){var e=JD(this,t);if(this.getBorderLineDash){var i=this.getBorderLineDash();i&&(e.lineDash=i)}return e}},tC=["itemStyle","barBorderWidth"];a(No.prototype,QD),Zs({type:"bar",render:function(t,e,i){this._updateDrawMode(t);var n=t.get("coordinateSystem");return"cartesian2d"!==n&&"polar"!==n||(this._isLargeDraw?this._renderLarge(t,e,i):this._renderNormal(t,e,i)),this.group},incrementalPrepareRender:function(t,e,i){this._clear(),this._updateDrawMode(t)},incrementalRender:function(t,e,i,n){this._incrementalRenderLarge(t,e)},_updateDrawMode:function(t){var e=t.pipelineContext.large;(null==this._isLargeDraw||e^this._isLargeDraw)&&(this._isLargeDraw=e,this._clear())},_renderNormal:function(t,e,i){var n,o=this.group,a=t.getData(),r=this._data,s=t.coordinateSystem,l=s.getBaseAxis();"cartesian2d"===s.type?n=l.isHorizontal():"polar"===s.type&&(n="angle"===l.dim);var u=t.isAnimationEnabled()?t:null;a.diff(r).add(function(e){if(a.hasValue(e)){var i=a.getItemModel(e),r=iC[s.type](a,e,i),l=eC[s.type](a,e,i,r,n,u);a.setItemGraphicEl(e,l),o.add(l),Eh(l,a,e,i,r,t,n,"polar"===s.type)}}).update(function(e,i){var l=r.getItemGraphicEl(i);if(a.hasValue(e)){var h=a.getItemModel(e),c=iC[s.type](a,e,h);l?Io(l,{shape:c},u,e):l=eC[s.type](a,e,h,c,n,u,!0),a.setItemGraphicEl(e,l),o.add(l),Eh(l,a,e,h,c,t,n,"polar"===s.type)}else o.remove(l)}).remove(function(t){var e=r.getItemGraphicEl(t);"cartesian2d"===s.type?e&&Nh(t,u,e):e&&Oh(t,u,e)}).execute(),this._data=a},_renderLarge:function(t,e,i){this._clear(),zh(t,this.group)},_incrementalRenderLarge:function(t,e){zh(e,this.group,!0)},dispose:B,remove:function(t){this._clear(t)},_clear:function(t){var e=this.group,i=this._data;t&&t.get("animation")&&i&&!this._isLargeDraw?i.eachItemGraphicEl(function(e){"sector"===e.type?Oh(e.dataIndex,t,e):Nh(e.dataIndex,t,e)}):e.removeAll(),this._data=null}});var eC={cartesian2d:function(t,e,i,n,o,r,s){var l=new yM({shape:a({},n)});if(r){var u=l.shape,h=o?"height":"width",c={};u[h]=0,c[h]=n[h],zM[s?"updateProps":"initProps"](l,{shape:c},r,e)}return l},polar:function(t,e,i,n,o,a,s){var l=n.startAngle0?1:-1,r=n.height>0?1:-1;return{x:n.x+a*o/2,y:n.y+r*o/2,width:n.width-a*o,height:n.height-r*o}},polar:function(t,e,i){var n=t.getItemLayout(e);return{cx:n.cx,cy:n.cy,r0:n.r0,r:n.r,startAngle:n.startAngle,endAngle:n.endAngle}}},nC=Pn.extend({type:"largeBar",shape:{points:[]},buildPath:function(t,e){for(var i=e.points,n=this.__startPoint,o=this.__valueIdx,a=0;a0&&"scale"!==u){var d=o.getItemLayout(0),f=Math.max(i.getWidth(),i.getHeight())/2,p=m(r.removeClipPath,r);r.setClipPath(this._createClipPath(d.cx,d.cy,f,d.startAngle,d.clockwise,p,t))}else r.removeClipPath();this._data=o}},dispose:function(){},_createClipPath:function(t,e,i,n,o,a,r){var s=new hM({shape:{cx:t,cy:e,r0:0,r:i,startAngle:n,endAngle:n,clockwise:o}});return To(s,{shape:{endAngle:n+(o?1:-1)*Math.PI*2}},r,a),s},containPoint:function(t,e){var i=e.getData().getItemLayout(0);if(i){var n=t[0]-i.cx,o=t[1]-i.cy,a=Math.sqrt(n*n+o*o);return a<=i.r&&a>=i.r0}}});var lC=function(t,e){d(e,function(e){e.update="updateView",Es(e,function(i,n){var o={};return n.eachComponent({mainType:"series",subType:t,query:i},function(t){t[e.method]&&t[e.method](i.name,i.dataIndex);var n=t.getData();n.each(function(e){var i=n.getName(e);o[i]=t.isSelected(i)||!1})}),{name:i.name,selected:o}})})},uC=function(t){return{getTargetSeries:function(e){var i={},n=R();return e.eachSeriesByType(t,function(t){t.__paletteScope=i,n.set(t.uid,t)}),n},reset:function(t,e){var i=t.getRawData(),n={},o=t.getData();o.each(function(t){var e=o.getRawIndex(t);n[e]=t}),i.each(function(e){var a=n[e],r=null!=a&&o.getItemVisual(a,"color",!0);if(r)i.setItemVisual(e,"color",r);else{var s=i.getItemModel(e).get("itemStyle.color")||t.getColorFromPalette(i.getName(e)||e+"",t.__paletteScope,i.count());i.setItemVisual(e,"color",s),null!=a&&o.setItemVisual(a,"color",s)}})}}},hC=function(t,e,i,n){var o,a,r=t.getData(),s=[],l=!1;r.each(function(i){var n,u,h,c,d=r.getItemLayout(i),f=r.getItemModel(i),p=f.getModel("label"),g=p.get("position")||f.get("emphasis.label.position"),m=f.getModel("labelLine"),v=m.get("length"),y=m.get("length2"),x=(d.startAngle+d.endAngle)/2,_=Math.cos(x),w=Math.sin(x);o=d.cx,a=d.cy;var b="inside"===g||"inner"===g;if("center"===g)n=d.cx,u=d.cy,c="center";else{var S=(b?(d.r+d.r0)/2*_:d.r*_)+o,M=(b?(d.r+d.r0)/2*w:d.r*w)+a;if(n=S+3*_,u=M+3*w,!b){var I=S+_*(v+e-d.r),T=M+w*(v+e-d.r),A=I+(_<0?-1:1)*y,D=T;n=A+(_<0?-5:5),u=D,h=[[S,M],[I,T],[A,D]]}c=b?"center":_>0?"left":"right"}var C=p.getFont(),L=p.get("rotate")?_<0?-x+Math.PI:-x:0,k=ke(t.getFormattedLabel(i,"normal")||r.getName(i),C,c,"top");l=!!L,d.label={x:n,y:u,position:g,height:k.height,len:v,len2:y,linePoints:h,textAlign:c,verticalAlign:"middle",rotation:L,inside:b},b||s.push(d.label)}),!l&&t.get("avoidLabelOverlap")&&Hh(s,o,a,e,i,n)},cC=2*Math.PI,dC=Math.PI/180,fC=function(t){return{seriesType:t,reset:function(t,e){var i=e.findComponents({mainType:"legend"});if(i&&i.length){var n=t.getData();n.filterSelf(function(t){for(var e=n.getName(t),o=0;o=0;s--){var l=2*s,u=n[l]-a/2,h=n[l+1]-r/2;if(t>=u&&e>=h&&t<=u+a&&e<=h+r)return s}return-1}}),gC=Uh.prototype;gC.isPersistent=function(){return!this._incremental},gC.updateData=function(t){this.group.removeAll();var e=new pC({rectHover:!0,cursor:"default"});e.setShape({points:t.getLayout("symbolPoints")}),this._setCommon(e,t),this.group.add(e),this._incremental=null},gC.updateLayout=function(t){if(!this._incremental){var e=t.getLayout("symbolPoints");this.group.eachChild(function(t){if(null!=t.startIndex){var i=2*(t.endIndex-t.startIndex),n=4*t.startIndex*2;e=new Float32Array(e.buffer,n,i)}t.setShape("points",e)})}},gC.incrementalPrepareUpdate=function(t){this.group.removeAll(),this._clearIncremental(),t.count()>2e6?(this._incremental||(this._incremental=new Zn({silent:!0})),this.group.add(this._incremental)):this._incremental=null},gC.incrementalUpdate=function(t,e){var i;this._incremental?(i=new pC,this._incremental.addDisplayable(i,!0)):((i=new pC({rectHover:!0,cursor:"default",startIndex:t.start,endIndex:t.end})).incremental=!0,this.group.add(i)),i.setShape({points:e.getLayout("symbolPoints")}),this._setCommon(i,e,!!this._incremental)},gC._setCommon=function(t,e,i){var n=e.hostModel,o=e.getVisual("symbolSize");t.setShape("size",o instanceof Array?o:[o,o]),t.symbolProxy=Jl(e.getVisual("symbol"),0,0,0,0),t.setColor=t.symbolProxy.setColor;var a=t.shape.size[0]<4;t.useStyle(n.getModel("itemStyle").getItemStyle(a?["color","shadowBlur","shadowColor"]:["color"]));var r=e.getVisual("color");r&&t.setColor(r),i||(t.seriesIndex=n.seriesIndex,t.on("mousemove",function(e){t.dataIndex=null;var i=t.findDataIndex(e.offsetX,e.offsetY);i>=0&&(t.dataIndex=i+(t.startIndex||0))}))},gC.remove=function(){this._clearIncremental(),this._incremental=null,this.group.removeAll()},gC._clearIncremental=function(){var t=this._incremental;t&&t.clearDisplaybles()},Zs({type:"scatter",render:function(t,e,i){var n=t.getData();this._updateSymbolDraw(n,t).updateData(n),this._finished=!0},incrementalPrepareRender:function(t,e,i){var n=t.getData();this._updateSymbolDraw(n,t).incrementalPrepareUpdate(n),this._finished=!1},incrementalRender:function(t,e,i){this._symbolDraw.incrementalUpdate(t,e.getData()),this._finished=t.end===e.getData().count()},updateTransform:function(t,e,i){var n=t.getData();if(this.group.dirty(),!this._finished||n.count()>1e4||!this._symbolDraw.isPersistent())return{update:!0};var o=AD().reset(t);o.progress&&o.progress({start:0,end:n.count()},n),this._symbolDraw.updateLayout(n)},_updateSymbolDraw:function(t,e){var i=this._symbolDraw,n=e.pipelineContext.large;return i&&n===this._isLargeDraw||(i&&i.remove(),i=this._symbolDraw=n?new Uh:new Du,this._isLargeDraw=n,this.group.removeAll()),this.group.add(i.group),i},remove:function(t,e){this._symbolDraw&&this._symbolDraw.remove(!0),this._symbolDraw=null},dispose:function(){}}),Bs(TD("scatter","circle")),zs(AD("scatter")),u(Xh,aD),jh.prototype.getIndicatorAxes=function(){return this._indicatorAxes},jh.prototype.dataToPoint=function(t,e){var i=this._indicatorAxes[e];return this.coordToPoint(i.dataToCoord(t),e)},jh.prototype.coordToPoint=function(t,e){var i=this._indicatorAxes[e].angle;return[this.cx+t*Math.cos(i),this.cy-t*Math.sin(i)]},jh.prototype.pointToData=function(t){var e=t[0]-this.cx,i=t[1]-this.cy,n=Math.sqrt(e*e+i*i);e/=n,i/=n;for(var o,a=Math.atan2(-i,e),r=1/0,s=-1,l=0;ln[0]&&isFinite(c)&&isFinite(n[0]))}else{r.getTicks().length-1>a&&(u=i(u));var d=Math.round((n[0]+n[1])/2/u)*u,f=Math.round(a/2);r.setExtent(Go(d-f*u),Go(d+(a-f)*u)),r.setInterval(u)}})},jh.dimensions=[],jh.create=function(t,e){var i=[];return t.eachComponent("radar",function(n){var o=new jh(n,t,e);i.push(o),n.coordinateSystem=o}),t.eachSeriesByType("radar",function(t){"radar"===t.get("coordinateSystem")&&(t.coordinateSystem=i[t.get("radarIndex")||0])}),i},Fa.register("radar",jh);var mC=ND.valueAxis,vC=(Fs({type:"radar",optionUpdated:function(){var t=this.get("boundaryGap"),e=this.get("splitNumber"),o=this.get("scale"),s=this.get("axisLine"),l=this.get("axisTick"),u=this.get("axisLabel"),h=this.get("name"),c=this.get("name.show"),d=this.get("name.formatter"),p=this.get("nameGap"),g=this.get("triggerEvent"),m=f(this.get("indicator")||[],function(f){null!=f.max&&f.max>0&&!f.min?f.min=0:null!=f.min&&f.min<0&&!f.max&&(f.max=0);var m=h;if(null!=f.color&&(m=r({color:f.color},h)),f=n(i(f),{boundaryGap:t,splitNumber:e,scale:o,axisLine:s,axisTick:l,axisLabel:u,name:f.text,nameLocation:"end",nameGap:p,nameTextStyle:m,triggerEvent:g},!1),c||(f.name=""),"string"==typeof d){var v=f.name;f.name=d.replace("{value}",null!=v?v:"")}else"function"==typeof d&&(f.name=d(f.name,f));var y=a(new No(f,null,this.ecModel),UA);return y.mainType="radar",y.componentIndex=this.componentIndex,y},this);this.getIndicatorModels=function(){return m}},defaultOption:{zlevel:0,z:0,center:["50%","50%"],radius:"75%",startAngle:90,name:{show:!0},boundaryGap:[0,0],splitNumber:5,nameGap:15,scale:!1,shape:"polygon",axisLine:n({lineStyle:{color:"#bbb"}},mC.axisLine),axisLabel:Yh(mC.axisLabel,!1),axisTick:Yh(mC.axisTick,!1),splitLine:Yh(mC.splitLine,!0),splitArea:Yh(mC.splitArea,!0),indicator:[]}}),["axisLine","axisTickLabel","axisName"]);Ws({type:"radar",render:function(t,e,i){this.group.removeAll(),this._buildAxes(t),this._buildSplitLineAndArea(t)},_buildAxes:function(t){var e=t.coordinateSystem;d(f(e.getIndicatorAxes(),function(t){return new FD(t.model,{position:[e.cx,e.cy],rotation:t.angle,labelDirection:-1,tickDirection:-1,nameDirection:1})}),function(t){d(vC,t.add,t),this.group.add(t.getGroup())},this)},_buildSplitLineAndArea:function(t){function e(t,e,i){var n=i%e.length;return t[n]=t[n]||[],n}var i=t.coordinateSystem,n=i.getIndicatorAxes();if(n.length){var o=t.get("shape"),a=t.getModel("splitLine"),s=t.getModel("splitArea"),l=a.getModel("lineStyle"),u=s.getModel("areaStyle"),h=a.get("show"),c=s.get("show"),p=l.get("color"),g=u.get("color");p=y(p)?p:[p],g=y(g)?g:[g];var m=[],v=[];if("circle"===o)for(var x=n[0].getTicksCoords(),_=i.cx,w=i.cy,b=0;b"+f(i,function(i,n){var o=e.get(e.mapDimension(i.dim),t);return ia(i.name+" : "+o)}).join("
      ")},defaultOption:{zlevel:0,z:2,coordinateSystem:"radar",legendHoverLink:!0,radarIndex:0,lineStyle:{width:2,type:"solid"},label:{position:"top"},symbol:"emptyCircle",symbolSize:4}});Zs({type:"radar",render:function(t,e,n){function o(t,e){var i=t.getItemVisual(e,"symbol")||"circle",n=t.getItemVisual(e,"color");if("none"!==i){var o=qh(t.getItemVisual(e,"symbolSize")),a=Jl(i,-1,-1,2,2,n);return a.attr({style:{strokeNoScale:!0},z2:100,scale:[o[0]/2,o[1]/2]}),a}}function a(e,i,n,a,r,s){n.removeAll();for(var l=0;l"+ia(n+" : "+i)},getTooltipPosition:function(t){if(null!=t){var e=this.getData().getName(t),i=this.coordinateSystem,n=i.getRegion(e);return n&&i.dataToPoint(n.center)}},setZoom:function(t){this.option.zoom=t},setCenter:function(t){this.option.center=t},defaultOption:{zlevel:0,z:2,coordinateSystem:"geo",map:"",left:"center",top:"center",aspectScale:.75,showLegendSymbol:!0,dataRangeHoverLink:!0,boundingCoords:null,center:null,zoom:1,scaleLimit:null,label:{show:!1,color:"#000"},itemStyle:{borderWidth:.5,borderColor:"#444",areaColor:"#eee"},emphasis:{label:{show:!0,color:"rgb(100,0,0)"},itemStyle:{areaColor:"rgba(255,215,0,0.8)"}}}}),aC);var EC="\0_ec_interaction_mutex";Es({type:"takeGlobalCursor",event:"globalCursorTaken",update:"update"},function(){}),h(oc,fw);var RC={axisPointer:1,tooltip:1,brush:1};xc.prototype={constructor:xc,draw:function(t,e,i,n,o){var a="geo"===t.mainType,r=t.getData&&t.getData();a&&e.eachComponent({mainType:"series",subType:"map"},function(e){r||e.getHostGeoModel()!==t||(r=e.getData())});var s=t.coordinateSystem;this._updateBackground(s);var l=this._regionsGroup,u=this.group,h=s.scale,c={position:s.position,scale:h};!l.childAt(0)||o?u.attr(c):Io(u,c,t),l.removeAll();var f=["itemStyle"],p=["emphasis","itemStyle"],g=["label"],m=["emphasis","label"],v=R();d(s.regions,function(e){var i=v.get(e.name)||v.set(e.name,new tb),n=new MM({shape:{paths:[]}});i.add(n);var o,s=(C=t.getRegionModel(e.name)||t).getModel(f),u=C.getModel(p),c=mc(s),y=mc(u),x=C.getModel(g),_=C.getModel(m);if(r){o=r.indexOfName(e.name);var w=r.getItemVisual(o,"color",!0);w&&(c.fill=w)}d(e.geometries,function(t){if("polygon"===t.type){n.shape.paths.push(new pM({shape:{points:t.exterior}}));for(var e=0;e<(t.interiors?t.interiors.length:0);e++)n.shape.paths.push(new pM({shape:{points:t.interiors[e]}}))}}),n.setStyle(c),n.style.strokeNoScale=!0,n.culling=!0;var b=x.get("show"),S=_.get("show"),M=r&&isNaN(r.get(r.mapDimension("value"),o)),I=r&&r.getItemLayout(o);if(a||M&&(b||S)||I&&I.showLabel){var T,A=a?e.name:o;(!r||o>=0)&&(T=t);var D=new rM({position:e.center.slice(),scale:[1/h[0],1/h[1]],z2:10,silent:!0});go(D.style,D.hoverStyle={},x,_,{labelFetcher:T,labelDataIndex:A,defaultText:e.name,useInsideStyle:!1},{textAlign:"center",textVerticalAlign:"middle"}),i.add(D)}if(r)r.setItemGraphicEl(o,i);else{var C=t.getRegionModel(e.name);n.eventData={componentType:"geo",componentIndex:t.componentIndex,geoIndex:t.componentIndex,name:e.name,region:C&&C.option||{}}}(i.__regions||(i.__regions=[])).push(e),fo(i,y,{hoverSilentOnTouch:!!t.get("selectedMode")}),l.add(i)}),this._updateController(t,e,i),vc(this,t,l,i,n),yc(t,l)},remove:function(){this._regionsGroup.removeAll(),this._backgroundGroup.removeAll(),this._controller.dispose(),this._mapName&&OC.removeGraphic(this._mapName,this.uid),this._mapName=null,this._controllerHost={}},_updateBackground:function(t){var e=t.map;this._mapName!==e&&d(OC.makeGraphic(e,this.uid),function(t){this._backgroundGroup.add(t)},this),this._mapName=e},_updateController:function(t,e,i){function n(){var e={type:"geoRoam",componentType:l};return e[l+"Id"]=t.id,e}var o=t.coordinateSystem,r=this._controller,s=this._controllerHost;s.zoomLimit=t.get("scaleLimit"),s.zoom=o.getZoom(),r.enable(t.get("roam")||!1);var l=t.mainType;r.off("pan").on("pan",function(t){this._mouseDownFlag=!1,fc(s,t.dx,t.dy),i.dispatchAction(a(n(),{dx:t.dx,dy:t.dy}))},this),r.off("zoom").on("zoom",function(t){if(this._mouseDownFlag=!1,pc(s,t.scale,t.originX,t.originY),i.dispatchAction(a(n(),{zoom:t.scale,originX:t.originX,originY:t.originY})),this._updateGroup){var e=this.group.scale;this._regionsGroup.traverse(function(t){"text"===t.type&&t.attr("scale",[1/e[0],1/e[1]])})}},this),r.setPointerChecker(function(e,n,a){return o.getViewRectAfterRoam().contain(n,a)&&!gc(e,i,t)})}};var zC="__seriesMapHighDown",BC="__seriesMapCallKey";Zs({type:"map",render:function(t,e,i,n){if(!n||"mapToggleSelect"!==n.type||n.from!==this.uid){var o=this.group;if(o.removeAll(),!t.getHostGeoModel()){if(n&&"geoRoam"===n.type&&"series"===n.componentType&&n.seriesId===t.id)(a=this._mapDraw)&&o.add(a.group);else if(t.needsDrawMap){var a=this._mapDraw||new xc(i,!0);o.add(a.group),a.draw(t,e,i,this,n),this._mapDraw=a}else this._mapDraw&&this._mapDraw.remove(),this._mapDraw=null;t.get("showLegendSymbol")&&e.getComponent("legend")&&this._renderSymbols(t,e,i)}}},remove:function(){this._mapDraw&&this._mapDraw.remove(),this._mapDraw=null,this.group.removeAll()},dispose:function(){this._mapDraw&&this._mapDraw.remove(),this._mapDraw=null},_renderSymbols:function(t,e,i){var n=t.originalData,o=this.group;n.each(n.mapDimension("value"),function(e,i){if(!isNaN(e)){var r=n.getItemLayout(i);if(r&&r.point){var s=r.point,l=r.offset,u=new sM({style:{fill:t.getData().getVisual("color")},shape:{cx:s[0]+9*l,cy:s[1],r:3},silent:!0,z2:8+(l?0:NM+1)});if(!l){var h=t.mainSeries.getData(),c=n.getName(i),d=h.indexOfName(c),f=n.getItemModel(i),p=f.getModel("label"),g=f.getModel("emphasis.label"),m=h.getItemGraphicEl(d),y=A(t.getFormattedLabel(d,"normal"),c),x=A(t.getFormattedLabel(d,"emphasis"),y),_=m[zC],w=Math.random();if(!_){_=m[zC]={};var b=v(_c,!0),S=v(_c,!1);m.on("mouseover",b).on("mouseout",S).on("emphasis",b).on("normal",S)}m[BC]=w,a(_,{recordVersion:w,circle:u,labelModel:p,hoverLabelModel:g,emphasisText:x,normalText:y}),wc(_,!1)}o.add(u)}}})}}),Es({type:"geoRoam",event:"geoRoam",update:"updateTransform"},function(t,e){var i=t.componentType||"series";e.eachComponent({mainType:i,query:t},function(e){var n=e.coordinateSystem;if("geo"===n.type){var o=bc(n,t,e.get("scaleLimit"));e.setCenter&&e.setCenter(o.center),e.setZoom&&e.setZoom(o.zoom),"series"===i&&d(e.seriesGroup,function(t){t.setCenter(o.center),t.setZoom(o.zoom)})}})});var VC=Q;h(Sc,Tw),Mc.prototype={constructor:Mc,type:"view",dimensions:["x","y"],setBoundingRect:function(t,e,i,n){return this._rect=new de(t,e,i,n),this._rect},getBoundingRect:function(){return this._rect},setViewRect:function(t,e,i,n){this.transformTo(t,e,i,n),this._viewRect=new de(t,e,i,n)},transformTo:function(t,e,i,n){var o=this.getBoundingRect(),a=this._rawTransformable;a.transform=o.calculateTransform(new de(t,e,i,n)),a.decomposeTransform(),this._updateTransform()},setCenter:function(t){t&&(this._center=t,this._updateCenterAndZoom())},setZoom:function(t){t=t||1;var e=this.zoomLimit;e&&(null!=e.max&&(t=Math.min(e.max,t)),null!=e.min&&(t=Math.max(e.min,t))),this._zoom=t,this._updateCenterAndZoom()},getDefaultCenter:function(){var t=this.getBoundingRect();return[t.x+t.width/2,t.y+t.height/2]},getCenter:function(){return this._center||this.getDefaultCenter()},getZoom:function(){return this._zoom||1},getRoamTransform:function(){return this._roamTransformable.getLocalTransform()},_updateCenterAndZoom:function(){var t=this._rawTransformable.getLocalTransform(),e=this._roamTransformable,i=this.getDefaultCenter(),n=this.getCenter(),o=this.getZoom();n=Q([],n,t),i=Q([],i,t),e.origin=n,e.position=[i[0]-n[0],i[1]-n[1]],e.scale=[o,o],this._updateTransform()},_updateTransform:function(){var t=this._roamTransformable,e=this._rawTransformable;e.parent=t,t.updateTransform(),e.updateTransform(),wt(this.transform||(this.transform=[]),e.transform||xt()),this._rawTransform=e.getLocalTransform(),this.invTransform=this.invTransform||[],Tt(this.invTransform,this.transform),this.decomposeTransform()},getViewRect:function(){return this._viewRect},getViewRectAfterRoam:function(){var t=this.getBoundingRect().clone();return t.applyTransform(this.transform),t},dataToPoint:function(t,e,i){var n=e?this._rawTransform:this.transform;return i=i||[],n?VC(i,t,n):G(i,t)},pointToData:function(t){var e=this.invTransform;return e?VC([],t,e):[t[0],t[1]]},convertToPixel:v(Ic,"dataToPoint"),convertFromPixel:v(Ic,"pointToData"),containPoint:function(t){return this.getViewRectAfterRoam().contain(t[0],t[1])}},h(Mc,Tw),Tc.prototype={constructor:Tc,type:"geo",dimensions:["lng","lat"],containCoord:function(t){for(var e=this.regions,i=0;ie&&(e=n.height)}this.height=e+1},getNodeById:function(t){if(this.getId()===t)return this;for(var e=0,i=this.children,n=i.length;e=0&&this.hostTree.data.setItemLayout(this.dataIndex,t,e)},getLayout:function(){return this.hostTree.data.getItemLayout(this.dataIndex)},getModel:function(t){if(!(this.dataIndex<0)){var e,i=this.hostTree,n=i.data.getItemModel(this.dataIndex),o=this.getLevelModel();return o||0!==this.children.length&&(0===this.children.length||!1!==this.isExpand)||(e=this.getLeavesModel()),n.getModel(t,(o||e||i.hostModel).getModel(t))}},getLevelModel:function(){return(this.hostTree.levelModels||[])[this.depth]},getLeavesModel:function(){return this.hostTree.leavesModel},setVisual:function(t,e){this.dataIndex>=0&&this.hostTree.data.setItemVisual(this.dataIndex,t,e)},getVisual:function(t,e){return this.hostTree.data.getItemVisual(this.dataIndex,t,e)},getRawIndex:function(){return this.hostTree.data.getRawIndex(this.dataIndex)},getId:function(){return this.hostTree.data.getId(this.dataIndex)},isAncestorOf:function(t){for(var e=t.parentNode;e;){if(e===this)return!0;e=e.parentNode}return!1},isDescendantOf:function(t){return t!==this&&t.isAncestorOf(this)}},Vc.prototype={constructor:Vc,type:"tree",eachNode:function(t,e,i){this.root.eachNode(t,e,i)},getNodeByDataIndex:function(t){var e=this.data.getRawIndex(t);return this._nodes[e]},getNodeByName:function(t){return this.root.getNodeByName(t)},update:function(){for(var t=this.data,e=this._nodes,i=0,n=e.length;ia&&(a=t.depth)});var r=t.expandAndCollapse&&t.initialTreeDepth>=0?t.initialTreeDepth:a;return o.root.eachNode("preorder",function(t){var e=t.hostTree.data.getRawDataItem(t.dataIndex);t.isExpand=e&&null!=e.collapsed?!e.collapsed:t.depth<=r}),o.data},getOrient:function(){var t=this.get("orient");return"horizontal"===t?t="LR":"vertical"===t&&(t="TB"),t},setZoom:function(t){this.option.zoom=t},setCenter:function(t){this.option.center=t},formatTooltip:function(t){for(var e=this.getData().tree,i=e.root.children[0],n=e.getNodeByDataIndex(t),o=n.getValue(),a=n.name;n&&n!==i;)a=n.parentNode.name+"."+a,n=n.parentNode;return ia(a+(isNaN(o)||null==o?"":" : "+o))},defaultOption:{zlevel:0,z:2,coordinateSystem:"view",left:"12%",top:"12%",right:"12%",bottom:"12%",layout:"orthogonal",roam:!1,nodeScaleRatio:.4,center:null,zoom:1,orient:"LR",symbol:"emptyCircle",symbolSize:7,expandAndCollapse:!0,initialTreeDepth:2,lineStyle:{color:"#ccc",width:1.5,curveness:.5},itemStyle:{color:"lightsteelblue",borderColor:"#c23531",borderWidth:1.5},label:{show:!0,color:"#555"},leaves:{label:{show:!0}},animationEasing:"linear",animationDuration:700,animationDurationUpdate:1e3}}),Zs({type:"tree",init:function(t,e){this._oldTree,this._mainGroup=new tb,this._controller=new oc(e.getZr()),this._controllerHost={target:this.group},this.group.add(this._mainGroup)},render:function(t,e,i,n){var o=t.getData(),a=t.layoutInfo,r=this._mainGroup,s=t.get("layout");"radial"===s?r.attr("position",[a.x+a.width/2,a.y+a.height/2]):r.attr("position",[a.x,a.y]),this._updateViewCoordSys(t),this._updateController(t,e,i);var l=this._data,u={expandAndCollapse:t.get("expandAndCollapse"),layout:s,orient:t.getOrient(),curvature:t.get("lineStyle.curveness"),symbolRotate:t.get("symbolRotate"),symbolOffset:t.get("symbolOffset"),hoverAnimation:t.get("hoverAnimation"),useNameLabel:!0,fadeIn:!0};o.diff(l).add(function(e){td(o,e)&&id(o,e,null,r,t,u)}).update(function(e,i){var n=l.getItemGraphicEl(i);td(o,e)?id(o,e,n,r,t,u):n&&nd(l,i,n,r,t,u)}).remove(function(e){var i=l.getItemGraphicEl(e);i&&nd(l,e,i,r,t,u)}).execute(),this._nodeScaleRatio=t.get("nodeScaleRatio"),this._updateNodeAndLinkScale(t),!0===u.expandAndCollapse&&o.eachItemGraphicEl(function(e,n){e.off("click").on("click",function(){i.dispatchAction({type:"treeExpandAndCollapse",seriesId:t.id,dataIndex:n})})}),this._data=o},_updateViewCoordSys:function(t){var e=t.getData(),i=[];e.each(function(t){var n=e.getItemLayout(t);!n||isNaN(n.x)||isNaN(n.y)||i.push([+n.x,+n.y])});var n=[],o=[];fn(i,n,o),o[0]-n[0]==0&&(o[0]+=1,n[0]-=1),o[1]-n[1]==0&&(o[1]+=1,n[1]-=1);var a=t.coordinateSystem=new Mc;a.zoomLimit=t.get("scaleLimit"),a.setBoundingRect(n[0],n[1],o[0]-n[0],o[1]-n[1]),a.setCenter(t.get("center")),a.setZoom(t.get("zoom")),this.group.attr({position:a.position,scale:a.scale}),this._viewCoordSys=a},_updateController:function(t,e,i){var n=this._controller,o=this._controllerHost,a=this.group;n.setPointerChecker(function(e,n,o){var r=a.getBoundingRect();return r.applyTransform(a.transform),r.contain(n,o)&&!gc(e,i,t)}),n.enable(t.get("roam")),o.zoomLimit=t.get("scaleLimit"),o.zoom=t.coordinateSystem.getZoom(),n.off("pan").off("zoom").on("pan",function(e){fc(o,e.dx,e.dy),i.dispatchAction({seriesId:t.id,type:"treeRoam",dx:e.dx,dy:e.dy})},this).on("zoom",function(e){pc(o,e.scale,e.originX,e.originY),i.dispatchAction({seriesId:t.id,type:"treeRoam",zoom:e.scale,originX:e.originX,originY:e.originY}),this._updateNodeAndLinkScale(t)},this)},_updateNodeAndLinkScale:function(t){var e=t.getData(),i=this._getNodeGlobalScale(t),n=[i,i];e.eachItemGraphicEl(function(t,e){t.attr("scale",n)})},_getNodeGlobalScale:function(t){var e=t.coordinateSystem;if("view"!==e.type)return 1;var i=this._nodeScaleRatio,n=e.scale,o=n&&n[0]||1;return((e.getZoom()-1)*i+1)/o},dispose:function(){this._controller&&this._controller.dispose(),this._controllerHost={}},remove:function(){this._mainGroup.removeAll(),this._data=null}}),Es({type:"treeExpandAndCollapse",event:"treeExpandAndCollapse",update:"update"},function(t,e){e.eachComponent({mainType:"series",subType:"tree",query:t},function(e){var i=t.dataIndex,n=e.getData().tree.getNodeByDataIndex(i);n.isExpand=!n.isExpand})}),Es({type:"treeRoam",event:"treeRoam",update:"none"},function(t,e){e.eachComponent({mainType:"series",subType:"tree",query:t},function(e){var i=bc(e.coordinateSystem,t);e.setCenter&&e.setCenter(i.center),e.setZoom&&e.setZoom(i.zoom)})});Bs(TD("tree","circle")),zs(function(t,e){t.eachSeriesByType("tree",function(t){sd(t,e)})}),YI.extend({type:"series.treemap",layoutMode:"box",dependencies:["grid","polar"],_viewRoot:null,defaultOption:{progressive:0,hoverLayerThreshold:1/0,left:"center",top:"middle",right:null,bottom:null,width:"80%",height:"80%",sort:!0,clipWindow:"origin",squareRatio:.5*(1+Math.sqrt(5)),leafDepth:null,drillDownIcon:"▶",zoomToNodeRatio:.1024,roam:!0,nodeClick:"zoomToNode",animation:!0,animationDurationUpdate:900,animationEasing:"quinticInOut",breadcrumb:{show:!0,height:22,left:"center",top:"bottom",emptyItemWidth:25,itemStyle:{color:"rgba(0,0,0,0.7)",borderColor:"rgba(255,255,255,0.7)",borderWidth:1,shadowColor:"rgba(150,150,150,1)",shadowBlur:3,shadowOffsetX:0,shadowOffsetY:0,textStyle:{color:"#fff"}},emphasis:{textStyle:{}}},label:{show:!0,distance:0,padding:5,position:"inside",color:"#fff",ellipsis:!0},upperLabel:{show:!1,position:[0,"50%"],height:20,color:"#fff",ellipsis:!0,verticalAlign:"middle"},itemStyle:{color:null,colorAlpha:null,colorSaturation:null,borderWidth:0,gapWidth:0,borderColor:"#fff",borderColorSaturation:null},emphasis:{upperLabel:{show:!0,position:[0,"50%"],color:"#fff",ellipsis:!0,verticalAlign:"middle"}},visualDimension:0,visualMin:null,visualMax:null,color:[],colorAlpha:null,colorSaturation:null,colorMappingBy:"index",visibleMin:10,childrenVisibleMin:null,levels:[]},getInitialData:function(t,e){var i={name:t.name,children:t.data};dd(i);var n=t.levels||[];n=t.levels=fd(n,e);var o={};return o.levels=n,Vc.createTree(i,this,o).data},optionUpdated:function(){this.resetViewRoot()},formatTooltip:function(t){var e=this.getData(),i=this.getRawValue(t),n=ta(y(i)?i[0]:i);return ia(e.getName(t)+": "+n)},getDataParams:function(t){var e=YI.prototype.getDataParams.apply(this,arguments),i=this.getData().tree.getNodeByDataIndex(t);return e.treePathInfo=cd(i,this),e},setLayoutInfo:function(t){this.layoutInfo=this.layoutInfo||{},a(this.layoutInfo,t)},mapIdToIndex:function(t){var e=this._idIndexMap;e||(e=this._idIndexMap=R(),this._idIndexMapCount=0);var i=e.get(t);return null==i&&e.set(t,i=this._idIndexMapCount++),i},getViewRoot:function(){return this._viewRoot},resetViewRoot:function(t){t?this._viewRoot=t:t=this._viewRoot;var e=this.getRawData().tree.root;t&&(t===e||e.contains(t))||(this._viewRoot=e)}});var UC=5;pd.prototype={constructor:pd,render:function(t,e,i,n){var o=t.getModel("breadcrumb"),a=this.group;if(a.removeAll(),o.get("show")&&i){var r=o.getModel("itemStyle"),s=r.getModel("textStyle"),l={pos:{left:o.get("left"),right:o.get("right"),top:o.get("top"),bottom:o.get("bottom")},box:{width:e.getWidth(),height:e.getHeight()},emptyItemWidth:o.get("emptyItemWidth"),totalWidth:0,renderList:[]};this._prepare(i,l,s),this._renderContent(t,l,r,s,n),da(a,l.pos,l.box)}},_prepare:function(t,e,i){for(var n=t;n;n=n.parentNode){var o=n.getModel().get("name"),a=i.getTextRect(o),r=Math.max(a.width+16,e.emptyItemWidth);e.totalWidth+=r+8,e.renderList.push({node:n,text:o,width:r})}},_renderContent:function(t,e,i,n,o){for(var a=0,s=e.emptyItemWidth,l=t.get("breadcrumb.height"),u=ha(e.pos,e.box),h=e.totalWidth,c=e.renderList,d=c.length-1;d>=0;d--){var f=c[d],p=f.node,g=f.width,m=f.text;h>u.width&&(h-=g-s,g=s,m=null);var y=new pM({shape:{points:gd(a,0,g,l,d===c.length-1,0===d)},style:r(i.getItemStyle(),{lineJoin:"bevel",text:m,textFill:n.getTextColor(),textFont:n.getFont()}),z:10,onclick:v(o,p)});this.group.add(y),md(y,t,p),a+=g+8}},remove:function(){this.group.removeAll()}};var XC=m,jC=tb,YC=yM,qC=d,KC=["label"],$C=["emphasis","label"],JC=["upperLabel"],QC=["emphasis","upperLabel"],tL=10,eL=1,iL=2,nL=Qb([["fill","color"],["stroke","strokeColor"],["lineWidth","strokeWidth"],["shadowBlur"],["shadowOffsetX"],["shadowOffsetY"],["shadowColor"]]),oL=function(t){var e=nL(t);return e.stroke=e.fill=e.lineWidth=null,e};Zs({type:"treemap",init:function(t,e){this._containerGroup,this._storage={nodeGroup:[],background:[],content:[]},this._oldTree,this._breadcrumb,this._controller,this._state="ready"},render:function(t,e,i,n){if(!(l(e.findComponents({mainType:"series",subType:"treemap",query:n}),t)<0)){this.seriesModel=t,this.api=i,this.ecModel=e;var o=ld(n,["treemapZoomToNode","treemapRootToNode"],t),a=n&&n.type,r=t.layoutInfo,s=!this._oldTree,u=this._storage,h="treemapRootToNode"===a&&o&&u?{rootNodeGroup:u.nodeGroup[o.node.getRawIndex()],direction:n.direction}:null,c=this._giveContainerGroup(r),d=this._doRender(c,t,h);s||a&&"treemapZoomToNode"!==a&&"treemapRootToNode"!==a?d.renderFinally():this._doAnimation(c,d,t,h),this._resetController(i),this._renderBreadcrumb(t,i,o)}},_giveContainerGroup:function(t){var e=this._containerGroup;return e||(e=this._containerGroup=new jC,this._initEvents(e),this.group.add(e)),e.attr("position",[t.x,t.y]),e},_doRender:function(t,e,i){function n(t,e,i,o,a){function r(t){return t.getId()}function s(r,s){var l=null!=r?t[r]:null,u=null!=s?e[s]:null,c=h(l,u,i,a);c&&n(l&&l.viewChildren||[],u&&u.viewChildren||[],c,o,a+1)}o?(e=t,qC(t,function(t,e){!t.isRemoved()&&s(e,e)})):new Xs(e,t,r,r).add(s).update(s).remove(v(s,null)).execute()}var o=e.getData().tree,a=this._oldTree,r={nodeGroup:[],background:[],content:[]},s={nodeGroup:[],background:[],content:[]},l=this._storage,u=[],h=v(yd,e,s,l,i,r,u);n(o.root?[o.root]:[],a&&a.root?[a.root]:[],t,o===a||!a,0);var c=function(t){var e={nodeGroup:[],background:[],content:[]};return t&&qC(t,function(t,i){var n=e[i];qC(t,function(t){t&&(n.push(t),t.__tmWillDelete=1)})}),e}(l);return this._oldTree=o,this._storage=s,{lastsForAnimation:r,willDeleteEls:c,renderFinally:function(){qC(c,function(t){qC(t,function(t){t.parent&&t.parent.remove(t)})}),qC(u,function(t){t.invisible=!0,t.dirty()})}}},_doAnimation:function(t,e,i,n){if(i.get("animation")){var o=i.get("animationDurationUpdate"),r=i.get("animationEasing"),s=vd();qC(e.willDeleteEls,function(t,e){qC(t,function(t,i){if(!t.invisible){var a,l=t.parent;if(n&&"drillDown"===n.direction)a=l===n.rootNodeGroup?{shape:{x:0,y:0,width:l.__tmNodeWidth,height:l.__tmNodeHeight},style:{opacity:0}}:{style:{opacity:0}};else{var u=0,h=0;l.__tmWillDelete||(u=l.__tmNodeWidth/2,h=l.__tmNodeHeight/2),a="nodeGroup"===e?{position:[u,h],style:{opacity:0}}:{shape:{x:u,y:h,width:0,height:0},style:{opacity:0}}}a&&s.add(t,a,o,r)}})}),qC(this._storage,function(t,i){qC(t,function(t,n){var l=e.lastsForAnimation[i][n],u={};l&&("nodeGroup"===i?l.old&&(u.position=t.position.slice(),t.attr("position",l.old)):(l.old&&(u.shape=a({},t.shape),t.setShape(l.old)),l.fadein?(t.setStyle("opacity",0),u.style={opacity:1}):1!==t.style.opacity&&(u.style={opacity:1})),s.add(t,u,o,r))})},this),this._state="animating",s.done(XC(function(){this._state="ready",e.renderFinally()},this)).start()}},_resetController:function(t){var e=this._controller;e||((e=this._controller=new oc(t.getZr())).enable(this.seriesModel.get("roam")),e.on("pan",XC(this._onPan,this)),e.on("zoom",XC(this._onZoom,this)));var i=new de(0,0,t.getWidth(),t.getHeight());e.setPointerChecker(function(t,e,n){return i.contain(e,n)})},_clearController:function(){var t=this._controller;t&&(t.dispose(),t=null)},_onPan:function(t){if("animating"!==this._state&&(Math.abs(t.dx)>3||Math.abs(t.dy)>3)){var e=this.seriesModel.getData().tree.root;if(!e)return;var i=e.getLayout();if(!i)return;this.api.dispatchAction({type:"treemapMove",from:this.uid,seriesId:this.seriesModel.id,rootRect:{x:i.x+t.dx,y:i.y+t.dy,width:i.width,height:i.height}})}},_onZoom:function(t){var e=t.originX,i=t.originY;if("animating"!==this._state){var n=this.seriesModel.getData().tree.root;if(!n)return;var o=n.getLayout();if(!o)return;var a=new de(o.x,o.y,o.width,o.height),r=this.seriesModel.layoutInfo;e-=r.x,i-=r.y;var s=xt();St(s,s,[-e,-i]),It(s,s,[t.scale,t.scale]),St(s,s,[e,i]),a.applyTransform(s),this.api.dispatchAction({type:"treemapRender",from:this.uid,seriesId:this.seriesModel.id,rootRect:{x:a.x,y:a.y,width:a.width,height:a.height}})}},_initEvents:function(t){t.on("click",function(t){if("ready"===this._state){var e=this.seriesModel.get("nodeClick",!0);if(e){var i=this.findTarget(t.offsetX,t.offsetY);if(i){var n=i.node;if(n.getLayout().isLeafRoot)this._rootToNode(i);else if("zoomToNode"===e)this._zoomToNode(i);else if("link"===e){var o=n.hostTree.data.getItemModel(n.dataIndex),a=o.get("link",!0),r=o.get("target",!0)||"blank";a&&window.open(a,r)}}}}},this)},_renderBreadcrumb:function(t,e,i){i||(i=null!=t.get("leafDepth",!0)?{node:t.getViewRoot()}:this.findTarget(e.getWidth()/2,e.getHeight()/2))||(i={node:t.getData().tree.root}),(this._breadcrumb||(this._breadcrumb=new pd(this.group))).render(t,e,i.node,XC(function(e){"animating"!==this._state&&(hd(t.getViewRoot(),e)?this._rootToNode({node:e}):this._zoomToNode({node:e}))},this))},remove:function(){this._clearController(),this._containerGroup&&this._containerGroup.removeAll(),this._storage={nodeGroup:[],background:[],content:[]},this._state="ready",this._breadcrumb&&this._breadcrumb.remove()},dispose:function(){this._clearController()},_zoomToNode:function(t){this.api.dispatchAction({type:"treemapZoomToNode",from:this.uid,seriesId:this.seriesModel.id,targetNode:t.node})},_rootToNode:function(t){this.api.dispatchAction({type:"treemapRootToNode",from:this.uid,seriesId:this.seriesModel.id,targetNode:t.node})},findTarget:function(t,e){var i;return this.seriesModel.getViewRoot().eachNode({attr:"viewChildren",order:"preorder"},function(n){var o=this._storage.background[n.getRawIndex()];if(o){var a=o.transformCoordToLocal(t,e),r=o.shape;if(!(r.x<=a[0]&&a[0]<=r.x+r.width&&r.y<=a[1]&&a[1]<=r.y+r.height))return!1;i={node:n,offsetX:a[0],offsetY:a[1]}}},this),i}});for(var aL=["treemapZoomToNode","treemapRender","treemapMove"],rL=0;rL=0&&t.call(e,i[o],o)},TL.eachEdge=function(t,e){for(var i=this.edges,n=i.length,o=0;o=0&&i[o].node1.dataIndex>=0&&i[o].node2.dataIndex>=0&&t.call(e,i[o],o)},TL.breadthFirstTraverse=function(t,e,i,n){if(Jd.isInstance(e)||(e=this._nodesMap[$d(e)]),e){for(var o="out"===i?"outEdges":"in"===i?"inEdges":"edges",a=0;a=0&&i.node2.dataIndex>=0});for(var o=0,a=n.length;o=0&&this[t][e].setItemVisual(this.dataIndex,i,n)},getVisual:function(i,n){return this[t][e].getItemVisual(this.dataIndex,i,n)},setLayout:function(i,n){this.dataIndex>=0&&this[t][e].setItemLayout(this.dataIndex,i,n)},getLayout:function(){return this[t][e].getItemLayout(this.dataIndex)},getGraphicEl:function(){return this[t][e].getItemGraphicEl(this.dataIndex)},getRawIndex:function(){return this[t][e].getRawIndex(this.dataIndex)}}};h(Jd,AL("hostGraph","data")),h(Qd,AL("hostGraph","edgeData")),IL.Node=Jd,IL.Edge=Qd,Yi(Jd),Yi(Qd);var DL=function(t,e,i,n,o){for(var a=new IL(n),r=0;r "+f)),h++)}var p,g=i.get("coordinateSystem");if("cartesian2d"===g||"polar"===g)p=ml(t,i);else{var m=Fa.get(g),v=m&&"view"!==m.type?m.dimensions||[]:[];l(v,"value")<0&&v.concat(["value"]);var y=_A(t,{coordDimensions:v});(p=new vA(y,i)).initData(t)}var x=new vA(["value"],i);return x.initData(u,s),o&&o(p,x),kc({mainData:p,struct:a,structAttr:"graph",datas:{node:p,edge:x},datasAttr:{node:"data",edge:"edgeData"}}),a.update(),a},CL=Hs({type:"series.graph",init:function(t){CL.superApply(this,"init",arguments),this.legendDataProvider=function(){return this._categoriesData},this.fillDataTextStyle(t.edges||t.links),this._updateCategoriesData()},mergeOption:function(t){CL.superApply(this,"mergeOption",arguments),this.fillDataTextStyle(t.edges||t.links),this._updateCategoriesData()},mergeDefaultAndTheme:function(t){CL.superApply(this,"mergeDefaultAndTheme",arguments),Ci(t,["edgeLabel"],["show"])},getInitialData:function(t,e){var i=t.edges||t.links||[],n=t.data||t.nodes||[],o=this;if(n&&i)return DL(n,i,this,!0,function(t,i){function n(t){return(t=this.parsePath(t))&&"label"===t[0]?r:t&&"emphasis"===t[0]&&"label"===t[1]?l:this.parentModel}t.wrapMethod("getItemModel",function(t){var e=o._categoriesModels[t.getShallow("category")];return e&&(e.parentModel=t.parentModel,t.parentModel=e),t});var a=o.getModel("edgeLabel"),r=new No({label:a.option},a.parentModel,e),s=o.getModel("emphasis.edgeLabel"),l=new No({emphasis:{label:s.option}},s.parentModel,e);i.wrapMethod("getItemModel",function(t){return t.customizeGetParent(n),t})}).data},getGraph:function(){return this.getData().graph},getEdgeData:function(){return this.getGraph().edgeData},getCategoriesData:function(){return this._categoriesData},formatTooltip:function(t,e,i){if("edge"===i){var n=this.getData(),o=this.getDataParams(t,i),a=n.graph.getEdgeByIndex(t),r=n.getName(a.node1.dataIndex),s=n.getName(a.node2.dataIndex),l=[];return null!=r&&l.push(r),null!=s&&l.push(s),l=ia(l.join(" > ")),o.value&&(l+=" : "+ia(o.value)),l}return CL.superApply(this,"formatTooltip",arguments)},_updateCategoriesData:function(){var t=f(this.option.categories||[],function(t){return null!=t.value?t:a({value:0},t)}),e=new vA(["value"],this);e.initData(t),this._categoriesData=e,this._categoriesModels=e.mapArray(function(t){return e.getItemModel(t,!0)})},setZoom:function(t){this.option.zoom=t},setCenter:function(t){this.option.center=t},isAnimationEnabled:function(){return CL.superCall(this,"isAnimationEnabled")&&!("force"===this.get("layout")&&this.get("force.layoutAnimation"))},defaultOption:{zlevel:0,z:2,coordinateSystem:"view",legendHoverLink:!0,hoverAnimation:!0,layout:null,focusNodeAdjacency:!1,circular:{rotateLabel:!1},force:{initLayout:null,repulsion:[0,50],gravity:.1,edgeLength:30,layoutAnimation:!0},left:"center",top:"center",symbol:"circle",symbolSize:10,edgeSymbol:["none","none"],edgeSymbolSize:10,edgeLabel:{position:"middle"},draggable:!1,roam:!1,center:null,zoom:1,nodeScaleRatio:.6,label:{show:!1,formatter:"{b}"},itemStyle:{},lineStyle:{color:"#aaa",width:1,curveness:0,opacity:.5},emphasis:{label:{show:!0}}}}),LL=_M.prototype,kL=bM.prototype,PL=Un({type:"ec-line",style:{stroke:"#000",fill:null},shape:{x1:0,y1:0,x2:0,y2:0,percent:1,cpx1:null,cpy1:null},buildPath:function(t,e){(tf(e)?LL:kL).buildPath(t,e)},pointAt:function(t){return tf(this.shape)?LL.pointAt.call(this,t):kL.pointAt.call(this,t)},tangentAt:function(t){var e=this.shape,i=tf(e)?[e.x2-e.x1,e.y2-e.y1]:kL.tangentAt.call(this,t);return q(i,i)}}),NL=["fromSymbol","toSymbol"],OL=rf.prototype;OL.beforeUpdate=function(){var t=this,e=t.childOfName("fromSymbol"),i=t.childOfName("toSymbol"),n=t.childOfName("label");if(e||i||!n.ignore){for(var o=1,a=this.parent;a;)a.scale&&(o/=a.scale[0]),a=a.parent;var r=t.childOfName("line");if(this.__dirty||r.__dirty){var s=r.shape.percent,l=r.pointAt(0),u=r.pointAt(s),h=U([],u,l);if(q(h,h),e&&(e.attr("position",l),c=r.tangentAt(0),e.attr("rotation",Math.PI/2-Math.atan2(c[1],c[0])),e.attr("scale",[o*s,o*s])),i){i.attr("position",u);var c=r.tangentAt(1);i.attr("rotation",-Math.PI/2-Math.atan2(c[1],c[0])),i.attr("scale",[o*s,o*s])}if(!n.ignore){n.attr("position",u);var d,f,p,g=5*o;if("end"===n.__position)d=[h[0]*g+u[0],h[1]*g+u[1]],f=h[0]>.8?"left":h[0]<-.8?"right":"center",p=h[1]>.8?"top":h[1]<-.8?"bottom":"middle";else if("middle"===n.__position){var m=s/2,v=[(c=r.tangentAt(m))[1],-c[0]],y=r.pointAt(m);v[1]>0&&(v[0]=-v[0],v[1]=-v[1]),d=[y[0]+v[0]*g,y[1]+v[1]*g],f="center",p="bottom";var x=-Math.atan2(c[1],c[0]);u[0].8?"right":h[0]<-.8?"left":"center",p=h[1]>.8?"bottom":h[1]<-.8?"top":"middle";n.attr({style:{textVerticalAlign:n.__verticalAlign||p,textAlign:n.__textAlign||f},position:d,scale:[o,o]})}}}},OL._createLine=function(t,e,i){var n=t.hostModel,o=of(t.getItemLayout(e));o.shape.percent=0,To(o,{shape:{percent:1}},n,e),this.add(o);var a=new rM({name:"label",lineLabelOriginalOpacity:1});this.add(a),d(NL,function(i){var n=nf(i,t,e);this.add(n),this[ef(i)]=t.getItemVisual(e,i)},this),this._updateCommonStl(t,e,i)},OL.updateData=function(t,e,i){var n=t.hostModel,o=this.childOfName("line"),a=t.getItemLayout(e),r={shape:{}};af(r.shape,a),Io(o,r,n,e),d(NL,function(i){var n=t.getItemVisual(e,i),o=ef(i);if(this[o]!==n){this.remove(this.childOfName(i));var a=nf(i,t,e);this.add(a)}this[o]=n},this),this._updateCommonStl(t,e,i)},OL._updateCommonStl=function(t,e,i){var n=t.hostModel,o=this.childOfName("line"),a=i&&i.lineStyle,s=i&&i.hoverLineStyle,l=i&&i.labelModel,u=i&&i.hoverLabelModel;if(!i||t.hasItemOption){var h=t.getItemModel(e);a=h.getModel("lineStyle").getLineStyle(),s=h.getModel("emphasis.lineStyle").getLineStyle(),l=h.getModel("label"),u=h.getModel("emphasis.label")}var c=t.getItemVisual(e,"color"),f=D(t.getItemVisual(e,"opacity"),a.opacity,1);o.useStyle(r({strokeNoScale:!0,fill:"none",stroke:c,opacity:f},a)),o.hoverStyle=s,d(NL,function(t){var e=this.childOfName(t);e&&(e.setColor(c),e.setStyle({opacity:f}))},this);var p,g,m=l.getShallow("show"),v=u.getShallow("show"),y=this.childOfName("label");if((m||v)&&(p=c||"#000",null==(g=n.getFormattedLabel(e,"normal",t.dataType)))){var x=n.getRawValue(e);g=null==x?t.getName(e):isFinite(x)?Go(x):x}var _=m?g:null,w=v?A(n.getFormattedLabel(e,"emphasis",t.dataType),g):null,b=y.style;null==_&&null==w||(mo(y.style,l,{text:_},{autoColor:p}),y.__textAlign=b.textAlign,y.__verticalAlign=b.textVerticalAlign,y.__position=l.get("position")||"middle"),y.hoverStyle=null!=w?{text:w,textFill:u.getTextColor(!0),fontStyle:u.getShallow("fontStyle"),fontWeight:u.getShallow("fontWeight"),fontSize:u.getShallow("fontSize"),fontFamily:u.getShallow("fontFamily")}:{text:null},y.ignore=!m&&!v,fo(this)},OL.highlight=function(){this.trigger("emphasis")},OL.downplay=function(){this.trigger("normal")},OL.updateLayout=function(t,e){this.setLinePoints(t.getItemLayout(e))},OL.setLinePoints=function(t){var e=this.childOfName("line");af(e.shape,t),e.dirty()},u(rf,tb);var EL=sf.prototype;EL.isPersistent=function(){return!0},EL.updateData=function(t){var e=this,i=e.group,n=e._lineData;e._lineData=t,n||i.removeAll();var o=hf(t);t.diff(n).add(function(i){lf(e,t,i,o)}).update(function(i,a){uf(e,n,t,a,i,o)}).remove(function(t){i.remove(n.getItemGraphicEl(t))}).execute()},EL.updateLayout=function(){var t=this._lineData;t&&t.eachItemGraphicEl(function(e,i){e.updateLayout(t,i)},this)},EL.incrementalPrepareUpdate=function(t){this._seriesScope=hf(t),this._lineData=null,this.group.removeAll()},EL.incrementalUpdate=function(t,e){for(var i=t.start;i=o/3?1:2),l=e.y-n(r)*a*(a>=o/3?1:2);r=e.angle-Math.PI/2,t.moveTo(s,l),t.lineTo(e.x+i(r)*a,e.y+n(r)*a),t.lineTo(e.x+i(e.angle)*o,e.y+n(e.angle)*o),t.lineTo(e.x-i(r)*a,e.y-n(r)*a),t.lineTo(s,l)}}),YL=2*Math.PI,qL=(Ar.extend({type:"gauge",render:function(t,e,i){this.group.removeAll();var n=t.get("axisLine.lineStyle.color"),o=Sf(t,i);this._renderMain(t,e,i,n,o)},dispose:function(){},_renderMain:function(t,e,i,n,o){for(var a=this.group,r=t.getModel("axisLine").getModel("lineStyle"),s=t.get("clockwise"),l=-t.get("startAngle")/180*Math.PI,u=-t.get("endAngle")/180*Math.PI,h=(u-l)%YL,c=l,d=r.get("width"),f=0;f=t&&(0===e?0:n[e-1][0]).4?"bottom":"middle",textAlign:A<-.4?"left":A>.4?"right":"center"},{autoColor:P}),silent:!0}))}if(g.get("show")&&T!==v){for(var N=0;N<=y;N++){var A=Math.cos(w),D=Math.sin(w),O=new _M({shape:{x1:A*c+u,y1:D*c+h,x2:A*(c-_)+u,y2:D*(c-_)+h},silent:!0,style:I});"auto"===I.stroke&&O.setStyle({stroke:n((T+N/y)/v)}),l.add(O),w+=S}w-=S}else w+=b}},_renderPointer:function(t,e,i,n,o,a,r,s){var l=this.group,u=this._data;if(t.get("pointer.show")){var h=[+t.get("min"),+t.get("max")],c=[a,r],d=t.getData(),f=d.mapDimension("value");d.diff(u).add(function(e){var i=new jL({shape:{angle:a}});To(i,{shape:{angle:Bo(d.get(f,e),h,c,!0)}},t),l.add(i),d.setItemGraphicEl(e,i)}).update(function(e,i){var n=u.getItemGraphicEl(i);Io(n,{shape:{angle:Bo(d.get(f,e),h,c,!0)}},t),l.add(n),d.setItemGraphicEl(e,n)}).remove(function(t){var e=u.getItemGraphicEl(t);l.remove(e)}).execute(),d.eachItemGraphicEl(function(t,e){var i=d.getItemModel(e),a=i.getModel("pointer");t.setShape({x:o.cx,y:o.cy,width:Vo(a.get("width"),o.r),r:Vo(a.get("length"),o.r)}),t.useStyle(i.getModel("itemStyle").getItemStyle()),"auto"===t.style.fill&&t.setStyle("fill",n(Bo(d.get(f,e),h,[0,1],!0))),fo(t,i.getModel("emphasis.itemStyle").getItemStyle())}),this._data=d}else u&&u.eachItemGraphicEl(function(t){l.remove(t)})},_renderTitle:function(t,e,i,n,o){var a=t.getData(),r=a.mapDimension("value"),s=t.getModel("title");if(s.get("show")){var l=s.get("offsetCenter"),u=o.cx+Vo(l[0],o.r),h=o.cy+Vo(l[1],o.r),c=+t.get("min"),d=+t.get("max"),f=n(Bo(t.getData().get(r,0),[c,d],[0,1],!0));this.group.add(new rM({silent:!0,style:mo({},s,{x:u,y:h,text:a.getName(0),textAlign:"center",textVerticalAlign:"middle"},{autoColor:f,forceRich:!0})}))}},_renderDetail:function(t,e,i,n,o){var a=t.getModel("detail"),r=+t.get("min"),s=+t.get("max");if(a.get("show")){var l=a.get("offsetCenter"),u=o.cx+Vo(l[0],o.r),h=o.cy+Vo(l[1],o.r),c=Vo(a.get("width"),o.r),d=Vo(a.get("height"),o.r),f=t.getData(),p=f.get(f.mapDimension("value"),0),g=n(Bo(p,[r,s],[0,1],!0));this.group.add(new rM({silent:!0,style:mo({},a,{x:u,y:h,text:Mf(p,a.get("formatter")),textWidth:isNaN(c)?null:c,textHeight:isNaN(d)?null:d,textAlign:"center",textVerticalAlign:"middle"},{autoColor:g,forceRich:!0})}))}}}),Hs({type:"series.funnel",init:function(t){qL.superApply(this,"init",arguments),this.legendDataProvider=function(){return this.getRawData()},this._defaultLabelLine(t)},getInitialData:function(t,e){return oC(this,["value"])},_defaultLabelLine:function(t){Ci(t,"labelLine",["show"]);var e=t.labelLine,i=t.emphasis.labelLine;e.show=e.show&&t.label.show,i.show=i.show&&t.emphasis.label.show},getDataParams:function(t){var e=this.getData(),i=qL.superCall(this,"getDataParams",t),n=e.mapDimension("value"),o=e.getSum(n);return i.percent=o?+(e.get(n,t)/o*100).toFixed(2):0,i.$vars.push("percent"),i},defaultOption:{zlevel:0,z:2,legendHoverLink:!0,left:80,top:60,right:80,bottom:60,minSize:"0%",maxSize:"100%",sort:"descending",gap:0,funnelAlign:"center",label:{show:!0,position:"outer"},labelLine:{show:!0,length:20,lineStyle:{width:1,type:"solid"}},itemStyle:{borderColor:"#fff",borderWidth:1},emphasis:{label:{show:!0}}}})),KL=If.prototype,$L=["itemStyle","opacity"];KL.updateData=function(t,e,i){var n=this.childAt(0),o=t.hostModel,a=t.getItemModel(e),s=t.getItemLayout(e),l=t.getItemModel(e).get($L);l=null==l?1:l,n.useStyle({}),i?(n.setShape({points:s.points}),n.setStyle({opacity:0}),To(n,{style:{opacity:l}},o,e)):Io(n,{style:{opacity:l},shape:{points:s.points}},o,e);var u=a.getModel("itemStyle"),h=t.getItemVisual(e,"color");n.setStyle(r({lineJoin:"round",fill:h},u.getItemStyle(["opacity"]))),n.hoverStyle=u.getModel("emphasis").getItemStyle(),this._updateLabel(t,e),fo(this)},KL._updateLabel=function(t,e){var i=this.childAt(1),n=this.childAt(2),o=t.hostModel,a=t.getItemModel(e),r=t.getItemLayout(e).label,s=t.getItemVisual(e,"color");Io(i,{shape:{points:r.linePoints||r.linePoints}},o,e),Io(n,{style:{x:r.x,y:r.y}},o,e),n.attr({rotation:r.rotation,origin:[r.x,r.y],z2:10});var l=a.getModel("label"),u=a.getModel("emphasis.label"),h=a.getModel("labelLine"),c=a.getModel("emphasis.labelLine"),s=t.getItemVisual(e,"color");go(n.style,n.hoverStyle={},l,u,{labelFetcher:t.hostModel,labelDataIndex:e,defaultText:t.getName(e),autoColor:s,useInsideStyle:!!r.inside},{textAlign:r.textAlign,textVerticalAlign:r.verticalAlign}),n.ignore=n.normalIgnore=!l.get("show"),n.hoverIgnore=!u.get("show"),i.ignore=i.normalIgnore=!h.get("show"),i.hoverIgnore=!c.get("show"),i.setStyle({stroke:s}),i.setStyle(h.getModel("lineStyle").getLineStyle()),i.hoverStyle=c.getModel("lineStyle").getLineStyle()},u(If,tb);Ar.extend({type:"funnel",render:function(t,e,i){var n=t.getData(),o=this._data,a=this.group;n.diff(o).add(function(t){var e=new If(n,t);n.setItemGraphicEl(t,e),a.add(e)}).update(function(t,e){var i=o.getItemGraphicEl(e);i.updateData(n,t),a.add(i),n.setItemGraphicEl(t,i)}).remove(function(t){var e=o.getItemGraphicEl(t);a.remove(e)}).execute(),this._data=n},remove:function(){this.group.removeAll(),this._data=null},dispose:function(){}});Bs(uC("funnel")),zs(function(t,e,i){t.eachSeriesByType("funnel",function(t){var i=t.getData(),n=i.mapDimension("value"),o=t.get("sort"),a=Tf(t,e),r=Af(i,o),s=[Vo(t.get("minSize"),a.width),Vo(t.get("maxSize"),a.width)],l=i.getDataExtent(n),u=t.get("min"),h=t.get("max");null==u&&(u=Math.min(l[0],0)),null==h&&(h=l[1]);var c=t.get("funnelAlign"),d=t.get("gap"),f=(a.height-d*(i.count()-1))/i.count(),p=a.y,g=function(t,e){var o,r=Bo(i.get(n,t)||0,[u,h],s,!0);switch(c){case"left":o=a.x;break;case"center":o=a.x+(a.width-r)/2;break;case"right":o=a.x+a.width-r}return[[o,e],[o+r,e]]};"ascending"===o&&(f=-f,d=-d,p+=a.height,r=r.reverse());for(var m=0;ma&&(e[1-n]=e[n]+h.sign*a),e},tk=d,ek=Math.min,ik=Math.max,nk=Math.floor,ok=Math.ceil,ak=Go,rk=Math.PI;Nf.prototype={type:"parallel",constructor:Nf,_init:function(t,e,i){var n=t.dimensions,o=t.parallelAxisIndex;tk(n,function(t,i){var n=o[i],a=e.getComponent("parallelAxis",n),r=this._axesMap.set(t,new JL(t,Hl(a),[0,0],a.get("type"),n)),s="category"===r.type;r.onBand=s&&a.get("boundaryGap"),r.inverse=a.get("inverse"),a.axis=r,r.model=a,r.coordinateSystem=a.coordinateSystem=this},this)},update:function(t,e){this._updateAxesFromSeries(this._model,t)},containPoint:function(t){var e=this._makeLayoutInfo(),i=e.axisBase,n=e.layoutBase,o=e.pixelDimIndex,a=t[1-o],r=t[o];return a>=i&&a<=i+e.axisLength&&r>=n&&r<=n+e.layoutLength},getModel:function(){return this._model},_updateAxesFromSeries:function(t,e){e.eachSeries(function(i){if(t.contains(i,e)){var n=i.getData();tk(this.dimensions,function(t){var e=this._axesMap.get(t);e.scale.unionExtentFromData(n,n.mapDimension(t)),Wl(e.scale,e.model)},this)}},this)},resize:function(t,e){this._rect=ca(t.getBoxLayoutParams(),{width:e.getWidth(),height:e.getHeight()}),this._layoutAxes()},getRect:function(){return this._rect},_makeLayoutInfo:function(){var t,e=this._model,i=this._rect,n=["x","y"],o=["width","height"],a=e.get("layout"),r="horizontal"===a?0:1,s=i[o[r]],l=[0,s],u=this.dimensions.length,h=Of(e.get("axisExpandWidth"),l),c=Of(e.get("axisExpandCount")||0,[0,u]),d=e.get("axisExpandable")&&u>3&&u>c&&c>1&&h>0&&s>0,f=e.get("axisExpandWindow");f?(t=Of(f[1]-f[0],l),f[1]=f[0]+t):(t=Of(h*(c-1),l),(f=[h*(e.get("axisExpandCenter")||nk(u/2))-t/2])[1]=f[0]+t);var p=(s-t)/(u-c);p<3&&(p=0);var g=[nk(ak(f[0]/h,1))+1,ok(ak(f[1]/h,1))-1],m=p/h*f[0];return{layout:a,pixelDimIndex:r,layoutBase:i[n[r]],layoutLength:s,axisBase:i[n[1-r]],axisLength:i[o[1-r]],axisExpandable:d,axisExpandWidth:h,axisCollapseWidth:p,axisExpandWindow:f,axisCount:u,winInnerIndices:g,axisExpandWindow0Pos:m}},_layoutAxes:function(){var t=this._rect,e=this._axesMap,i=this.dimensions,n=this._makeLayoutInfo(),o=n.layout;e.each(function(t){var e=[0,n.axisLength],i=t.inverse?1:0;t.setExtent(e[i],e[1-i])}),tk(i,function(e,i){var a=(n.axisExpandable?Rf:Ef)(i,n),r={horizontal:{x:a.position,y:n.axisLength},vertical:{x:0,y:a.position}},s={horizontal:rk/2,vertical:0},l=[r[o].x+t.x,r[o].y+t.y],u=s[o],h=xt();Mt(h,h,u),St(h,h,l),this._axesLayout[e]={position:l,rotation:u,transform:h,axisNameAvailableWidth:a.axisNameAvailableWidth,axisLabelShow:a.axisLabelShow,nameTruncateMaxWidth:a.nameTruncateMaxWidth,tickDirection:1,labelDirection:1}},this)},getAxis:function(t){return this._axesMap.get(t)},dataToPoint:function(t,e){return this.axisCoordToPoint(this._axesMap.get(e).dataToCoord(t),e)},eachActiveState:function(t,e,i,n){null==i&&(i=0),null==n&&(n=t.count());var o=this._axesMap,a=this.dimensions,r=[],s=[];d(a,function(e){r.push(t.mapDimension(e)),s.push(o.get(e).model)});for(var l=this.hasAxisBrushed(),u=i;uo*(1-h[0])?(l="jump",r=s-o*(1-h[2])):(r=s-o*h[1])>=0&&(r=s-o*(1-h[1]))<=0&&(r=0),(r*=e.axisExpandWidth/u)?QL(r,n,a,"all"):l="none";else{o=n[1]-n[0];(n=[ik(0,a[1]*s/o-o/2)])[1]=ek(a[1],n[0]+o),n[0]=n[1]-o}return{axisExpandWindow:n,behavior:l}}},Fa.register("parallel",{create:function(t,e){var i=[];return t.eachComponent("parallel",function(n,o){var a=new Nf(n,t,e);a.name="parallel_"+o,a.resize(n,e),n.coordinateSystem=a,a.model=n,i.push(a)}),t.eachSeries(function(e){if("parallel"===e.get("coordinateSystem")){var i=t.queryComponents({mainType:"parallel",index:e.get("parallelIndex"),id:e.get("parallelId")})[0];e.coordinateSystem=i.coordinateSystem}}),i}});var sk=lI.extend({type:"baseParallelAxis",axis:null,activeIntervals:[],getAreaSelectStyle:function(){return Qb([["fill","color"],["lineWidth","borderWidth"],["stroke","borderColor"],["width","width"],["opacity","opacity"]])(this.getModel("areaSelectStyle"))},setActiveIntervals:function(t){var e=this.activeIntervals=i(t);if(e)for(var n=e.length-1;n>=0;n--)Fo(e[n])},getActiveState:function(t){var e=this.activeIntervals;if(!e.length)return"normal";if(null==t||isNaN(t))return"inactive";if(1===e.length){var i=e[0];if(i[0]<=t&&t<=i[1])return"active"}else for(var n=0,o=e.length;n5)return;var n=this._model.coordinateSystem.getSlidedAxisExpandWindow([t.offsetX,t.offsetY]);"none"!==n.behavior&&this._dispatchExpand({axisExpandWindow:n.axisExpandWindow})}this._mouseDownPoint=null},mousemove:function(t){if(!this._mouseDownPoint&&Ip(this,"mousemove")){var e=this._model,i=e.coordinateSystem.getSlidedAxisExpandWindow([t.offsetX,t.offsetY]),n=i.behavior;"jump"===n&&this._throttledDispatchExpand.debounceNextCall(e.get("axisExpandDebounce")),this._throttledDispatchExpand("none"===n?null:{axisExpandWindow:i.axisExpandWindow,animation:"jump"===n&&null})}}};Ns(function(t){Cf(t),Lf(t)}),YI.extend({type:"series.parallel",dependencies:["parallel"],visualColorAccessPath:"lineStyle.color",getInitialData:function(t,e){var i=this.getSource();return Tp(i,this),ml(i,this)},getRawIndicesByActiveState:function(t){var e=this.coordinateSystem,i=this.getData(),n=[];return e.eachActiveState(i,function(e,o){t===e&&n.push(i.getRawIndex(o))}),n},defaultOption:{zlevel:0,z:2,coordinateSystem:"parallel",parallelIndex:0,label:{show:!1},inactiveOpacity:.05,activeOpacity:1,lineStyle:{width:1,opacity:.45,type:"solid"},emphasis:{label:{show:!1}},progressive:500,smooth:!1,animationEasing:"linear"}});var Dk=.3,Ck=(Ar.extend({type:"parallel",init:function(){this._dataGroup=new tb,this.group.add(this._dataGroup),this._data,this._initialized},render:function(t,e,i,n){var o=this._dataGroup,a=t.getData(),r=this._data,s=t.coordinateSystem,l=s.dimensions,u=kp(t);if(a.diff(r).add(function(t){Pp(Lp(a,o,t,l,s),a,t,u)}).update(function(e,i){var o=r.getItemGraphicEl(i),h=Cp(a,e,l,s);a.setItemGraphicEl(e,o),Io(o,{shape:{points:h}},n&&!1===n.animation?null:t,e),Pp(o,a,e,u)}).remove(function(t){var e=r.getItemGraphicEl(t);o.remove(e)}).execute(),!this._initialized){this._initialized=!0;var h=Dp(s,t,function(){setTimeout(function(){o.removeClipPath()})});o.setClipPath(h)}this._data=a},incrementalPrepareRender:function(t,e,i){this._initialized=!0,this._data=null,this._dataGroup.removeAll()},incrementalRender:function(t,e,i){for(var n=e.getData(),o=e.coordinateSystem,a=o.dimensions,r=kp(e),s=t.start;sn&&(n=e)}),d(e,function(e){var o=new hL({type:"color",mappingMethod:"linear",dataExtent:[i,n],visual:t.get("color")}).mapValueToVisual(e.getLayout().value);e.setVisual("color",o);var a=e.getModel().get("itemStyle.color");null!=a&&e.setVisual("color",a)})}})});var Ok={_baseAxisDim:null,getInitialData:function(t,e){var i,n,o=e.getComponent("xAxis",this.get("xAxisIndex")),a=e.getComponent("yAxis",this.get("yAxisIndex")),r=o.get("type"),s=a.get("type");"category"===r?(t.layout="horizontal",i=o.getOrdinalMeta(),n=!0):"category"===s?(t.layout="vertical",i=a.getOrdinalMeta(),n=!0):t.layout=t.layout||"horizontal";var l=["x","y"],u="horizontal"===t.layout?0:1,h=this._baseAxisDim=l[u],c=l[1-u],f=[o,a],p=f[u].get("type"),g=f[1-u].get("type"),m=t.data;if(m&&n){var v=[];d(m,function(t,e){var i;t.value&&y(t.value)?(i=t.value.slice(),t.value.unshift(e)):y(t)?(i=t.slice(),t.unshift(e)):i=t,v.push(i)}),t.data=v}var x=this.defaultValueDimensions;return oC(this,{coordDimensions:[{name:h,type:qs(p),ordinalMeta:i,otherDims:{tooltip:!1,itemName:0},dimsDef:["base"]},{name:c,type:qs(g),dimsDef:x.slice()}],dimensionsCount:x.length+1})},getBaseAxis:function(){var t=this._baseAxisDim;return this.ecModel.getComponent(t+"Axis",this.get(t+"AxisIndex")).axis}};h(YI.extend({type:"series.boxplot",dependencies:["xAxis","yAxis","grid"],defaultValueDimensions:[{name:"min",defaultTooltip:!0},{name:"Q1",defaultTooltip:!0},{name:"median",defaultTooltip:!0},{name:"Q3",defaultTooltip:!0},{name:"max",defaultTooltip:!0}],dimensions:null,defaultOption:{zlevel:0,z:2,coordinateSystem:"cartesian2d",legendHoverLink:!0,hoverAnimation:!0,layout:null,boxWidth:[7,50],itemStyle:{color:"#fff",borderWidth:1},emphasis:{itemStyle:{borderWidth:2,shadowBlur:5,shadowOffsetX:2,shadowOffsetY:2,shadowColor:"rgba(0,0,0,0.4)"}},animationEasing:"elasticOut",animationDuration:800}}),Ok,!0);var Ek=["itemStyle"],Rk=["emphasis","itemStyle"],zk=(Ar.extend({type:"boxplot",render:function(t,e,i){var n=t.getData(),o=this.group,a=this._data;this._data||o.removeAll();var r="horizontal"===t.get("layout")?1:0;n.diff(a).add(function(t){if(n.hasValue(t)){var e=ig(n.getItemLayout(t),n,t,r,!0);n.setItemGraphicEl(t,e),o.add(e)}}).update(function(t,e){var i=a.getItemGraphicEl(e);if(n.hasValue(t)){var s=n.getItemLayout(t);i?ng(s,i,n,t):i=ig(s,n,t,r),o.add(i),n.setItemGraphicEl(t,i)}else o.remove(i)}).remove(function(t){var e=a.getItemGraphicEl(t);e&&o.remove(e)}).execute(),this._data=n},remove:function(t){var e=this.group,i=this._data;this._data=null,i&&i.eachItemGraphicEl(function(t){t&&e.remove(t)})},dispose:B}),Pn.extend({type:"boxplotBoxPath",shape:{},buildPath:function(t,e){var i=e.points,n=0;for(t.moveTo(i[n][0],i[n][1]),n++;n<4;n++)t.lineTo(i[n][0],i[n][1]);for(t.closePath();n0?jk:Yk)}function n(t,e){return e.get(t>0?Uk:Xk)}var o=t.getData(),a=t.pipelineContext.large;if(o.setVisual({legendSymbol:"roundRect",colorP:i(1,t),colorN:i(-1,t),borderColorP:n(1,t),borderColorN:n(-1,t)}),!e.isSeriesFiltered(t))return!a&&{progress:function(t,e){for(var o;null!=(o=t.next());){var a=e.getItemModel(o),r=e.getItemLayout(o).sign;e.setItemVisual(o,{color:i(r,a),borderColor:n(r,a)})}}}}},Kk="undefined"!=typeof Float32Array?Float32Array:Array,$k={seriesType:"candlestick",plan:$I(),reset:function(t){var e=t.coordinateSystem,i=t.getData(),n=pg(t,i),o=0,a=1,r=["x","y"],s=i.mapDimension(r[o]),l=i.mapDimension(r[a],!0),u=l[0],h=l[1],c=l[2],d=l[3];if(i.setLayout({candleWidth:n,isSimpleBox:n<=1.3}),!(null==s||l.length<4))return{progress:t.pipelineContext.large?function(t,i){for(var n,r,l=new Kk(5*t.count),f=0,p=[],g=[];null!=(r=t.next());){var m=i.get(s,r),v=i.get(u,r),y=i.get(h,r),x=i.get(c,r),_=i.get(d,r);isNaN(m)||isNaN(x)||isNaN(_)?(l[f++]=NaN,f+=4):(l[f++]=fg(i,r,v,y,h),p[o]=m,p[a]=x,n=e.dataToPoint(p,null,g),l[f++]=n?n[0]:NaN,l[f++]=n?n[1]:NaN,p[a]=_,n=e.dataToPoint(p,null,g),l[f++]=n?n[1]:NaN)}i.setLayout("largePoints",l)}:function(t,i){function r(t,i){var n=[];return n[o]=i,n[a]=t,isNaN(i)||isNaN(t)?[NaN,NaN]:e.dataToPoint(n)}function l(t,e,i){var a=e.slice(),r=e.slice();a[o]=Jn(a[o]+n/2,1,!1),r[o]=Jn(r[o]-n/2,1,!0),i?t.push(a,r):t.push(r,a)}function f(t){return t[o]=Jn(t[o],1),t}for(var p;null!=(p=t.next());){var g=i.get(s,p),m=i.get(u,p),v=i.get(h,p),y=i.get(c,p),x=i.get(d,p),_=Math.min(m,v),w=Math.max(m,v),b=r(_,g),S=r(w,g),M=r(y,g),I=r(x,g),T=[];l(T,S,0),l(T,b,1),T.push(f(I),f(S),f(M),f(b)),i.setItemLayout(p,{sign:fg(i,p,m,v,h),initBaseline:m>v?S[a]:b[a],ends:T,brushRect:function(t,e,i){var s=r(t,i),l=r(e,i);return s[o]-=n/2,l[o]-=n/2,{x:s[0],y:s[1],width:a?n:l[0]-s[0],height:a?l[1]-s[1]:n}}(y,x,g)})}}}}};Ns(function(t){t&&y(t.series)&&d(t.series,function(t){w(t)&&"k"===t.type&&(t.type="candlestick")})}),Bs(qk),zs($k),YI.extend({type:"series.effectScatter",dependencies:["grid","polar"],getInitialData:function(t,e){return ml(this.getSource(),this)},brushSelector:"point",defaultOption:{coordinateSystem:"cartesian2d",zlevel:0,z:2,legendHoverLink:!0,effectType:"ripple",progressive:0,showEffectOn:"render",rippleEffect:{period:4,scale:2.5,brushType:"fill"},symbolSize:10}});var Jk=vg.prototype;Jk.stopEffectAnimation=function(){this.childAt(1).removeAll()},Jk.startEffectAnimation=function(t){for(var e=t.symbolType,i=t.color,n=this.childAt(1),o=0;o<3;o++){var a=Jl(e,-1,-1,2,2,i);a.attr({style:{strokeNoScale:!0},z2:99,silent:!0,scale:[.5,.5]});var r=-o/3*t.period+t.effectOffset;a.animate("",!0).when(t.period,{scale:[t.rippleScale/2,t.rippleScale/2]}).delay(r).start(),a.animateStyle(!0).when(t.period,{opacity:0}).delay(r).start(),n.add(a)}mg(n,t)},Jk.updateEffectAnimation=function(t){for(var e=this._effectCfg,i=this.childAt(1),n=["symbolType","period","rippleScale"],o=0;o "))},preventIncremental:function(){return!!this.get("effect.show")},getProgressive:function(){var t=this.option.progressive;return null==t?this.option.large?1e4:this.get("progressive"):t},getProgressiveThreshold:function(){var t=this.option.progressiveThreshold;return null==t?this.option.large?2e4:this.get("progressiveThreshold"):t},defaultOption:{coordinateSystem:"geo",zlevel:0,z:2,legendHoverLink:!0,hoverAnimation:!0,xAxisIndex:0,yAxisIndex:0,symbol:["none","none"],symbolSize:[10,10],geoIndex:0,effect:{show:!1,period:4,constantSpeed:0,symbol:"circle",symbolSize:3,loop:!0,trailLength:.2},large:!1,largeThreshold:2e3,polyline:!1,label:{show:!1,position:"end"},lineStyle:{opacity:.5}}}),iP=xg.prototype;iP.createLine=function(t,e,i){return new rf(t,e,i)},iP._updateEffectSymbol=function(t,e){var i=t.getItemModel(e).getModel("effect"),n=i.get("symbolSize"),o=i.get("symbol");y(n)||(n=[n,n]);var a=i.get("color")||t.getItemVisual(e,"color"),r=this.childAt(1);this._symbolType!==o&&(this.remove(r),(r=Jl(o,-.5,-.5,1,1,a)).z2=100,r.culling=!0,this.add(r)),r&&(r.setStyle("shadowColor",a),r.setStyle(i.getItemStyle(["color"])),r.attr("scale",n),r.setColor(a),r.attr("scale",n),this._symbolType=o,this._updateEffectAnimation(t,i,e))},iP._updateEffectAnimation=function(t,e,i){var n=this.childAt(1);if(n){var o=this,a=t.getItemLayout(i),r=1e3*e.get("period"),s=e.get("loop"),l=e.get("constantSpeed"),u=T(e.get("delay"),function(e){return e/t.count()*r/3}),h="function"==typeof u;if(n.ignore=!0,this.updateAnimationPoints(n,a),l>0&&(r=this.getLineLength(n)/l*1e3),r!==this._period||s!==this._loop){n.stopAnimation();var c=u;h&&(c=u(i)),n.__t>0&&(c=-r*n.__t),n.__t=0;var d=n.animate("",s).when(r,{__t:1}).delay(c).during(function(){o.updateSymbolPosition(n)});s||d.done(function(){o.remove(n)}),d.start()}this._period=r,this._loop=s}},iP.getLineLength=function(t){return uw(t.__p1,t.__cp1)+uw(t.__cp1,t.__p2)},iP.updateAnimationPoints=function(t,e){t.__p1=e[0],t.__p2=e[1],t.__cp1=e[2]||[(e[0][0]+e[1][0])/2,(e[0][1]+e[1][1])/2]},iP.updateData=function(t,e,i){this.childAt(0).updateData(t,e,i),this._updateEffectSymbol(t,e)},iP.updateSymbolPosition=function(t){var e=t.__p1,i=t.__p2,n=t.__cp1,o=t.__t,a=t.position,r=sn,s=ln;a[0]=r(e[0],n[0],i[0],o),a[1]=r(e[1],n[1],i[1],o);var l=s(e[0],n[0],i[0],o),u=s(e[1],n[1],i[1],o);t.rotation=-Math.atan2(u,l)-Math.PI/2,t.ignore=!1},iP.updateLayout=function(t,e){this.childAt(0).updateLayout(t,e);var i=t.getItemModel(e).getModel("effect");this._updateEffectAnimation(t,i,e)},u(xg,tb);var nP=_g.prototype;nP._createPolyline=function(t,e,i){var n=t.getItemLayout(e),o=new gM({shape:{points:n}});this.add(o),this._updateCommonStl(t,e,i)},nP.updateData=function(t,e,i){var n=t.hostModel;Io(this.childAt(0),{shape:{points:t.getItemLayout(e)}},n,e),this._updateCommonStl(t,e,i)},nP._updateCommonStl=function(t,e,i){var n=this.childAt(0),o=t.getItemModel(e),a=t.getItemVisual(e,"color"),s=i&&i.lineStyle,l=i&&i.hoverLineStyle;i&&!t.hasItemOption||(s=o.getModel("lineStyle").getLineStyle(),l=o.getModel("emphasis.lineStyle").getLineStyle()),n.useStyle(r({strokeNoScale:!0,fill:"none",stroke:a},s)),n.hoverStyle=l,fo(this)},nP.updateLayout=function(t,e){this.childAt(0).setShape("points",t.getItemLayout(e))},u(_g,tb);var oP=wg.prototype;oP.createLine=function(t,e,i){return new _g(t,e,i)},oP.updateAnimationPoints=function(t,e){this._points=e;for(var i=[0],n=0,o=1;o=0&&!(n[r]<=e);r--);r=Math.min(r,o-2)}else{for(var r=a;re);r++);r=Math.min(r-1,o-2)}J(t.position,i[r],i[r+1],(e-n[r])/(n[r+1]-n[r]));var s=i[r+1][0]-i[r][0],l=i[r+1][1]-i[r][1];t.rotation=-Math.atan2(l,s)-Math.PI/2,this._lastFrame=r,this._lastFramePercent=e,t.ignore=!1}},u(wg,xg);var aP=Un({shape:{polyline:!1,curveness:0,segs:[]},buildPath:function(t,e){var i=e.segs,n=e.curveness;if(e.polyline)for(r=0;r0){t.moveTo(i[r++],i[r++]);for(var a=1;a0){var c=(s+u)/2-(l-h)*n,d=(l+h)/2-(u-s)*n;t.quadraticCurveTo(c,d,u,h)}else t.lineTo(u,h)}},findDataIndex:function(t,e){var i=this.shape,n=i.segs,o=i.curveness;if(i.polyline)for(var a=0,r=0;r0)for(var l=n[r++],u=n[r++],h=1;h0){if(_n(l,u,(l+c)/2-(u-d)*o,(u+d)/2-(c-l)*o,c,d))return a}else if(yn(l,u,c,d))return a;a++}return-1}}),rP=bg.prototype;rP.isPersistent=function(){return!this._incremental},rP.updateData=function(t){this.group.removeAll();var e=new aP({rectHover:!0,cursor:"default"});e.setShape({segs:t.getLayout("linesPoints")}),this._setCommon(e,t),this.group.add(e),this._incremental=null},rP.incrementalPrepareUpdate=function(t){this.group.removeAll(),this._clearIncremental(),t.count()>5e5?(this._incremental||(this._incremental=new Zn({silent:!0})),this.group.add(this._incremental)):this._incremental=null},rP.incrementalUpdate=function(t,e){var i=new aP;i.setShape({segs:e.getLayout("linesPoints")}),this._setCommon(i,e,!!this._incremental),this._incremental?this._incremental.addDisplayable(i,!0):(i.rectHover=!0,i.cursor="default",i.__startIndex=t.start,this.group.add(i))},rP.remove=function(){this._clearIncremental(),this._incremental=null,this.group.removeAll()},rP._setCommon=function(t,e,i){var n=e.hostModel;t.setShape({polyline:n.get("polyline"),curveness:n.get("lineStyle.curveness")}),t.useStyle(n.getModel("lineStyle").getLineStyle()),t.style.strokeNoScale=!0;var o=e.getVisual("color");o&&t.setStyle("stroke",o),t.setStyle("fill"),i||(t.seriesIndex=n.seriesIndex,t.on("mousemove",function(e){t.dataIndex=null;var i=t.findDataIndex(e.offsetX,e.offsetY);i>0&&(t.dataIndex=i+t.__startIndex)}))},rP._clearIncremental=function(){var t=this._incremental;t&&t.clearDisplaybles()};var sP={seriesType:"lines",plan:$I(),reset:function(t){var e=t.coordinateSystem,i=t.get("polyline"),n=t.pipelineContext.large;return{progress:function(o,a){var r=[];if(n){var s,l=o.end-o.start;if(i){for(var u=0,h=o.start;h0){var I=a(v)?s:l;v>0&&(v=v*S+b),x[_++]=I[M],x[_++]=I[M+1],x[_++]=I[M+2],x[_++]=I[M+3]*v*256}else _+=4}return c.putImageData(y,0,0),h},_getBrush:function(){var t=this._brushCanvas||(this._brushCanvas=iw()),e=this.pointSize+this.blurSize,i=2*e;t.width=i,t.height=i;var n=t.getContext("2d");return n.clearRect(0,0,i,i),n.shadowOffsetX=i,n.shadowBlur=this.blurSize,n.shadowColor="#000",n.beginPath(),n.arc(-e,e,this.pointSize,0,2*Math.PI,!0),n.closePath(),n.fill(),t},_getGradient:function(t,e,i){for(var n=this._gradientPixels,o=n[i]||(n[i]=new Uint8ClampedArray(1024)),a=[0,0,0,0],r=0,s=0;s<256;s++)e[i](s/255,!0,a),o[r++]=a[0],o[r++]=a[1],o[r++]=a[2],o[r++]=a[3];return o}},Zs({type:"heatmap",render:function(t,e,i){var n;e.eachComponent("visualMap",function(e){e.eachTargetSeries(function(i){i===t&&(n=e)})}),this.group.removeAll(),this._incrementalDisplayable=null;var o=t.coordinateSystem;"cartesian2d"===o.type||"calendar"===o.type?this._renderOnCartesianAndCalendar(t,i,0,t.getData().count()):Ag(o)&&this._renderOnGeo(o,t,n,i)},incrementalPrepareRender:function(t,e,i){this.group.removeAll()},incrementalRender:function(t,e,i,n){e.coordinateSystem&&this._renderOnCartesianAndCalendar(e,n,t.start,t.end,!0)},_renderOnCartesianAndCalendar:function(t,e,i,n,o){var r,s,l=t.coordinateSystem;if("cartesian2d"===l.type){var u=l.getAxis("x"),h=l.getAxis("y");r=u.getBandWidth(),s=h.getBandWidth()}for(var c=this.group,d=t.getData(),f=t.getModel("itemStyle").getItemStyle(["color"]),p=t.getModel("emphasis.itemStyle").getItemStyle(),g=t.getModel("label"),m=t.getModel("emphasis.label"),v=l.type,y="cartesian2d"===v?[d.mapDimension("x"),d.mapDimension("y"),d.mapDimension("value")]:[d.mapDimension("time"),d.mapDimension("value")],x=i;x=e.y&&t[1]<=e.y+e.height:i.contain(i.toLocalCoord(t[1]))&&t[0]>=e.y&&t[0]<=e.y+e.height},pointToData:function(t){var e=this.getAxis();return[e.coordToData(e.toLocalCoord(t["horizontal"===e.orient?0:1]))]},dataToPoint:function(t){var e=this.getAxis(),i=this.getRect(),n=[],o="horizontal"===e.orient?0:1;return t instanceof Array&&(t=t[0]),n[o]=e.toGlobalCoord(e.dataToCoord(+t)),n[1-o]=0===o?i.y+i.height/2:i.x+i.width/2,n}},Fa.register("single",{create:function(t,e){var i=[];return t.eachComponent("singleAxis",function(n,o){var a=new $g(n,t,e);a.name="single_"+o,a.resize(n,e),n.coordinateSystem=a,i.push(a)}),t.eachSeries(function(e){if("singleAxis"===e.get("coordinateSystem")){var i=t.queryComponents({mainType:"singleAxis",index:e.get("singleAxisIndex"),id:e.get("singleAxisId")})[0];e.coordinateSystem=i&&i.coordinateSystem}}),i},dimensions:$g.prototype.dimensions});var gP=["axisLine","axisTickLabel","axisName"],mP=XD.extend({type:"singleAxis",axisPointerClass:"SingleAxisPointer",render:function(t,e,i,n){var o=this.group;o.removeAll();var a=Jg(t),r=new FD(t,a);d(gP,r.add,r),o.add(r.getGroup()),t.get("splitLine.show")&&this._splitLine(t),mP.superCall(this,"render",t,e,i,n)},_splitLine:function(t){var e=t.axis;if(!e.scale.isBlank()){var i=t.getModel("splitLine"),n=i.getModel("lineStyle"),o=n.get("width"),a=n.get("color");a=a instanceof Array?a:[a];for(var r=t.coordinateSystem.getRect(),s=e.isHorizontal(),l=[],u=0,h=e.getTicksCoords({tickModel:i}),c=[],d=[],f=0;f=0)&&i({type:"updateAxisPointer",currTrigger:t,x:e&&e.offsetX,y:e&&e.offsetY})})},remove:function(t,e){gm(e.getZr(),"axisPointer"),IP.superApply(this._model,"remove",arguments)},dispose:function(t,e){gm("axisPointer",e),IP.superApply(this._model,"dispose",arguments)}}),TP=Bi(),AP=i,DP=m;(mm.prototype={_group:null,_lastGraphicKey:null,_handle:null,_dragging:!1,_lastValue:null,_lastStatus:null,_payloadInfo:null,animationThreshold:15,render:function(t,e,i,n){var o=e.get("value"),a=e.get("status");if(this._axisModel=t,this._axisPointerModel=e,this._api=i,n||this._lastValue!==o||this._lastStatus!==a){this._lastValue=o,this._lastStatus=a;var r=this._group,s=this._handle;if(!a||"hide"===a)return r&&r.hide(),void(s&&s.hide());r&&r.show(),s&&s.show();var l={};this.makeElOption(l,o,t,e,i);var u=l.graphicKey;u!==this._lastGraphicKey&&this.clear(i),this._lastGraphicKey=u;var h=this._moveAnimation=this.determineAnimation(t,e);if(r){var c=v(vm,e,h);this.updatePointerEl(r,l,c,e),this.updateLabelEl(r,l,c,e)}else r=this._group=new tb,this.createPointerEl(r,l,t,e),this.createLabelEl(r,l,t,e),i.getZr().add(r);wm(r,e,!0),this._renderHandle(o)}},remove:function(t){this.clear(t)},dispose:function(t){this.clear(t)},determineAnimation:function(t,e){var i=e.get("animation"),n=t.axis,o="category"===n.type,a=e.get("snap");if(!a&&!o)return!1;if("auto"===i||null==i){var r=this.animationThreshold;if(o&&n.getBandWidth()>r)return!0;if(a){var s=Mh(t).seriesDataCount,l=n.getExtent();return Math.abs(l[0]-l[1])/s>r}return!1}return!0===i},makeElOption:function(t,e,i,n,o){},createPointerEl:function(t,e,i,n){var o=e.pointer;if(o){var a=TP(t).pointerEl=new zM[o.type](AP(e.pointer));t.add(a)}},createLabelEl:function(t,e,i,n){if(e.label){var o=TP(t).labelEl=new yM(AP(e.label));t.add(o),xm(o,n)}},updatePointerEl:function(t,e,i){var n=TP(t).pointerEl;n&&(n.setStyle(e.pointer.style),i(n,{shape:e.pointer.shape}))},updateLabelEl:function(t,e,i,n){var o=TP(t).labelEl;o&&(o.setStyle(e.label.style),i(o,{shape:e.label.shape,position:e.label.position}),xm(o,n))},_renderHandle:function(t){if(!this._dragging&&this.updateHandleTransform){var e=this._axisPointerModel,i=this._api.getZr(),n=this._handle,o=e.getModel("handle"),a=e.get("status");if(!o.get("show")||!a||"hide"===a)return n&&i.remove(n),void(this._handle=null);var r;this._handle||(r=!0,n=this._handle=Po(o.get("icon"),{cursor:"move",draggable:!0,onmousemove:function(t){mw(t.event)},onmousedown:DP(this._onHandleDragMove,this,0,0),drift:DP(this._onHandleDragMove,this),ondragend:DP(this._onHandleDragEnd,this)}),i.add(n)),wm(n,e,!1);var s=["color","borderColor","borderWidth","opacity","shadowColor","shadowBlur","shadowOffsetX","shadowOffsetY"];n.setStyle(o.getItemStyle(null,s));var l=o.get("size");y(l)||(l=[l,l]),n.attr("scale",[l[0]/2,l[1]/2]),Nr(this,"_doDispatchAxisPointer",o.get("throttle")||0,"fixRate"),this._moveHandleToValue(t,r)}},_moveHandleToValue:function(t,e){vm(this._axisPointerModel,!e&&this._moveAnimation,this._handle,_m(this.getHandleTransform(t,this._axisModel,this._axisPointerModel)))},_onHandleDragMove:function(t,e){var i=this._handle;if(i){this._dragging=!0;var n=this.updateHandleTransform(_m(i),[t,e],this._axisModel,this._axisPointerModel);this._payloadInfo=n,i.stopAnimation(),i.attr(_m(n)),TP(i).lastProp=null,this._doDispatchAxisPointer()}},_doDispatchAxisPointer:function(){if(this._handle){var t=this._payloadInfo,e=this._axisModel;this._api.dispatchAction({type:"updateAxisPointer",x:t.cursorPoint[0],y:t.cursorPoint[1],tooltipOption:t.tooltipOption,axesInfo:[{axisDim:e.axis.dim,axisIndex:e.componentIndex}]})}},_onHandleDragEnd:function(t){if(this._dragging=!1,this._handle){var e=this._axisPointerModel.get("value");this._moveHandleToValue(e),this._api.dispatchAction({type:"hideTip"})}},getHandleTransform:null,updateHandleTransform:null,clear:function(t){this._lastValue=null,this._lastStatus=null;var e=t.getZr(),i=this._group,n=this._handle;e&&i&&(this._lastGraphicKey=null,i&&e.remove(i),n&&e.remove(n),this._group=null,this._handle=null,this._payloadInfo=null)},doClear:function(){},buildLabel:function(t,e,i){return i=i||0,{x:t[i],y:t[1-i],width:e[i],height:e[1-i]}}}).constructor=mm,ji(mm);var CP=mm.extend({makeElOption:function(t,e,i,n,o){var a=i.axis,r=a.grid,s=n.get("type"),l=km(r,a).getOtherAxis(a).getGlobalExtent(),u=a.toGlobalCoord(a.dataToCoord(e,!0));if(s&&"none"!==s){var h=bm(n),c=LP[s](a,u,l,h);c.style=h,t.graphicKey=c.type,t.pointer=c}Am(e,t,Lh(r.model,i),i,n,o)},getHandleTransform:function(t,e,i){var n=Lh(e.axis.grid.model,e,{labelInside:!1});return n.labelMargin=i.get("handle.margin"),{position:Tm(e.axis,t,n),rotation:n.rotation+(n.labelDirection<0?Math.PI:0)}},updateHandleTransform:function(t,e,i,n){var o=i.axis,a=o.grid,r=o.getGlobalExtent(!0),s=km(a,o).getOtherAxis(o).getGlobalExtent(),l="x"===o.dim?0:1,u=t.position;u[l]+=e[l],u[l]=Math.min(r[1],u[l]),u[l]=Math.max(r[0],u[l]);var h=(s[1]+s[0])/2,c=[h,h];c[l]=u[l];var d=[{verticalAlign:"middle"},{align:"center"}];return{position:u,rotation:t.rotation,cursorPoint:c,tooltipOption:d[l]}}}),LP={line:function(t,e,i,n){var o=Dm([e,i[0]],[e,i[1]],Pm(t));return Kn({shape:o,style:n}),{type:"Line",shape:o}},shadow:function(t,e,i,n){var o=Math.max(1,t.getBandWidth()),a=i[1]-i[0];return{type:"Rect",shape:Cm([e-o/2,i[0]],[o,a],Pm(t))}}};XD.registerAxisPointerClass("CartesianAxisPointer",CP),Ns(function(t){if(t){(!t.axisPointer||0===t.axisPointer.length)&&(t.axisPointer={});var e=t.axisPointer.link;e&&!y(e)&&(t.axisPointer.link=[e])}}),Os(VT.PROCESSOR.STATISTIC,function(t,e){t.getComponent("axisPointer").coordSysAxesInfo=vh(t,e)}),Es({type:"updateAxisPointer",event:"updateAxisPointer",update:":updateAxisPointer"},function(t,e,i){var n=t.currTrigger,o=[t.x,t.y],a=t,r=t.dispatchAction||m(i.dispatchAction,i),s=e.getComponent("axisPointer").coordSysAxesInfo;if(s){lm(o)&&(o=xP({seriesIndex:a.seriesIndex,dataIndex:a.dataIndex},e).point);var l=lm(o),u=a.axesInfo,h=s.axesInfo,c="leave"===n||lm(o),d={},f={},p={list:[],map:{}},g={showPointer:wP(em,f),showTooltip:wP(im,p)};_P(s.coordSysMap,function(t,e){var i=l||t.containPoint(o);_P(s.coordSysAxesInfo[e],function(t,e){var n=t.axis,a=rm(u,t);if(!c&&i&&(!u||a)){var r=a&&a.value;null!=r||l||(r=n.pointToData(o)),null!=r&&Qg(t,r,g,!1,d)}})});var v={};return _P(h,function(t,e){var i=t.linkGroup;i&&!f[e]&&_P(i.axesInfo,function(e,n){var o=f[n];if(e!==t&&o){var a=o.value;i.mapper&&(a=t.axis.scale.parse(i.mapper(a,sm(e),sm(t)))),v[t.key]=a}})}),_P(v,function(t,e){Qg(h[e],t,g,!0,d)}),nm(f,h,d),om(p,o,t,r),am(h,0,i),d}});var kP=["x","y"],PP=["width","height"],NP=mm.extend({makeElOption:function(t,e,i,n,o){var a=i.axis,r=a.coordinateSystem,s=Om(r,1-Nm(a)),l=r.dataToPoint(e)[0],u=n.get("type");if(u&&"none"!==u){var h=bm(n),c=OP[u](a,l,s,h);c.style=h,t.graphicKey=c.type,t.pointer=c}Am(e,t,Jg(i),i,n,o)},getHandleTransform:function(t,e,i){var n=Jg(e,{labelInside:!1});return n.labelMargin=i.get("handle.margin"),{position:Tm(e.axis,t,n),rotation:n.rotation+(n.labelDirection<0?Math.PI:0)}},updateHandleTransform:function(t,e,i,n){var o=i.axis,a=o.coordinateSystem,r=Nm(o),s=Om(a,r),l=t.position;l[r]+=e[r],l[r]=Math.min(s[1],l[r]),l[r]=Math.max(s[0],l[r]);var u=Om(a,1-r),h=(u[1]+u[0])/2,c=[h,h];return c[r]=l[r],{position:l,rotation:t.rotation,cursorPoint:c,tooltipOption:{verticalAlign:"middle"}}}}),OP={line:function(t,e,i,n){var o=Dm([e,i[0]],[e,i[1]],Nm(t));return Kn({shape:o,style:n}),{type:"Line",shape:o}},shadow:function(t,e,i,n){var o=t.getBandWidth(),a=i[1]-i[0];return{type:"Rect",shape:Cm([e-o/2,i[0]],[o,a],Nm(t))}}};XD.registerAxisPointerClass("SingleAxisPointer",NP),Ws({type:"single"});var EP=YI.extend({type:"series.themeRiver",dependencies:["singleAxis"],nameMap:null,init:function(t){EP.superApply(this,"init",arguments),this.legendDataProvider=function(){return this.getRawData()}},fixData:function(t){var e=t.length,i=[];Zi(t,function(t){return t[2]}).buckets.each(function(t,e){i.push({name:e,dataList:t})});for(var n=i.length,o=-1,a=-1,r=0;ro&&(o=s,a=r)}for(var l=0;lMath.PI/2?"right":"left"):x&&"center"!==x?"left"===x?(f=u.r0+y,p>Math.PI/2&&(x="right")):"right"===x&&(f=u.r-y,p>Math.PI/2&&(x="left")):(f=(u.r+u.r0)/2,x="center"),d.attr("style",{text:l,textAlign:x,textVerticalAlign:n("verticalAlign")||"middle",opacity:n("opacity")});var _=f*g+u.cx,w=f*m+u.cy;d.attr("position",[_,w]);var b=n("rotate"),S=0;"radial"===b?(S=-p)<-Math.PI/2&&(S+=Math.PI):"tangential"===b?(S=Math.PI/2-p)>Math.PI/2?S-=Math.PI:S<-Math.PI/2&&(S+=Math.PI):"number"==typeof b&&(S=b*Math.PI/180),d.attr("rotation",S)},VP._initEvents=function(t,e,i,n){t.off("mouseover").off("mouseout").off("emphasis").off("normal");var o=this,a=function(){o.onEmphasis(n)},r=function(){o.onNormal()};i.isAnimationEnabled()&&t.on("mouseover",a).on("mouseout",r).on("emphasis",a).on("normal",r).on("downplay",function(){o.onDownplay()}).on("highlight",function(){o.onHighlight()})},u(Vm,tb);Ar.extend({type:"sunburst",init:function(){},render:function(t,e,i,n){function o(i,n){if(c||!i||i.getValue()||(i=null),i!==l&&n!==l)if(n&&n.piece)i?(n.piece.updateData(!1,i,"normal",t,e),s.setItemGraphicEl(i.dataIndex,n.piece)):a(n);else if(i){var o=new Vm(i,t,e);h.add(o),s.setItemGraphicEl(i.dataIndex,o)}}function a(t){t&&t.piece&&(h.remove(t.piece),t.piece=null)}var r=this;this.seriesModel=t,this.api=i,this.ecModel=e;var s=t.getData(),l=s.tree.root,u=t.getViewRoot(),h=this.group,c=t.get("renderLabelForZeroData"),d=[];u.eachNode(function(t){d.push(t)});var f=this._oldChildren||[];if(function(t,e){function i(t){return t.getId()}function n(i,n){o(null==i?null:t[i],null==n?null:e[n])}0===t.length&&0===e.length||new Xs(e,t,i,i).add(n).update(n).remove(v(n,null)).execute()}(d,f),function(i,n){if(n.depth>0){r.virtualPiece?r.virtualPiece.updateData(!1,i,"normal",t,e):(r.virtualPiece=new Vm(i,t,e),h.add(r.virtualPiece)),n.piece._onclickEvent&&n.piece.off("click",n.piece._onclickEvent);var o=function(t){r._rootToNode(n.parentNode)};n.piece._onclickEvent=o,r.virtualPiece.on("click",o)}else r.virtualPiece&&(h.remove(r.virtualPiece),r.virtualPiece=null)}(l,u),n&&n.highlight&&n.highlight.piece){var p=t.getShallow("highlightPolicy");n.highlight.piece.onEmphasis(p)}else if(n&&n.unhighlight){var g=this.virtualPiece;!g&&l.children.length&&(g=l.children[0].piece),g&&g.onNormal()}this._initEvents(),this._oldChildren=d},dispose:function(){},_initEvents:function(){var t=this,e=function(e){var i=!1;t.seriesModel.getViewRoot().eachNode(function(n){if(!i&&n.piece&&n.piece.childAt(0)===e.target){var o=n.getModel().get("nodeClick");if("rootToNode"===o)t._rootToNode(n);else if("link"===o){var a=n.getModel(),r=a.get("link");if(r){var s=a.get("target",!0)||"_blank";window.open(r,s)}}i=!0}})};this.group._onclickEvent&&this.group.off("click",this.group._onclickEvent),this.group.on("click",e),this.group._onclickEvent=e},_rootToNode:function(t){t!==this.seriesModel.getViewRoot()&&this.api.dispatchAction({type:"sunburstRootToNode",from:this.uid,seriesId:this.seriesModel.id,targetNode:t})},containPoint:function(t,e){var i=e.getData().getItemLayout(0);if(i){var n=t[0]-i.cx,o=t[1]-i.cy,a=Math.sqrt(n*n+o*o);return a<=i.r&&a>=i.r0}}});var GP="sunburstRootToNode";Es({type:GP,update:"updateView"},function(t,e){e.eachComponent({mainType:"series",subType:"sunburst",query:t},function(e,i){var n=ld(t,[GP],e);if(n){var o=e.getViewRoot();o&&(t.direction=hd(o,n.node)?"rollUp":"drillDown"),e.resetViewRoot(n.node)}})});var FP="sunburstHighlight";Es({type:FP,update:"updateView"},function(t,e){e.eachComponent({mainType:"series",subType:"sunburst",query:t},function(e,i){var n=ld(t,[FP],e);n&&(t.highlight=n.node)})});Es({type:"sunburstUnhighlight",update:"updateView"},function(t,e){e.eachComponent({mainType:"series",subType:"sunburst",query:t},function(e,i){t.unhighlight=!0})});var WP=Math.PI/180;Bs(v(uC,"sunburst")),zs(v(function(t,e,i,n){e.eachSeriesByType(t,function(t){var e=t.get("center"),n=t.get("radius");y(n)||(n=[0,n]),y(e)||(e=[e,e]);var o=i.getWidth(),a=i.getHeight(),r=Math.min(o,a),s=Vo(e[0],o),l=Vo(e[1],a),u=Vo(n[0],r/2),h=Vo(n[1],r/2),c=-t.get("startAngle")*WP,f=t.get("minAngle")*WP,p=t.getData().tree.root,g=t.getViewRoot(),m=g.depth,v=t.get("sort");null!=v&&Zm(g,v);var x=0;d(g.children,function(t){!isNaN(t.getValue())&&x++});var _=g.getValue(),w=Math.PI/(_||x)*2,b=g.depth>0,S=g.height-(b?-1:1),M=(h-u)/(S||1),I=t.get("clockwise"),T=t.get("stillShowZeroSum"),A=I?1:-1,D=function(t,e){if(t){var i=e;if(t!==p){var n=t.getValue(),o=0===_&&T?w:n*w;on[1]&&n.reverse(),{coordSys:{type:"polar",cx:t.cx,cy:t.cy,r:n[1],r0:n[0]},api:{coord:m(function(n){var o=e.dataToRadius(n[0]),a=i.dataToAngle(n[1]),r=t.coordToPoint([o,a]);return r.push(o,a*Math.PI/180),r}),size:m(qm,t)}}},calendar:function(t){var e=t.getRect(),i=t.getRangeInfo();return{coordSys:{type:"calendar",x:e.x,y:e.y,width:e.width,height:e.height,cellWidth:t.getCellWidth(),cellHeight:t.getCellHeight(),rangeInfo:{start:i.start,end:i.end,weeks:i.weeks,dayCount:i.allDay}},api:{coord:function(e,i){return t.dataToPoint(e,i)}}}}};YI.extend({type:"series.custom",dependencies:["grid","polar","geo","singleAxis","calendar"],defaultOption:{coordinateSystem:"cartesian2d",zlevel:0,z:2,legendHoverLink:!0,useTransform:!0},getInitialData:function(t,e){return ml(this.getSource(),this)},getDataParams:function(t,e,i){var n=YI.prototype.getDataParams.apply(this,arguments);return i&&(n.info=i.info),n}}),Ar.extend({type:"custom",_data:null,render:function(t,e,i,n){var o=this._data,a=t.getData(),r=this.group,s=Qm(t,a,e,i);a.diff(o).add(function(e){ev(null,e,s(e,n),t,r,a)}).update(function(e,i){ev(o.getItemGraphicEl(i),e,s(e,n),t,r,a)}).remove(function(t){var e=o.getItemGraphicEl(t);e&&r.remove(e)}).execute(),this._data=a},incrementalPrepareRender:function(t,e,i){this.group.removeAll(),this._data=null},incrementalRender:function(t,e,i,n,o){for(var a=e.getData(),r=Qm(e,a,i,n),s=t.start;s=0;l--)null==o[l]?o.splice(l,1):delete o[l].$action},_flatten:function(t,e,i){d(t,function(t){if(t){i&&(t.parentOption=i),e.push(t);var n=t.children;"group"===t.type&&n&&this._flatten(n,e,t),delete t.children}},this)},useElOptionsToUpdate:function(){var t=this._elOptionsToUpdate;return this._elOptionsToUpdate=null,t}});Ws({type:"graphic",init:function(t,e){this._elMap=R(),this._lastGraphicModel},render:function(t,e,i){t!==this._lastGraphicModel&&this._clear(),this._lastGraphicModel=t,this._updateElements(t),this._relocate(t,i)},_updateElements:function(t){var e=t.useElOptionsToUpdate();if(e){var i=this._elMap,n=this.group;d(e,function(e){var o=e.$action,a=e.id,r=i.get(a),s=e.parentId,l=null!=s?i.get(s):n,u=e.style;"text"===e.type&&u&&(e.hv&&e.hv[1]&&(u.textVerticalAlign=u.textBaseline=null),!u.hasOwnProperty("textFill")&&u.fill&&(u.textFill=u.fill),!u.hasOwnProperty("textStroke")&&u.stroke&&(u.textStroke=u.stroke));var h=fv(e);o&&"merge"!==o?"replace"===o?(dv(r,i),cv(a,l,h,i)):"remove"===o&&dv(r,i):r?r.attr(h):cv(a,l,h,i);var c=i.get(a);c&&(c.__ecGraphicWidth=e.width,c.__ecGraphicHeight=e.height,yv(c,t))})}},_relocate:function(t,e){for(var i=t.option.elements,n=this.group,o=this._elMap,a=i.length-1;a>=0;a--){var r=i[a],s=o.get(r.id);if(s){var l=s.parent;da(s,r,l===n?{width:e.getWidth(),height:e.getHeight()}:{width:l.__ecGraphicWidth||0,height:l.__ecGraphicHeight||0},null,{hv:r.hv,boundingMode:r.bounding})}}},_clear:function(){var t=this._elMap;t.each(function(e){dv(e,t)}),this._elMap=R()},dispose:function(){this._clear()}});var KP=Fs({type:"legend.plain",dependencies:["series"],layoutMode:{type:"box",ignoreSize:!0},init:function(t,e,i){this.mergeDefaultAndTheme(t,i),t.selected=t.selected||{}},mergeOption:function(t){KP.superCall(this,"mergeOption",t)},optionUpdated:function(){this._updateData(this.ecModel);var t=this._data;if(t[0]&&"single"===this.get("selectedMode")){for(var e=!1,i=0;i=0},defaultOption:{zlevel:0,z:4,show:!0,orient:"horizontal",left:"center",top:0,align:"auto",backgroundColor:"rgba(0,0,0,0)",borderColor:"#ccc",borderRadius:0,borderWidth:0,padding:5,itemGap:10,itemWidth:25,itemHeight:14,inactiveColor:"#ccc",textStyle:{color:"#333"},selectedMode:!0,tooltip:{show:!1}}});Es("legendToggleSelect","legendselectchanged",v(xv,"toggleSelected")),Es("legendSelect","legendselected",v(xv,"select")),Es("legendUnSelect","legendunselected",v(xv,"unSelect"));var $P=v,JP=d,QP=tb,tN=Ws({type:"legend.plain",newlineDisabled:!1,init:function(){this.group.add(this._contentGroup=new QP),this._backgroundEl,this._isFirstRender=!0},getContentGroup:function(){return this._contentGroup},render:function(t,e,i){var n=this._isFirstRender;if(this._isFirstRender=!1,this.resetInner(),t.get("show",!0)){var o=t.get("align");o&&"auto"!==o||(o="right"===t.get("left")&&"vertical"===t.get("orient")?"right":"left"),this.renderInner(o,t,e,i);var a=t.getBoxLayoutParams(),s={width:i.getWidth(),height:i.getHeight()},l=t.get("padding"),u=ca(a,s,l),h=this.layoutInner(t,o,u,n),c=ca(r({width:h.width,height:h.height},a),s,l);this.group.attr("position",[c.x-h.x,c.y-h.y]),this.group.add(this._backgroundEl=wv(h,t))}},resetInner:function(){this.getContentGroup().removeAll(),this._backgroundEl&&this.group.remove(this._backgroundEl)},renderInner:function(t,e,i,n){var o=this.getContentGroup(),a=R(),r=e.get("selectedMode"),s=[];i.eachRawSeries(function(t){!t.get("legendHoverLink")&&s.push(t.id)}),JP(e.getData(),function(l,u){var h=l.get("name");if(this.newlineDisabled||""!==h&&"\n"!==h){var c=i.getSeriesByName(h)[0];if(!a.get(h))if(c){var d=c.getData(),f=d.getVisual("color");"function"==typeof f&&(f=f(c.getDataParams(0)));var p=d.getVisual("legendSymbol")||"roundRect",g=d.getVisual("symbol");this._createItem(h,u,l,e,p,g,t,f,r).on("click",$P(bv,h,n)).on("mouseover",$P(Sv,c.name,null,n,s)).on("mouseout",$P(Mv,c.name,null,n,s)),a.set(h,!0)}else i.eachRawSeries(function(i){if(!a.get(h)&&i.legendDataProvider){var o=i.legendDataProvider(),c=o.indexOfName(h);if(c<0)return;var d=o.getItemVisual(c,"color");this._createItem(h,u,l,e,"roundRect",null,t,d,r).on("click",$P(bv,h,n)).on("mouseover",$P(Sv,null,h,n,s)).on("mouseout",$P(Mv,null,h,n,s)),a.set(h,!0)}},this)}else o.add(new QP({newline:!0}))},this)},_createItem:function(t,e,i,n,o,r,s,l,u){var h=n.get("itemWidth"),c=n.get("itemHeight"),d=n.get("inactiveColor"),f=n.get("symbolKeepAspect"),p=n.isSelected(t),g=new QP,m=i.getModel("textStyle"),v=i.get("icon"),y=i.getModel("tooltip"),x=y.parentModel;if(o=v||o,g.add(Jl(o,0,0,h,c,p?l:d,null==f||f)),!v&&r&&(r!==o||"none"===r)){var _=.8*c;"none"===r&&(r="circle"),g.add(Jl(r,(h-_)/2,(c-_)/2,_,_,p?l:d,null==f||f))}var w="left"===s?h+5:-5,b=s,S=n.get("formatter"),M=t;"string"==typeof S&&S?M=S.replace("{name}",null!=t?t:""):"function"==typeof S&&(M=S(t)),g.add(new rM({style:mo({},m,{text:M,x:w,y:c/2,textFill:p?m.getTextColor():d,textAlign:b,textVerticalAlign:"middle"})}));var I=new yM({shape:g.getBoundingRect(),invisible:!0,tooltip:y.get("show")?a({content:t,formatter:x.get("formatter",!0)||function(){return t},formatterParams:{componentType:"legend",legendIndex:n.componentIndex,name:t,$vars:["name"]}},y.option):null});return g.add(I),g.eachChild(function(t){t.silent=!0}),I.silent=!u,this.getContentGroup().add(g),fo(g),g.__legendDataIndex=e,g},layoutInner:function(t,e,i){var n=this.getContentGroup();aI(t.get("orient"),n,t.get("itemGap"),i.width,i.height);var o=n.getBoundingRect();return n.attr("position",[-o.x,-o.y]),this.group.getBoundingRect()},remove:function(){this.getContentGroup().removeAll(),this._isFirstRender=!0}});Os(function(t){var e=t.findComponents({mainType:"legend"});e&&e.length&&t.filterSeries(function(t){for(var i=0;ii[l],p=[-c.x,-c.y];n||(p[s]=o.position[s]);var g=[0,0],m=[-d.x,-d.y],v=A(t.get("pageButtonGap",!0),t.get("itemGap",!0));f&&("end"===t.get("pageButtonPosition",!0)?m[s]+=i[l]-d[l]:g[s]+=d[l]+v),m[1-s]+=c[u]/2-d[u]/2,o.attr("position",p),a.attr("position",g),r.attr("position",m);var y=this.group.getBoundingRect();if((y={x:0,y:0})[l]=f?i[l]:c[l],y[u]=Math.max(c[u],d[u]),y[h]=Math.min(0,d[h]+m[1-s]),a.__rectSize=i[l],f){var x={x:0,y:0};x[l]=Math.max(i[l]-d[l]-v,0),x[u]=y[u],a.setClipPath(new yM({shape:x})),a.__rectSize=x[l]}else r.eachChild(function(t){t.attr({invisible:!0,silent:!0})});var _=this._getPageInfo(t);return null!=_.pageIndex&&Io(o,{position:_.contentPosition},!!f&&t),this._updatePageInfoView(t,_),y},_pageGo:function(t,e,i){var n=this._getPageInfo(e)[t];null!=n&&i.dispatchAction({type:"legendScroll",scrollDataIndex:n,legendId:e.id})},_updatePageInfoView:function(t,e){var i=this._controllerGroup;d(["pagePrev","pageNext"],function(n){var o=null!=e[n+"DataIndex"],a=i.childOfName(n);a&&(a.setStyle("fill",o?t.get("pageIconColor",!0):t.get("pageIconInactiveColor",!0)),a.cursor=o?"pointer":"default")});var n=i.childOfName("pageText"),o=t.get("pageFormatter"),a=e.pageIndex,r=null!=a?a+1:0,s=e.pageCount;n&&o&&n.setStyle("text",_(o)?o.replace("{current}",r).replace("{total}",s):o({current:r,total:s}))},_getPageInfo:function(t){function e(t){if(t){var e=t.getBoundingRect(),i=e[l]+t.position[r];return{s:i,e:i+e[s],i:t.__legendDataIndex}}}function i(t,e){return t.e>=e&&t.s<=e+a}var n=t.get("scrollDataIndex",!0),o=this.getContentGroup(),a=this._containerGroup.__rectSize,r=t.getOrient().index,s=nN[r],l=oN[r],u=this._findTargetItemIndex(n),h=o.children(),c=h[u],d=h.length,f=d?1:0,p={contentPosition:o.position.slice(),pageCount:f,pageIndex:f-1,pagePrevDataIndex:null,pageNextDataIndex:null};if(!c)return p;var g=e(c);p.contentPosition[r]=-g.s;for(var m=u+1,v=g,y=g,x=null;m<=d;++m)(!(x=e(h[m]))&&y.e>v.s+a||x&&!i(x,v.s))&&(v=y.i>v.i?y:x)&&(null==p.pageNextDataIndex&&(p.pageNextDataIndex=v.i),++p.pageCount),y=x;for(var m=u-1,v=g,y=g,x=null;m>=-1;--m)(x=e(h[m]))&&i(y,x.s)||!(v.i=0;){var r=o.indexOf("|}"),s=o.substr(a+"{marker".length,r-a-"{marker".length);s.indexOf("sub")>-1?n["marker"+s]={textWidth:4,textHeight:4,textBorderRadius:2,textBackgroundColor:e[s],textOffset:[3,0]}:n["marker"+s]={textWidth:10,textHeight:10,textBorderRadius:5,textBackgroundColor:e[s]},a=(o=o.substr(r+1)).indexOf("{marker")}this.el=new rM({style:{rich:n,text:t,textLineHeight:20,textBackgroundColor:i.get("backgroundColor"),textBorderRadius:i.get("borderRadius"),textFill:i.get("textStyle.color"),textPadding:i.get("padding")},z:i.get("z")}),this._zr.add(this.el);var l=this;this.el.on("mouseover",function(){l._enterable&&(clearTimeout(l._hideTimeout),l._show=!0),l._inContent=!0}),this.el.on("mouseout",function(){l._enterable&&l._show&&l.hideLater(l._hideDelay),l._inContent=!1})},setEnterable:function(t){this._enterable=t},getSize:function(){var t=this.el.getBoundingRect();return[t.width,t.height]},moveTo:function(t,e){this.el&&this.el.attr("position",[t,e])},hide:function(){this.el.hide(),this._show=!1},hideLater:function(t){!this._show||this._inContent&&this._enterable||(t?(this._hideDelay=t,this._show=!1,this._hideTimeout=setTimeout(m(this.hide,this),t)):this.hide())},isShow:function(){return this._show},getOuterSize:function(){return this.getSize()}};var uN=m,hN=d,cN=Vo,dN=new yM({shape:{x:-1,y:-1,width:2,height:2}});Ws({type:"tooltip",init:function(t,e){if(!U_.node){var i=t.getComponent("tooltip").get("renderMode");this._renderMode=Hi(i);var n;"html"===this._renderMode?(n=new Cv(e.getDom(),e),this._newLine="
      "):(n=new Lv(e),this._newLine="\n"),this._tooltipContent=n}},render:function(t,e,i){if(!U_.node){this.group.removeAll(),this._tooltipModel=t,this._ecModel=e,this._api=i,this._lastDataByCoordSys=null,this._alwaysShowContent=t.get("alwaysShowContent");var n=this._tooltipContent;n.update(),n.setEnterable(t.get("enterable")),this._initGlobalListener(),this._keepShow()}},_initGlobalListener:function(){var t=this._tooltipModel.get("triggerOn");um("itemTooltip",this._api,uN(function(e,i,n){"none"!==t&&(t.indexOf(e)>=0?this._tryShow(i,n):"leave"===e&&this._hide(n))},this))},_keepShow:function(){var t=this._tooltipModel,e=this._ecModel,i=this._api;if(null!=this._lastX&&null!=this._lastY&&"none"!==t.get("triggerOn")){var n=this;clearTimeout(this._refreshUpdateTimeout),this._refreshUpdateTimeout=setTimeout(function(){n.manuallyShowTip(t,e,i,{x:n._lastX,y:n._lastY})})}},manuallyShowTip:function(t,e,i,n){if(n.from!==this.uid&&!U_.node){var o=Pv(n,i);this._ticket="";var a=n.dataByCoordSys;if(n.tooltip&&null!=n.x&&null!=n.y){var r=dN;r.position=[n.x,n.y],r.update(),r.tooltip=n.tooltip,this._tryShow({offsetX:n.x,offsetY:n.y,target:r},o)}else if(a)this._tryShow({offsetX:n.x,offsetY:n.y,position:n.position,event:{},dataByCoordSys:n.dataByCoordSys,tooltipOption:n.tooltipOption},o);else if(null!=n.seriesIndex){if(this._manuallyAxisShowTip(t,e,i,n))return;var s=xP(n,e),l=s.point[0],u=s.point[1];null!=l&&null!=u&&this._tryShow({offsetX:l,offsetY:u,position:n.position,target:s.el,event:{}},o)}else null!=n.x&&null!=n.y&&(i.dispatchAction({type:"updateAxisPointer",x:n.x,y:n.y}),this._tryShow({offsetX:n.x,offsetY:n.y,position:n.position,target:i.getZr().findHover(n.x,n.y).target,event:{}},o))}},manuallyHideTip:function(t,e,i,n){var o=this._tooltipContent;!this._alwaysShowContent&&this._tooltipModel&&o.hideLater(this._tooltipModel.get("hideDelay")),this._lastX=this._lastY=null,n.from!==this.uid&&this._hide(Pv(n,i))},_manuallyAxisShowTip:function(t,e,i,n){var o=n.seriesIndex,a=n.dataIndex,r=e.getComponent("axisPointer").coordSysAxesInfo;if(null!=o&&null!=a&&null!=r){var s=e.getSeriesByIndex(o);if(s&&"axis"===(t=kv([s.getData().getItemModel(a),s,(s.coordinateSystem||{}).model,t])).get("trigger"))return i.dispatchAction({type:"updateAxisPointer",seriesIndex:o,dataIndex:a,position:n.position}),!0}},_tryShow:function(t,e){var i=t.target;if(this._tooltipModel){this._lastX=t.offsetX,this._lastY=t.offsetY;var n=t.dataByCoordSys;n&&n.length?this._showAxisTooltip(n,t):i&&null!=i.dataIndex?(this._lastDataByCoordSys=null,this._showSeriesItemTooltip(t,i,e)):i&&i.tooltip?(this._lastDataByCoordSys=null,this._showComponentItemTooltip(t,i,e)):(this._lastDataByCoordSys=null,this._hide(e))}},_showOrMove:function(t,e){var i=t.get("showDelay");e=m(e,this),clearTimeout(this._showTimout),i>0?this._showTimout=setTimeout(e,i):e()},_showAxisTooltip:function(t,e){var i=this._ecModel,o=this._tooltipModel,a=[e.offsetX,e.offsetY],r=[],s=[],l=kv([e.tooltipOption,o]),u=this._renderMode,h=this._newLine,c={};hN(t,function(t){hN(t.dataByAxis,function(t){var e=i.getComponent(t.axisDim+"Axis",t.axisIndex),o=t.value,a=[];if(e&&null!=o){var l=Im(o,e.axis,i,t.seriesDataIndices,t.valueLabelOpt);d(t.seriesDataIndices,function(r){var h=i.getSeriesByIndex(r.seriesIndex),d=r.dataIndexInside,f=h&&h.getDataParams(d);if(f.axisDim=t.axisDim,f.axisIndex=t.axisIndex,f.axisType=t.axisType,f.axisId=t.axisId,f.axisValue=Xl(e.axis,o),f.axisValueLabel=l,f){s.push(f);var p,g=h.formatTooltip(d,!0,null,u);if(w(g)){p=g.html;var m=g.markers;n(c,m)}else p=g;a.push(p)}});var f=l;"html"!==u?r.push(a.join(h)):r.push((f?ia(f)+h:"")+a.join(h))}})},this),r.reverse(),r=r.join(this._newLine+this._newLine);var f=e.position;this._showOrMove(l,function(){this._updateContentNotChangedOnAxis(t)?this._updatePosition(l,f,a[0],a[1],this._tooltipContent,s):this._showTooltipContent(l,r,s,Math.random(),a[0],a[1],f,void 0,c)})},_showSeriesItemTooltip:function(t,e,i){var n=this._ecModel,o=e.seriesIndex,a=n.getSeriesByIndex(o),r=e.dataModel||a,s=e.dataIndex,l=e.dataType,u=r.getData(),h=kv([u.getItemModel(s),r,a&&(a.coordinateSystem||{}).model,this._tooltipModel]),c=h.get("trigger");if(null==c||"item"===c){var d,f,p=r.getDataParams(s,l),g=r.formatTooltip(s,!1,l,this._renderMode);w(g)?(d=g.html,f=g.markers):(d=g,f=null);var m="item_"+r.name+"_"+s;this._showOrMove(h,function(){this._showTooltipContent(h,d,p,m,t.offsetX,t.offsetY,t.position,t.target,f)}),i({type:"showTip",dataIndexInside:s,dataIndex:u.getRawIndex(s),seriesIndex:o,from:this.uid})}},_showComponentItemTooltip:function(t,e,i){var n=e.tooltip;if("string"==typeof n){var o=n;n={content:o,formatter:o}}var a=new No(n,this._tooltipModel,this._ecModel),r=a.get("content"),s=Math.random();this._showOrMove(a,function(){this._showTooltipContent(a,r,a.get("formatterParams")||{},s,t.offsetX,t.offsetY,t.position,e)}),i({type:"showTip",from:this.uid})},_showTooltipContent:function(t,e,i,n,o,a,r,s,l){if(this._ticket="",t.get("showContent")&&t.get("show")){var u=this._tooltipContent,h=t.get("formatter");r=r||t.get("position");var c=e;if(h&&"string"==typeof h)c=na(h,i,!0);else if("function"==typeof h){var d=uN(function(e,n){e===this._ticket&&(u.setContent(n,l,t),this._updatePosition(t,r,o,a,u,i,s))},this);this._ticket=n,c=h(i,n,d)}u.setContent(c,l,t),u.show(t),this._updatePosition(t,r,o,a,u,i,s)}},_updatePosition:function(t,e,i,n,o,a,r){var s=this._api.getWidth(),l=this._api.getHeight();e=e||t.get("position");var u=o.getSize(),h=t.get("align"),c=t.get("verticalAlign"),d=r&&r.getBoundingRect().clone();if(r&&d.applyTransform(r.transform),"function"==typeof e&&(e=e([i,n],a,o.el,d,{viewSize:[s,l],contentSize:u.slice()})),y(e))i=cN(e[0],s),n=cN(e[1],l);else if(w(e)){e.width=u[0],e.height=u[1];var f=ca(e,{width:s,height:l});i=f.x,n=f.y,h=null,c=null}else"string"==typeof e&&r?(i=(p=Ev(e,d,u))[0],n=p[1]):(i=(p=Nv(i,n,o,s,l,h?null:20,c?null:20))[0],n=p[1]);if(h&&(i-=Rv(h)?u[0]/2:"right"===h?u[0]:0),c&&(n-=Rv(c)?u[1]/2:"bottom"===c?u[1]:0),t.get("confine")){var p=Ov(i,n,o,s,l);i=p[0],n=p[1]}o.moveTo(i,n)},_updateContentNotChangedOnAxis:function(t){var e=this._lastDataByCoordSys,i=!!e&&e.length===t.length;return i&&hN(e,function(e,n){var o=e.dataByAxis||{},a=(t[n]||{}).dataByAxis||[];(i&=o.length===a.length)&&hN(o,function(t,e){var n=a[e]||{},o=t.seriesDataIndices||[],r=n.seriesDataIndices||[];(i&=t.value===n.value&&t.axisType===n.axisType&&t.axisId===n.axisId&&o.length===r.length)&&hN(o,function(t,e){var n=r[e];i&=t.seriesIndex===n.seriesIndex&&t.dataIndex===n.dataIndex})})}),this._lastDataByCoordSys=t,!!i},_hide:function(t){this._lastDataByCoordSys=null,t({type:"hideTip",from:this.uid})},dispose:function(t,e){U_.node||(this._tooltipContent.hide(),gm("itemTooltip",e))}}),Es({type:"showTip",event:"showTip",update:"tooltip:manuallyShowTip"},function(){}),Es({type:"hideTip",event:"hideTip",update:"tooltip:manuallyHideTip"},function(){}),Gv.prototype={constructor:Gv,pointToData:function(t,e){return this.polar.pointToData(t,e)["radius"===this.dim?0:1]},dataToRadius:aD.prototype.dataToCoord,radiusToData:aD.prototype.coordToData},u(Gv,aD);var fN=Bi();Fv.prototype={constructor:Fv,pointToData:function(t,e){return this.polar.pointToData(t,e)["radius"===this.dim?0:1]},dataToAngle:aD.prototype.dataToCoord,angleToData:aD.prototype.coordToData,calculateCategoryInterval:function(){var t=this,e=t.getLabelModel(),i=t.scale,n=i.getExtent(),o=i.count();if(n[1]-n[0]<1)return 0;var a=n[0],r=t.dataToCoord(a+1)-t.dataToCoord(a),s=Math.abs(r),l=ke(a,e.getFont(),"center","top"),u=Math.max(l.height,7)/s;isNaN(u)&&(u=1/0);var h=Math.max(0,Math.floor(u)),c=fN(t.model),d=c.lastAutoInterval,f=c.lastTickCount;return null!=d&&null!=f&&Math.abs(d-h)<=1&&Math.abs(f-o)<=1&&d>h?h=d:(c.lastTickCount=o,c.lastAutoInterval=h),h}},u(Fv,aD);var pN=function(t){this.name=t||"",this.cx=0,this.cy=0,this._radiusAxis=new Gv,this._angleAxis=new Fv,this._radiusAxis.polar=this._angleAxis.polar=this};pN.prototype={type:"polar",axisPointerEnabled:!0,constructor:pN,dimensions:["radius","angle"],model:null,containPoint:function(t){var e=this.pointToCoord(t);return this._radiusAxis.contain(e[0])&&this._angleAxis.contain(e[1])},containData:function(t){return this._radiusAxis.containData(t[0])&&this._angleAxis.containData(t[1])},getAxis:function(t){return this["_"+t+"Axis"]},getAxes:function(){return[this._radiusAxis,this._angleAxis]},getAxesByScale:function(t){var e=[],i=this._angleAxis,n=this._radiusAxis;return i.scale.type===t&&e.push(i),n.scale.type===t&&e.push(n),e},getAngleAxis:function(){return this._angleAxis},getRadiusAxis:function(){return this._radiusAxis},getOtherAxis:function(t){var e=this._angleAxis;return t===e?this._radiusAxis:e},getBaseAxis:function(){return this.getAxesByScale("ordinal")[0]||this.getAxesByScale("time")[0]||this.getAngleAxis()},getTooltipAxes:function(t){var e=null!=t&&"auto"!==t?this.getAxis(t):this.getBaseAxis();return{baseAxes:[e],otherAxes:[this.getOtherAxis(e)]}},dataToPoint:function(t,e){return this.coordToPoint([this._radiusAxis.dataToRadius(t[0],e),this._angleAxis.dataToAngle(t[1],e)])},pointToData:function(t,e){var i=this.pointToCoord(t);return[this._radiusAxis.radiusToData(i[0],e),this._angleAxis.angleToData(i[1],e)]},pointToCoord:function(t){var e=t[0]-this.cx,i=t[1]-this.cy,n=this.getAngleAxis(),o=n.getExtent(),a=Math.min(o[0],o[1]),r=Math.max(o[0],o[1]);n.inverse?a=r-360:r=a+360;var s=Math.sqrt(e*e+i*i);e/=s,i/=s;for(var l=Math.atan2(-i,e)/Math.PI*180,u=lr;)l+=360*u;return[s,l]},coordToPoint:function(t){var e=t[0],i=t[1]/180*Math.PI;return[Math.cos(i)*e+this.cx,-Math.sin(i)*e+this.cy]}};var gN=lI.extend({type:"polarAxis",axis:null,getCoordSysModel:function(){return this.ecModel.queryComponents({mainType:"polar",index:this.option.polarIndex,id:this.option.polarId})[0]}});n(gN.prototype,UA);var mN={angle:{startAngle:90,clockwise:!0,splitNumber:12,axisLabel:{rotate:!1}},radius:{splitNumber:5}};ED("angle",gN,Wv,mN.angle),ED("radius",gN,Wv,mN.radius),Fs({type:"polar",dependencies:["polarAxis","angleAxis"],coordinateSystem:null,findAxisModel:function(t){var e;return this.ecModel.eachComponent(t,function(t){t.getCoordSysModel()===this&&(e=t)},this),e},defaultOption:{zlevel:0,z:0,center:["50%","50%"],radius:"80%"}});var vN={dimensions:pN.prototype.dimensions,create:function(t,e){var i=[];return t.eachComponent("polar",function(t,n){var o=new pN(n);o.update=Zv;var a=o.getRadiusAxis(),r=o.getAngleAxis(),s=t.findAxisModel("radiusAxis"),l=t.findAxisModel("angleAxis");Uv(a,s),Uv(r,l),Hv(o,t,e),i.push(o),t.coordinateSystem=o,o.model=t}),t.eachSeries(function(e){if("polar"===e.get("coordinateSystem")){var i=t.queryComponents({mainType:"polar",index:e.get("polarIndex"),id:e.get("polarId")})[0];e.coordinateSystem=i.coordinateSystem}}),i}};Fa.register("polar",vN);var yN=["axisLine","axisLabel","axisTick","splitLine","splitArea"];XD.extend({type:"angleAxis",axisPointerClass:"PolarAxisPointer",render:function(t,e){if(this.group.removeAll(),t.get("show")){var n=t.axis,o=n.polar,a=o.getRadiusAxis().getExtent(),r=n.getTicksCoords(),s=f(n.getViewLabels(),function(t){return(t=i(t)).coord=n.dataToCoord(t.tickValue),t});Yv(s),Yv(r),d(yN,function(e){!t.get(e+".show")||n.scale.isBlank()&&"axisLine"!==e||this["_"+e](t,o,r,a,s)},this)}},_axisLine:function(t,e,i,n){var o=t.getModel("axisLine.lineStyle"),a=new sM({shape:{cx:e.cx,cy:e.cy,r:n[jv(e)]},style:o.getLineStyle(),z2:1,silent:!0});a.style.fill=null,this.group.add(a)},_axisTick:function(t,e,i,n){var o=t.getModel("axisTick"),a=(o.get("inside")?-1:1)*o.get("length"),s=n[jv(e)],l=f(i,function(t){return new _M({shape:Xv(e,[s,s+a],t.coord)})});this.group.add(OM(l,{style:r(o.getModel("lineStyle").getLineStyle(),{stroke:t.get("axisLine.lineStyle.color")})}))},_axisLabel:function(t,e,i,n,o){var a=t.getCategories(!0),r=t.getModel("axisLabel"),s=r.get("margin");d(o,function(i,o){var l=r,u=i.tickValue,h=n[jv(e)],c=e.coordToPoint([h+s,i.coord]),d=e.cx,f=e.cy,p=Math.abs(c[0]-d)/h<.3?"center":c[0]>d?"left":"right",g=Math.abs(c[1]-f)/h<.3?"middle":c[1]>f?"top":"bottom";a&&a[u]&&a[u].textStyle&&(l=new No(a[u].textStyle,r,r.ecModel));var m=new rM({silent:!0});this.group.add(m),mo(m.style,l,{x:c[0],y:c[1],textFill:l.getTextColor()||t.get("axisLine.lineStyle.color"),text:i.formattedLabel,textAlign:p,textVerticalAlign:g})},this)},_splitLine:function(t,e,i,n){var o=t.getModel("splitLine").getModel("lineStyle"),a=o.get("color"),s=0;a=a instanceof Array?a:[a];for(var l=[],u=0;u=0?"p":"n",M=y;v&&(n[r][b]||(n[r][b]={p:y,n:y}),M=n[r][b][S]);var I,T,A,D;if("radius"===h.dim){var C=h.dataToRadius(w)-y,L=a.dataToAngle(b);Math.abs(C)=0},kN.findTargetInfo=function(t,e){for(var i=this._targetInfoList,n=dy(e,t),o=0;o=0||AN(n,t.getAxis("y").model)>=0)&&a.push(t)}),e.push({panelId:"grid--"+t.id,gridModel:t,coordSysModel:t,coordSys:a[0],coordSyses:a,getPanelRect:ON.grid,xAxisDeclared:r[t.id],yAxisDeclared:s[t.id]})}))},geo:function(t,e){TN(t.geoModels,function(t){var i=t.coordinateSystem;e.push({panelId:"geo--"+t.id,geoModel:t,coordSysModel:t,coordSys:i,coordSyses:[i],getPanelRect:ON.geo})})}},NN=[function(t,e){var i=t.xAxisModel,n=t.yAxisModel,o=t.gridModel;return!o&&i&&(o=i.axis.grid.model),!o&&n&&(o=n.axis.grid.model),o&&o===e.gridModel},function(t,e){var i=t.geoModel;return i&&i===e.geoModel}],ON={grid:function(){return this.coordSys.grid.getRect().clone()},geo:function(){var t=this.coordSys,e=t.getBoundingRect().clone();return e.applyTransform(Ao(t)),e}},EN={lineX:DN(fy,0),lineY:DN(fy,1),rect:function(t,e,i){var n=e[CN[t]]([i[0][0],i[1][0]]),o=e[CN[t]]([i[0][1],i[1][1]]),a=[cy([n[0],o[0]]),cy([n[1],o[1]])];return{values:a,xyMinMax:a}},polygon:function(t,e,i){var n=[[1/0,-1/0],[1/0,-1/0]];return{values:f(i,function(i){var o=e[CN[t]](i);return n[0][0]=Math.min(n[0][0],o[0]),n[1][0]=Math.min(n[1][0],o[1]),n[0][1]=Math.max(n[0][1],o[0]),n[1][1]=Math.max(n[1][1],o[1]),o}),xyMinMax:n}}},RN={lineX:DN(py,0),lineY:DN(py,1),rect:function(t,e,i){return[[t[0][0]-i[0]*e[0][0],t[0][1]-i[0]*e[0][1]],[t[1][0]-i[1]*e[1][0],t[1][1]-i[1]*e[1][1]]]},polygon:function(t,e,i){return f(t,function(t,n){return[t[0]-i[0]*e[n][0],t[1]-i[1]*e[n][1]]})}},zN=["inBrush","outOfBrush"],BN="__ecBrushSelect",VN="__ecInBrushSelectEvent",GN=VT.VISUAL.BRUSH;zs(GN,function(t,e,i){t.eachComponent({mainType:"brush"},function(e){i&&"takeGlobalCursor"===i.type&&e.setBrushOption("brush"===i.key?i.brushOption:{brushType:!1}),(e.brushTargetManager=new hy(e.option,t)).setInputRanges(e.areas,t)})}),Bs(GN,function(t,e,n){var o,a,s=[];t.eachComponent({mainType:"brush"},function(e,n){function l(t){return"all"===m||v[t]}function u(t){return!!t.length}function h(t,e){var i=t.coordinateSystem;w|=i.hasAxisBrushed(),l(e)&&i.eachActiveState(t.getData(),function(t,e){"active"===t&&(x[e]=1)})}function c(i,n,o){var a=_y(i);if(a&&!wy(e,n)&&(d(b,function(n){a[n.brushType]&&e.brushTargetManager.controlSeries(n,i,t)&&o.push(n),w|=u(o)}),l(n)&&u(o))){var r=i.getData();r.each(function(t){xy(a,o,r,t)&&(x[t]=1)})}}var p={brushId:e.id,brushIndex:n,brushName:e.name,areas:i(e.areas),selected:[]};s.push(p);var g=e.option,m=g.brushLink,v=[],x=[],_=[],w=0;n||(o=g.throttleType,a=g.throttleDelay);var b=f(e.areas,function(t){return by(r({boundingRect:FN[t.brushType](t)},t))}),S=ty(e.option,zN,function(t){t.mappingMethod="fixed"});y(m)&&d(m,function(t){v[t]=1}),t.eachSeries(function(t,e){var i=_[e]=[];"parallel"===t.subType?h(t,e):c(t,e,i)}),t.eachSeries(function(t,e){var i={seriesId:t.id,seriesIndex:e,seriesName:t.name,dataIndex:[]};p.selected.push(i);var n=_y(t),o=_[e],a=t.getData(),r=l(e)?function(t){return x[t]?(i.dataIndex.push(a.getRawIndex(t)),"inBrush"):"outOfBrush"}:function(t){return xy(n,o,a,t)?(i.dataIndex.push(a.getRawIndex(t)),"inBrush"):"outOfBrush"};(l(e)?w:u(o))&&iy(zN,S,a,r)})}),vy(e,o,a,s,n)});var FN={lineX:B,lineY:B,rect:function(t){return Sy(t.range)},polygon:function(t){for(var e,i=t.range,n=0,o=i.length;ne[0][1]&&(e[0][1]=a[0]),a[1]e[1][1]&&(e[1][1]=a[1])}return e&&Sy(e)}},WN=["#ddd"];Fs({type:"brush",dependencies:["geo","grid","xAxis","yAxis","parallel","series"],defaultOption:{toolbox:null,brushLink:null,seriesIndex:"all",geoIndex:null,xAxisIndex:null,yAxisIndex:null,brushType:"rect",brushMode:"single",transformable:!0,brushStyle:{borderWidth:1,color:"rgba(120,140,180,0.3)",borderColor:"rgba(120,140,180,0.8)"},throttleType:"fixRate",throttleDelay:0,removeOnClick:!0,z:1e4},areas:[],brushType:null,brushOption:{},coordInfoList:[],optionUpdated:function(t,e){var i=this.option;!e&&ey(i,t,["inBrush","outOfBrush"]);var n=i.inBrush=i.inBrush||{};i.outOfBrush=i.outOfBrush||{color:WN},n.hasOwnProperty("liftZ")||(n.liftZ=5)},setAreas:function(t){t&&(this.areas=f(t,function(t){return My(this.option,t)},this))},setBrushOption:function(t){this.brushOption=My(this.option,t),this.brushType=this.brushOption.brushType}});Ws({type:"brush",init:function(t,e){this.ecModel=t,this.api=e,this.model,(this._brushController=new zf(e.getZr())).on("brush",m(this._onBrush,this)).mount()},render:function(t){return this.model=t,Iy.apply(this,arguments)},updateTransform:Iy,updateView:Iy,dispose:function(){this._brushController.dispose()},_onBrush:function(t,e){var n=this.model.id;this.model.brushTargetManager.setOutputRanges(t,this.ecModel),(!e.isEnd||e.removeOnClick)&&this.api.dispatchAction({type:"brush",brushId:n,areas:i(t),$from:n})}}),Es({type:"brush",event:"brush"},function(t,e){e.eachComponent({mainType:"brush",query:t},function(e){e.setAreas(t.areas)})}),Es({type:"brushSelect",event:"brushSelected",update:"none"},function(){});var HN={},ZN=rT.toolbox.brush;Dy.defaultOption={show:!0,type:["rect","polygon","lineX","lineY","keep","clear"],icon:{rect:"M7.3,34.7 M0.4,10V-0.2h9.8 M89.6,10V-0.2h-9.8 M0.4,60v10.2h9.8 M89.6,60v10.2h-9.8 M12.3,22.4V10.5h13.1 M33.6,10.5h7.8 M49.1,10.5h7.8 M77.5,22.4V10.5h-13 M12.3,31.1v8.2 M77.7,31.1v8.2 M12.3,47.6v11.9h13.1 M33.6,59.5h7.6 M49.1,59.5 h7.7 M77.5,47.6v11.9h-13",polygon:"M55.2,34.9c1.7,0,3.1,1.4,3.1,3.1s-1.4,3.1-3.1,3.1 s-3.1-1.4-3.1-3.1S53.5,34.9,55.2,34.9z M50.4,51c1.7,0,3.1,1.4,3.1,3.1c0,1.7-1.4,3.1-3.1,3.1c-1.7,0-3.1-1.4-3.1-3.1 C47.3,52.4,48.7,51,50.4,51z M55.6,37.1l1.5-7.8 M60.1,13.5l1.6-8.7l-7.8,4 M59,19l-1,5.3 M24,16.1l6.4,4.9l6.4-3.3 M48.5,11.6 l-5.9,3.1 M19.1,12.8L9.7,5.1l1.1,7.7 M13.4,29.8l1,7.3l6.6,1.6 M11.6,18.4l1,6.1 M32.8,41.9 M26.6,40.4 M27.3,40.2l6.1,1.6 M49.9,52.1l-5.6-7.6l-4.9-1.2",lineX:"M15.2,30 M19.7,15.6V1.9H29 M34.8,1.9H40.4 M55.3,15.6V1.9H45.9 M19.7,44.4V58.1H29 M34.8,58.1H40.4 M55.3,44.4 V58.1H45.9 M12.5,20.3l-9.4,9.6l9.6,9.8 M3.1,29.9h16.5 M62.5,20.3l9.4,9.6L62.3,39.7 M71.9,29.9H55.4",lineY:"M38.8,7.7 M52.7,12h13.2v9 M65.9,26.6V32 M52.7,46.3h13.2v-9 M24.9,12H11.8v9 M11.8,26.6V32 M24.9,46.3H11.8v-9 M48.2,5.1l-9.3-9l-9.4,9.2 M38.9-3.9V12 M48.2,53.3l-9.3,9l-9.4-9.2 M38.9,62.3V46.4",keep:"M4,10.5V1h10.3 M20.7,1h6.1 M33,1h6.1 M55.4,10.5V1H45.2 M4,17.3v6.6 M55.6,17.3v6.6 M4,30.5V40h10.3 M20.7,40 h6.1 M33,40h6.1 M55.4,30.5V40H45.2 M21,18.9h62.9v48.6H21V18.9z",clear:"M22,14.7l30.9,31 M52.9,14.7L22,45.7 M4.7,16.8V4.2h13.1 M26,4.2h7.8 M41.6,4.2h7.8 M70.3,16.8V4.2H57.2 M4.7,25.9v8.6 M70.3,25.9v8.6 M4.7,43.2v12.6h13.1 M26,55.8h7.8 M41.6,55.8h7.8 M70.3,43.2v12.6H57.2"},title:i(ZN.title)};var UN=Dy.prototype;UN.render=UN.updateView=function(t,e,i){var n,o,a;e.eachComponent({mainType:"brush"},function(t){n=t.brushType,o=t.brushOption.brushMode||"single",a|=t.areas.length}),this._brushType=n,this._brushMode=o,d(t.get("type",!0),function(e){t.setIconStatus(e,("keep"===e?"multiple"===o:"clear"===e?a:e===n)?"emphasis":"normal")})},UN.getIcons=function(){var t=this.model,e=t.get("icon",!0),i={};return d(t.get("type",!0),function(t){e[t]&&(i[t]=e[t])}),i},UN.onclick=function(t,e,i){var n=this._brushType,o=this._brushMode;"clear"===i?(e.dispatchAction({type:"axisAreaSelect",intervals:[]}),e.dispatchAction({type:"brush",command:"clear",areas:[]})):e.dispatchAction({type:"takeGlobalCursor",key:"brush",brushOption:{brushType:"keep"===i?n:n!==i&&i,brushMode:"keep"===i?"multiple"===o?"single":"multiple":o}})},Ty("brush",Dy),Ns(function(t,e){var i=t&&t.brush;if(y(i)||(i=i?[i]:[]),i.length){var n=[];d(i,function(t){var e=t.hasOwnProperty("toolbox")?t.toolbox:[];e instanceof Array&&(n=n.concat(e))});var o=t&&t.toolbox;y(o)&&(o=o[0]),o||(o={feature:{}},t.toolbox=[o]);var a=o.feature||(o.feature={}),r=a.brush||(a.brush={}),s=r.type||(r.type=[]);s.push.apply(s,n),Jv(s),e&&!s.length&&s.push.apply(s,SN)}});Cy.prototype={constructor:Cy,type:"calendar",dimensions:["time","value"],getDimensionsInfo:function(){return[{name:"time",type:"time"},"value"]},getRangeInfo:function(){return this._rangeInfo},getModel:function(){return this._model},getRect:function(){return this._rect},getCellWidth:function(){return this._sw},getCellHeight:function(){return this._sh},getOrient:function(){return this._orient},getFirstDayOfWeek:function(){return this._firstDayOfWeek},getDateInfo:function(t){var e=(t=Yo(t)).getFullYear(),i=t.getMonth()+1;i=i<10?"0"+i:i;var n=t.getDate();n=n<10?"0"+n:n;var o=t.getDay();return o=Math.abs((o+7-this.getFirstDayOfWeek())%7),{y:e,m:i,d:n,day:o,time:t.getTime(),formatedDate:e+"-"+i+"-"+n,date:t}},getNextNDay:function(t,e){return 0===(e=e||0)?this.getDateInfo(t):((t=new Date(this.getDateInfo(t).time)).setDate(t.getDate()+e),this.getDateInfo(t))},update:function(t,e){function i(t,e){return null!=t[e]&&"auto"!==t[e]}this._firstDayOfWeek=+this._model.getModel("dayLabel").get("firstDay"),this._orient=this._model.get("orient"),this._lineWidth=this._model.getModel("itemStyle").getItemStyle().lineWidth||0,this._rangeInfo=this._getRangeInfo(this._initRangeOption());var n=this._rangeInfo.weeks||1,o=["width","height"],a=this._model.get("cellSize").slice(),r=this._model.getBoxLayoutParams(),s="horizontal"===this._orient?[n,7]:[7,n];d([0,1],function(t){i(a,t)&&(r[o[t]]=a[t]*s[t])});var l={width:e.getWidth(),height:e.getHeight()},u=this._rect=ca(r,l);d([0,1],function(t){i(a,t)||(a[t]=u[o[t]]/s[t])}),this._sw=a[0],this._sh=a[1]},dataToPoint:function(t,e){y(t)&&(t=t[0]),null==e&&(e=!0);var i=this.getDateInfo(t),n=this._rangeInfo,o=i.formatedDate;if(e&&!(i.time>=n.start.time&&i.timea.end.time&&t.reverse(),t},_getRangeInfo:function(t){var e;(t=[this.getDateInfo(t[0]),this.getDateInfo(t[1])])[0].time>t[1].time&&(e=!0,t.reverse());var i=Math.floor(t[1].time/864e5)-Math.floor(t[0].time/864e5)+1,n=new Date(t[0].time),o=n.getDate(),a=t[1].date.getDate();if(n.setDate(o+i-1),n.getDate()!==a)for(var r=n.getTime()-t[1].time>0?1:-1;n.getDate()!==a&&(n.getTime()-t[1].time)*r>0;)i-=r,n.setDate(o+i-1);var s=Math.floor((i+t[0].day+6)/7),l=e?1-s:s-1;return e&&t.reverse(),{range:[t[0].formatedDate,t[1].formatedDate],start:t[0],end:t[1],allDay:i,weeks:s,nthWeek:l,fweek:t[0].day,lweek:t[1].day}},_getDateByWeeksAndDay:function(t,e,i){var n=this._getRangeInfo(i);if(t>n.weeks||0===t&&en.lweek)return!1;var o=7*(t-1)-n.fweek+e,a=new Date(n.start.time);return a.setDate(n.start.d+o),this.getDateInfo(a)}},Cy.dimensions=Cy.prototype.dimensions,Cy.getDimensionsInfo=Cy.prototype.getDimensionsInfo,Cy.create=function(t,e){var i=[];return t.eachComponent("calendar",function(n){var o=new Cy(n,t,e);i.push(o),n.coordinateSystem=o}),t.eachSeries(function(t){"calendar"===t.get("coordinateSystem")&&(t.coordinateSystem=i[t.get("calendarIndex")||0])}),i},Fa.register("calendar",Cy);var XN=lI.extend({type:"calendar",coordinateSystem:null,defaultOption:{zlevel:0,z:2,left:80,top:60,cellSize:20,orient:"horizontal",splitLine:{show:!0,lineStyle:{color:"#000",width:1,type:"solid"}},itemStyle:{color:"#fff",borderWidth:1,borderColor:"#ccc"},dayLabel:{show:!0,firstDay:0,position:"start",margin:"50%",nameMap:"en",color:"#000"},monthLabel:{show:!0,position:"start",margin:5,align:"center",nameMap:"en",formatter:null,color:"#000"},yearLabel:{show:!0,position:null,margin:30,formatter:null,color:"#ccc",fontFamily:"sans-serif",fontWeight:"bolder",fontSize:20}},init:function(t,e,i,n){var o=ga(t);XN.superApply(this,"init",arguments),ky(t,o)},mergeOption:function(t,e){XN.superApply(this,"mergeOption",arguments),ky(this.option,t)}}),jN={EN:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],CN:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"]},YN={EN:["S","M","T","W","T","F","S"],CN:["日","一","二","三","四","五","六"]};Ws({type:"calendar",_tlpoints:null,_blpoints:null,_firstDayOfMonth:null,_firstDayPoints:null,render:function(t,e,i){var n=this.group;n.removeAll();var o=t.coordinateSystem,a=o.getRangeInfo(),r=o.getOrient();this._renderDayRect(t,a,n),this._renderLines(t,a,r,n),this._renderYearText(t,a,r,n),this._renderMonthText(t,r,n),this._renderWeekText(t,a,r,n)},_renderDayRect:function(t,e,i){for(var n=t.coordinateSystem,o=t.getModel("itemStyle").getItemStyle(),a=n.getCellWidth(),r=n.getCellHeight(),s=e.start.time;s<=e.end.time;s=n.getNextNDay(s,1).time){var l=n.dataToRect([s],!1).tl,u=new yM({shape:{x:l[0],y:l[1],width:a,height:r},cursor:"default",style:o});i.add(u)}},_renderLines:function(t,e,i,n){function o(e){a._firstDayOfMonth.push(r.getDateInfo(e)),a._firstDayPoints.push(r.dataToRect([e],!1).tl);var o=a._getLinePointsOfOneWeek(t,e,i);a._tlpoints.push(o[0]),a._blpoints.push(o[o.length-1]),l&&a._drawSplitline(o,s,n)}var a=this,r=t.coordinateSystem,s=t.getModel("splitLine.lineStyle").getLineStyle(),l=t.get("splitLine.show"),u=s.lineWidth;this._tlpoints=[],this._blpoints=[],this._firstDayOfMonth=[],this._firstDayPoints=[];for(var h=e.start,c=0;h.time<=e.end.time;c++){o(h.formatedDate),0===c&&(h=r.getDateInfo(e.start.y+"-"+e.start.m));var d=h.date;d.setMonth(d.getMonth()+1),h=r.getDateInfo(d)}o(r.getNextNDay(e.end.time,1).formatedDate),l&&this._drawSplitline(a._getEdgesPoints(a._tlpoints,u,i),s,n),l&&this._drawSplitline(a._getEdgesPoints(a._blpoints,u,i),s,n)},_getEdgesPoints:function(t,e,i){var n=[t[0].slice(),t[t.length-1].slice()],o="horizontal"===i?0:1;return n[0][o]=n[0][o]-e/2,n[1][o]=n[1][o]+e/2,n},_drawSplitline:function(t,e,i){var n=new gM({z2:20,shape:{points:t},style:e});i.add(n)},_getLinePointsOfOneWeek:function(t,e,i){var n=t.coordinateSystem;e=n.getDateInfo(e);for(var o=[],a=0;a<7;a++){var r=n.getNextNDay(e.time,a),s=n.dataToRect([r.time],!1);o[2*r.day]=s.tl,o[2*r.day+1]=s["horizontal"===i?"bl":"tr"]}return o},_formatterLabel:function(t,e){return"string"==typeof t&&t?oa(t,e):"function"==typeof t?t(e):e.nameMap},_yearTextPositionControl:function(t,e,i,n,o){e=e.slice();var a=["center","bottom"];"bottom"===n?(e[1]+=o,a=["center","top"]):"left"===n?e[0]-=o:"right"===n?(e[0]+=o,a=["center","top"]):e[1]-=o;var r=0;return"left"!==n&&"right"!==n||(r=Math.PI/2),{rotation:r,position:e,style:{textAlign:a[0],textVerticalAlign:a[1]}}},_renderYearText:function(t,e,i,n){var o=t.getModel("yearLabel");if(o.get("show")){var a=o.get("margin"),r=o.get("position");r||(r="horizontal"!==i?"top":"left");var s=[this._tlpoints[this._tlpoints.length-1],this._blpoints[0]],l=(s[0][0]+s[1][0])/2,u=(s[0][1]+s[1][1])/2,h="horizontal"===i?0:1,c={top:[l,s[h][1]],bottom:[l,s[1-h][1]],left:[s[1-h][0],u],right:[s[h][0],u]},d=e.start.y;+e.end.y>+e.start.y&&(d=d+"-"+e.end.y);var f=o.get("formatter"),p={start:e.start.y,end:e.end.y,nameMap:d},g=this._formatterLabel(f,p),m=new rM({z2:30});mo(m.style,o,{text:g}),m.attr(this._yearTextPositionControl(m,c[r],i,r,a)),n.add(m)}},_monthTextPositionControl:function(t,e,i,n,o){var a="left",r="top",s=t[0],l=t[1];return"horizontal"===i?(l+=o,e&&(a="center"),"start"===n&&(r="bottom")):(s+=o,e&&(r="middle"),"start"===n&&(a="right")),{x:s,y:l,textAlign:a,textVerticalAlign:r}},_renderMonthText:function(t,e,i){var n=t.getModel("monthLabel");if(n.get("show")){var o=n.get("nameMap"),r=n.get("margin"),s=n.get("position"),l=n.get("align"),u=[this._tlpoints,this._blpoints];_(o)&&(o=jN[o.toUpperCase()]||[]);var h="start"===s?0:1,c="horizontal"===e?0:1;r="start"===s?-r:r;for(var d="center"===l,f=0;f=r[0]&&t<=r[1]}if(t===this._dataZoomModel){var n=this._dimName,o=this.getTargetSeriesModels(),a=t.get("filterMode"),r=this._valueWindow;"none"!==a&&$N(o,function(t){var e=t.getData(),o=e.mapDimension(n,!0);o.length&&("weakFilter"===a?e.filterSelf(function(t){for(var i,n,a,s=0;sr[1];if(u&&!h&&!c)return!0;u&&(a=!0),h&&(i=!0),c&&(n=!0)}return a&&i&&n}):$N(o,function(n){if("empty"===a)t.setData(e.map(n,function(t){return i(t)?t:NaN}));else{var o={};o[n]=r,e.selectRange(o)}}),$N(o,function(t){e.setApproximateExtent(r,t)}))})}}};var tO=d,eO=KN,iO=Fs({type:"dataZoom",dependencies:["xAxis","yAxis","zAxis","radiusAxis","angleAxis","singleAxis","series"],defaultOption:{zlevel:0,z:4,orient:null,xAxisIndex:null,yAxisIndex:null,filterMode:"filter",throttle:null,start:0,end:100,startValue:null,endValue:null,minSpan:null,maxSpan:null,minValueSpan:null,maxValueSpan:null,rangeMode:null},init:function(t,e,i){this._dataIntervalByAxis={},this._dataInfo={},this._axisProxies={},this.textStyleModel,this._autoThrottle=!0,this._rangePropMode=["percent","percent"];var n=By(t);this.mergeDefaultAndTheme(t,i),this.doInit(n)},mergeOption:function(t){var e=By(t);n(this.option,t,!0),this.doInit(e)},doInit:function(t){var e=this.option;U_.canvasSupported||(e.realtime=!1),this._setDefaultThrottle(t),Vy(this,t),tO([["start","startValue"],["end","endValue"]],function(t,i){"value"===this._rangePropMode[i]&&(e[t[0]]=null)},this),this.textStyleModel=this.getModel("textStyle"),this._resetTarget(),this._giveAxisProxies()},_giveAxisProxies:function(){var t=this._axisProxies;this.eachTargetAxis(function(e,i,n,o){var a=this.dependentModels[e.axis][i],r=a.__dzAxisProxy||(a.__dzAxisProxy=new QN(e.name,i,this,o));t[e.name+"_"+i]=r},this)},_resetTarget:function(){var t=this.option,e=this._judgeAutoMode();eO(function(e){var i=e.axisIndex;t[i]=Di(t[i])},this),"axisIndex"===e?this._autoSetAxisIndex():"orient"===e&&this._autoSetOrient()},_judgeAutoMode:function(){var t=this.option,e=!1;eO(function(i){null!=t[i.axisIndex]&&(e=!0)},this);var i=t.orient;return null==i&&e?"orient":e?void 0:(null==i&&(t.orient="horizontal"),"axisIndex")},_autoSetAxisIndex:function(){var t=!0,e=this.get("orient",!0),i=this.option,n=this.dependentModels;if(t){var o="vertical"===e?"y":"x";n[o+"Axis"].length?(i[o+"AxisIndex"]=[0],t=!1):tO(n.singleAxis,function(n){t&&n.get("orient",!0)===e&&(i.singleAxisIndex=[n.componentIndex],t=!1)})}t&&eO(function(e){if(t){var n=[],o=this.dependentModels[e.axis];if(o.length&&!n.length)for(var a=0,r=o.length;a0?100:20}},getFirstTargetAxisModel:function(){var t;return eO(function(e){if(null==t){var i=this.get(e.axisIndex);i.length&&(t=this.dependentModels[e.axis][i[0]])}},this),t},eachTargetAxis:function(t,e){var i=this.ecModel;eO(function(n){tO(this.get(n.axisIndex),function(o){t.call(e,n,o,this,i)},this)},this)},getAxisProxy:function(t,e){return this._axisProxies[t+"_"+e]},getAxisModel:function(t,e){var i=this.getAxisProxy(t,e);return i&&i.getAxisModel()},setRawRange:function(t,e){var i=this.option;tO([["start","startValue"],["end","endValue"]],function(e){null==t[e[0]]&&null==t[e[1]]||(i[e[0]]=t[e[0]],i[e[1]]=t[e[1]])},this),!e&&Vy(this,t)},getPercentRange:function(){var t=this.findRepresentativeAxisProxy();if(t)return t.getDataPercentWindow()},getValueRange:function(t,e){if(null!=t||null!=e)return this.getAxisProxy(t,e).getDataValueWindow();var i=this.findRepresentativeAxisProxy();return i?i.getDataValueWindow():void 0},findRepresentativeAxisProxy:function(t){if(t)return t.__dzAxisProxy;var e=this._axisProxies;for(var i in e)if(e.hasOwnProperty(i)&&e[i].hostedBy(this))return e[i];for(var i in e)if(e.hasOwnProperty(i)&&!e[i].hostedBy(this))return e[i]},getRangePropMode:function(){return this._rangePropMode.slice()}}),nO=qI.extend({type:"dataZoom",render:function(t,e,i,n){this.dataZoomModel=t,this.ecModel=e,this.api=i},getTargetCoordInfo:function(){function t(t,e,i,n){for(var o,a=0;a0&&e%g)p+=f;else{var i=null==t||isNaN(t)||""===t,n=i?0:aO(t,a,u,!0);i&&!l&&e?(c.push([c[c.length-1][0],0]),d.push([d[d.length-1][0],0])):!i&&l&&(c.push([p,0]),d.push([p,0])),c.push([p,n]),d.push([p,n]),p+=f,l=i}});var m=this.dataZoomModel;this._displayables.barGroup.add(new pM({shape:{points:c},style:r({fill:m.get("dataBackgroundColor")},m.getModel("dataBackground.areaStyle").getAreaStyle()),silent:!0,z2:-20})),this._displayables.barGroup.add(new gM({shape:{points:d},style:m.getModel("dataBackground.lineStyle").getLineStyle(),silent:!0,z2:-19}))}}},_prepareDataShadowInfo:function(){var t=this.dataZoomModel,e=t.get("showDataShadow");if(!1!==e){var i,n=this.ecModel;return t.eachTargetAxis(function(o,a){d(t.getAxisProxy(o.name,a).getTargetSeriesModels(),function(t){if(!(i||!0!==e&&l(cO,t.get("type"))<0)){var r,s=n.getComponent(o.axis,a).axis,u=Gy(o.name),h=t.coordinateSystem;null!=u&&h.getOtherAxis&&(r=h.getOtherAxis(s).inverse),u=t.getData().mapDimension(u),i={thisAxis:s,series:t,thisDim:o.name,otherDim:u,otherAxisInverse:r}}},this)},this),i}},_renderHandle:function(){var t=this._displayables,e=t.handles=[],i=t.handleLabels=[],n=this._displayables.barGroup,o=this._size,a=this.dataZoomModel;n.add(t.filler=new oO({draggable:!0,cursor:Fy(this._orient),drift:sO(this._onDragMove,this,"all"),onmousemove:function(t){mw(t.event)},ondragstart:sO(this._showDataInfo,this,!0),ondragend:sO(this._onDragEnd,this),onmouseover:sO(this._showDataInfo,this,!0),onmouseout:sO(this._showDataInfo,this,!1),style:{fill:a.get("fillerColor"),textPosition:"inside"}})),n.add(new oO($n({silent:!0,shape:{x:0,y:0,width:o[0],height:o[1]},style:{stroke:a.get("dataBackgroundColor")||a.get("borderColor"),lineWidth:1,fill:"rgba(0,0,0,0)"}}))),lO([0,1],function(t){var o=Po(a.get("handleIcon"),{cursor:Fy(this._orient),draggable:!0,drift:sO(this._onDragMove,this,t),onmousemove:function(t){mw(t.event)},ondragend:sO(this._onDragEnd,this),onmouseover:sO(this._showDataInfo,this,!0),onmouseout:sO(this._showDataInfo,this,!1)},{x:-1,y:0,width:2,height:2}),r=o.getBoundingRect();this._handleHeight=Vo(a.get("handleSize"),this._size[1]),this._handleWidth=r.width/r.height*this._handleHeight,o.setStyle(a.getModel("handleStyle").getItemStyle());var s=a.get("handleColor");null!=s&&(o.style.fill=s),n.add(e[t]=o);var l=a.textStyleModel;this.group.add(i[t]=new rM({silent:!0,invisible:!0,style:{x:0,y:0,text:"",textVerticalAlign:"middle",textAlign:"center",textFill:l.getTextColor(),textFont:l.getFont()},z2:10}))},this)},_resetInterval:function(){var t=this._range=this.dataZoomModel.getPercentRange(),e=this._getViewExtent();this._handleEnds=[aO(t[0],[0,100],e,!0),aO(t[1],[0,100],e,!0)]},_updateInterval:function(t,e){var i=this.dataZoomModel,n=this._handleEnds,o=this._getViewExtent(),a=i.findRepresentativeAxisProxy().getMinMaxSpan(),r=[0,100];QL(e,n,o,i.get("zoomLock")?"all":t,null!=a.minSpan?aO(a.minSpan,r,o,!0):null,null!=a.maxSpan?aO(a.maxSpan,r,o,!0):null);var s=this._range,l=this._range=rO([aO(n[0],o,r,!0),aO(n[1],o,r,!0)]);return!s||s[0]!==l[0]||s[1]!==l[1]},_updateView:function(t){var e=this._displayables,i=this._handleEnds,n=rO(i.slice()),o=this._size;lO([0,1],function(t){var n=e.handles[t],a=this._handleHeight;n.attr({scale:[a/2,a/2],position:[i[t],o[1]/2-a/2]})},this),e.filler.setShape({x:n[0],y:0,width:n[1]-n[0],height:o[1]}),this._updateDataInfo(t)},_updateDataInfo:function(t){function e(t){var e=Ao(n.handles[t].parent,this.group),i=Co(0===t?"right":"left",e),s=this._handleWidth/2+hO,l=Do([c[t]+(0===t?-s:s),this._size[1]/2],e);o[t].setStyle({x:l[0],y:l[1],textVerticalAlign:a===uO?"middle":i,textAlign:a===uO?i:"center",text:r[t]})}var i=this.dataZoomModel,n=this._displayables,o=n.handleLabels,a=this._orient,r=["",""];if(i.get("showDetail")){var s=i.findRepresentativeAxisProxy();if(s){var l=s.getAxisModel().axis,u=this._range,h=t?s.calculateDataWindow({start:u[0],end:u[1]}).valueWindow:s.getDataValueWindow();r=[this._formatLabel(h[0],l),this._formatLabel(h[1],l)]}}var c=rO(this._handleEnds.slice());e.call(this,0),e.call(this,1)},_formatLabel:function(t,e){var i=this.dataZoomModel,n=i.get("labelFormatter"),o=i.get("labelPrecision");null!=o&&"auto"!==o||(o=e.getPixelPrecision());var a=null==t||isNaN(t)?"":"category"===e.type||"time"===e.type?e.scale.getLabel(Math.round(t)):t.toFixed(Math.min(o,20));return x(n)?n(t,a):_(n)?n.replace("{value}",a):a},_showDataInfo:function(t){t=this._dragging||t;var e=this._displayables.handleLabels;e[0].attr("invisible",!t),e[1].attr("invisible",!t)},_onDragMove:function(t,e,i){this._dragging=!0;var n=Do([e,i],this._displayables.barGroup.getLocalTransform(),!0),o=this._updateInterval(t,n[0]),a=this.dataZoomModel.get("realtime");this._updateView(!a),o&&a&&this._dispatchZoomAction()},_onDragEnd:function(){this._dragging=!1,this._showDataInfo(!1),!this.dataZoomModel.get("realtime")&&this._dispatchZoomAction()},_onClickPanelClick:function(t){var e=this._size,i=this._displayables.barGroup.transformCoordToLocal(t.offsetX,t.offsetY);if(!(i[0]<0||i[0]>e[0]||i[1]<0||i[1]>e[1])){var n=this._handleEnds,o=(n[0]+n[1])/2,a=this._updateInterval("all",i[0]-o);this._updateView(),a&&this._dispatchZoomAction()}},_dispatchZoomAction:function(){var t=this._range;this.api.dispatchAction({type:"dataZoom",from:this.uid,dataZoomId:this.dataZoomModel.id,start:t[0],end:t[1]})},_findCoordRect:function(){var t;if(lO(this.getTargetCoordInfo(),function(e){if(!t&&e.length){var i=e[0].model.coordinateSystem;t=i.getRect&&i.getRect()}}),!t){var e=this.api.getWidth(),i=this.api.getHeight();t={x:.2*e,y:.2*i,width:.6*e,height:.6*i}}return t}});iO.extend({type:"dataZoom.inside",defaultOption:{disabled:!1,zoomLock:!1,zoomOnMouseWheel:!0,moveOnMouseMove:!0,moveOnMouseWheel:!1,preventDefaultMouseMove:!0}});var fO="\0_ec_dataZoom_roams",pO=m,gO=nO.extend({type:"dataZoom.inside",init:function(t,e){this._range},render:function(t,e,i,n){gO.superApply(this,"render",arguments),this._range=t.getPercentRange(),d(this.getTargetCoordInfo(),function(e,n){var o=f(e,function(t){return Zy(t.model)});d(e,function(e){var a=e.model,r={};d(["pan","zoom","scrollMove"],function(t){r[t]=pO(mO[t],this,e,n)},this),Wy(i,{coordId:Zy(a),allCoordIds:o,containsPoint:function(t,e,i){return a.coordinateSystem.containPoint([e,i])},dataZoomId:t.id,dataZoomModel:t,getRange:r})},this)},this)},dispose:function(){Hy(this.api,this.dataZoomModel.id),gO.superApply(this,"dispose",arguments),this._range=null}}),mO={zoom:function(t,e,i,n){var o=this._range,a=o.slice(),r=t.axisModels[0];if(r){var s=vO[e](null,[n.originX,n.originY],r,i,t),l=(s.signal>0?s.pixelStart+s.pixelLength-s.pixel:s.pixel-s.pixelStart)/s.pixelLength*(a[1]-a[0])+a[0],u=Math.max(1/n.scale,0);a[0]=(a[0]-l)*u+l,a[1]=(a[1]-l)*u+l;var h=this.dataZoomModel.findRepresentativeAxisProxy().getMinMaxSpan();return QL(0,a,[0,100],0,h.minSpan,h.maxSpan),this._range=a,o[0]!==a[0]||o[1]!==a[1]?a:void 0}},pan:Ky(function(t,e,i,n,o,a){var r=vO[n]([a.oldX,a.oldY],[a.newX,a.newY],e,o,i);return r.signal*(t[1]-t[0])*r.pixel/r.pixelLength}),scrollMove:Ky(function(t,e,i,n,o,a){return vO[n]([0,0],[a.scrollDelta,a.scrollDelta],e,o,i).signal*(t[1]-t[0])*a.scrollDelta})},vO={grid:function(t,e,i,n,o){var a=i.axis,r={},s=o.model.coordinateSystem.getRect();return t=t||[0,0],"x"===a.dim?(r.pixel=e[0]-t[0],r.pixelLength=s.width,r.pixelStart=s.x,r.signal=a.inverse?1:-1):(r.pixel=e[1]-t[1],r.pixelLength=s.height,r.pixelStart=s.y,r.signal=a.inverse?-1:1),r},polar:function(t,e,i,n,o){var a=i.axis,r={},s=o.model.coordinateSystem,l=s.getRadiusAxis().getExtent(),u=s.getAngleAxis().getExtent();return t=t?s.pointToCoord(t):[0,0],e=s.pointToCoord(e),"radiusAxis"===i.mainType?(r.pixel=e[0]-t[0],r.pixelLength=l[1]-l[0],r.pixelStart=l[0],r.signal=a.inverse?1:-1):(r.pixel=e[1]-t[1],r.pixelLength=u[1]-u[0],r.pixelStart=u[0],r.signal=a.inverse?-1:1),r},singleAxis:function(t,e,i,n,o){var a=i.axis,r=o.model.coordinateSystem.getRect(),s={};return t=t||[0,0],"horizontal"===a.orient?(s.pixel=e[0]-t[0],s.pixelLength=r.width,s.pixelStart=r.x,s.signal=a.inverse?1:-1):(s.pixel=e[1]-t[1],s.pixelLength=r.height,s.pixelStart=r.y,s.signal=a.inverse?-1:1),s}};Os({getTargetSeries:function(t){var e=R();return t.eachComponent("dataZoom",function(t){t.eachTargetAxis(function(t,i,n){d(n.getAxisProxy(t.name,i).getTargetSeriesModels(),function(t){e.set(t.uid,t)})})}),e},modifyOutputEnd:!0,overallReset:function(t,e){t.eachComponent("dataZoom",function(t){t.eachTargetAxis(function(t,i,n){n.getAxisProxy(t.name,i).reset(n,e)}),t.eachTargetAxis(function(t,i,n){n.getAxisProxy(t.name,i).filterData(n,e)})}),t.eachComponent("dataZoom",function(t){var e=t.findRepresentativeAxisProxy(),i=e.getDataPercentWindow(),n=e.getDataValueWindow();t.setRawRange({start:i[0],end:i[1],startValue:n[0],endValue:n[1]},!0)})}}),Es("dataZoom",function(t,e){var i=Ny(m(e.eachComponent,e,"dataZoom"),KN,function(t,e){return t.get(e.axisIndex)}),n=[];e.eachComponent({mainType:"dataZoom",query:t},function(t,e){n.push.apply(n,i(t).nodes)}),d(n,function(e,i){e.setRawRange({start:t.start,end:t.end,startValue:t.startValue,endValue:t.endValue})})});var yO=d,xO=function(t){var e=t&&t.visualMap;y(e)||(e=e?[e]:[]),yO(e,function(t){if(t){$y(t,"splitList")&&!$y(t,"pieces")&&(t.pieces=t.splitList,delete t.splitList);var e=t.pieces;e&&y(e)&&yO(e,function(t){w(t)&&($y(t,"start")&&!$y(t,"min")&&(t.min=t.start),$y(t,"end")&&!$y(t,"max")&&(t.max=t.end))})}})};lI.registerSubTypeDefaulter("visualMap",function(t){return t.categories||(t.pieces?t.pieces.length>0:t.splitNumber>0)&&!t.calculable?"piecewise":"continuous"});var _O=VT.VISUAL.COMPONENT;Bs(_O,{createOnAllSeries:!0,reset:function(t,e){var i=[];return e.eachComponent("visualMap",function(e){var n=t.pipelineContext;!e.isTargetSeries(t)||n&&n.large||i.push(ny(e.stateList,e.targetVisuals,m(e.getValueState,e),e.getDataDimension(t.getData())))}),i}}),Bs(_O,{createOnAllSeries:!0,reset:function(t,e){var i=t.getData(),n=[];e.eachComponent("visualMap",function(e){if(e.isTargetSeries(t)){var o=e.getVisualMeta(m(Jy,null,t,e))||{stops:[],outerColors:[]},a=e.getDataDimension(i),r=i.getDimensionInfo(a);null!=r&&(o.dimension=r.index,n.push(o))}}),t.getData().setVisual("visualMeta",n)}});var wO={get:function(t,e,n){var o=i((bO[t]||{})[e]);return n&&y(o)?o[o.length-1]:o}},bO={color:{active:["#006edd","#e0ffff"],inactive:["rgba(0,0,0,0)"]},colorHue:{active:[0,360],inactive:[0,0]},colorSaturation:{active:[.3,1],inactive:[0,0]},colorLightness:{active:[.9,.5],inactive:[0,0]},colorAlpha:{active:[.3,1],inactive:[0,0]},opacity:{active:[.3,1],inactive:[0,0]},symbol:{active:["circle","roundRect","diamond"],inactive:["none"]},symbolSize:{active:[10,50],inactive:[0,0]}},SO=hL.mapVisual,MO=hL.eachVisual,IO=y,TO=d,AO=Fo,DO=Bo,CO=B,LO=Fs({type:"visualMap",dependencies:["series"],stateList:["inRange","outOfRange"],replacableOptionKeys:["inRange","outOfRange","target","controller","color"],dataBound:[-1/0,1/0],layoutMode:{type:"box",ignoreSize:!0},defaultOption:{show:!0,zlevel:0,z:4,seriesIndex:"all",min:0,max:200,dimension:null,inRange:null,outOfRange:null,left:0,right:null,top:null,bottom:0,itemWidth:null,itemHeight:null,inverse:!1,orient:"vertical",backgroundColor:"rgba(0,0,0,0)",borderColor:"#ccc",contentColor:"#5793f3",inactiveColor:"#aaa",borderWidth:0,padding:5,textGap:10,precision:0,color:null,formatter:null,text:null,textStyle:{color:"#333"}},init:function(t,e,i){this._dataExtent,this.targetVisuals={},this.controllerVisuals={},this.textStyleModel,this.itemSize,this.mergeDefaultAndTheme(t,i)},optionUpdated:function(t,e){var i=this.option;U_.canvasSupported||(i.realtime=!1),!e&&ey(i,t,this.replacableOptionKeys),this.textStyleModel=this.getModel("textStyle"),this.resetItemSize(),this.completeVisualOption()},resetVisual:function(t){var e=this.stateList;t=m(t,this),this.controllerVisuals=ty(this.option.controller,e,t),this.targetVisuals=ty(this.option.target,e,t)},getTargetSeriesIndices:function(){var t=this.option.seriesIndex,e=[];return null==t||"all"===t?this.ecModel.eachSeries(function(t,i){e.push(i)}):e=Di(t),e},eachTargetSeries:function(t,e){d(this.getTargetSeriesIndices(),function(i){t.call(e,this.ecModel.getSeriesByIndex(i))},this)},isTargetSeries:function(t){var e=!1;return this.eachTargetSeries(function(i){i===t&&(e=!0)}),e},formatValueText:function(t,e,i){function n(t){return t===l[0]?"min":t===l[1]?"max":(+t).toFixed(Math.min(s,20))}var o,a,r=this.option,s=r.precision,l=this.dataBound,u=r.formatter;return i=i||["<",">"],y(t)&&(t=t.slice(),o=!0),a=e?t:o?[n(t[0]),n(t[1])]:n(t),_(u)?u.replace("{value}",o?a[0]:a).replace("{value2}",o?a[1]:a):x(u)?o?u(t[0],t[1]):u(t):o?t[0]===l[0]?i[0]+" "+a[1]:t[1]===l[1]?i[1]+" "+a[0]:a[0]+" - "+a[1]:a},resetExtent:function(){var t=this.option,e=AO([t.min,t.max]);this._dataExtent=e},getDataDimension:function(t){var e=this.option.dimension,i=t.dimensions;if(null!=e||i.length){if(null!=e)return t.getDimension(e);for(var n=t.dimensions,o=n.length-1;o>=0;o--){var a=n[o];if(!t.getDimensionInfo(a).isCalculationCoord)return a}}},getExtent:function(){return this._dataExtent.slice()},completeVisualOption:function(){function t(t){IO(o.color)&&!t.inRange&&(t.inRange={color:o.color.slice().reverse()}),t.inRange=t.inRange||{color:e.get("gradientColor")},TO(this.stateList,function(e){var i=t[e];if(_(i)){var n=wO.get(i,"active",l);n?(t[e]={},t[e][i]=n):delete t[e]}},this)}var e=this.ecModel,o=this.option,a={inRange:o.inRange,outOfRange:o.outOfRange},r=o.target||(o.target={}),s=o.controller||(o.controller={});n(r,a),n(s,a);var l=this.isCategory();t.call(this,r),t.call(this,s),function(t,e,i){var n=t[e],o=t[i];n&&!o&&(o=t[i]={},TO(n,function(t,e){if(hL.isValidType(e)){var i=wO.get(e,"inactive",l);null!=i&&(o[e]=i,"color"!==e||o.hasOwnProperty("opacity")||o.hasOwnProperty("colorAlpha")||(o.opacity=[0,0]))}}))}.call(this,r,"inRange","outOfRange"),function(t){var e=(t.inRange||{}).symbol||(t.outOfRange||{}).symbol,n=(t.inRange||{}).symbolSize||(t.outOfRange||{}).symbolSize,o=this.get("inactiveColor");TO(this.stateList,function(a){var r=this.itemSize,s=t[a];s||(s=t[a]={color:l?o:[o]}),null==s.symbol&&(s.symbol=e&&i(e)||(l?"roundRect":["roundRect"])),null==s.symbolSize&&(s.symbolSize=n&&i(n)||(l?r[0]:[r[0],r[0]])),s.symbol=SO(s.symbol,function(t){return"none"===t||"square"===t?"roundRect":t});var u=s.symbolSize;if(null!=u){var h=-1/0;MO(u,function(t){t>h&&(h=t)}),s.symbolSize=SO(u,function(t){return DO(t,[0,h],[0,r[0]],!0)})}},this)}.call(this,s)},resetItemSize:function(){this.itemSize=[parseFloat(this.get("itemWidth")),parseFloat(this.get("itemHeight"))]},isCategory:function(){return!!this.option.categories},setSelected:CO,getValueState:CO,getVisualMeta:CO}),kO=[20,140],PO=LO.extend({type:"visualMap.continuous",defaultOption:{align:"auto",calculable:!1,range:null,realtime:!0,itemHeight:null,itemWidth:null,hoverLink:!0,hoverLinkDataSize:null,hoverLinkOnHandle:null},optionUpdated:function(t,e){PO.superApply(this,"optionUpdated",arguments),this.resetExtent(),this.resetVisual(function(t){t.mappingMethod="linear",t.dataExtent=this.getExtent()}),this._resetRange()},resetItemSize:function(){PO.superApply(this,"resetItemSize",arguments);var t=this.itemSize;"horizontal"===this._orient&&t.reverse(),(null==t[0]||isNaN(t[0]))&&(t[0]=kO[0]),(null==t[1]||isNaN(t[1]))&&(t[1]=kO[1])},_resetRange:function(){var t=this.getExtent(),e=this.option.range;!e||e.auto?(t.auto=1,this.option.range=t):y(e)&&(e[0]>e[1]&&e.reverse(),e[0]=Math.max(e[0],t[0]),e[1]=Math.min(e[1],t[1]))},completeVisualOption:function(){LO.prototype.completeVisualOption.apply(this,arguments),d(this.stateList,function(t){var e=this.option.controller[t].symbolSize;e&&e[0]!==e[1]&&(e[0]=0)},this)},setSelected:function(t){this.option.range=t.slice(),this._resetRange()},getSelected:function(){var t=this.getExtent(),e=Fo((this.get("range")||[]).slice());return e[0]>t[1]&&(e[0]=t[1]),e[1]>t[1]&&(e[1]=t[1]),e[0]=i[1]||t<=e[1])?"inRange":"outOfRange"},findTargetDataIndices:function(t){var e=[];return this.eachTargetSeries(function(i){var n=[],o=i.getData();o.each(this.getDataDimension(o),function(e,i){t[0]<=e&&e<=t[1]&&n.push(i)},this),e.push({seriesId:i.id,dataIndex:n})},this),e},getVisualMeta:function(t){function e(e,i){o.push({value:e,color:t(e,i)})}for(var i=Qy(0,0,this.getExtent()),n=Qy(0,0,this.option.range.slice()),o=[],a=0,r=0,s=n.length,l=i.length;rt[1])break;i.push({color:this.getControllerVisual(a,"color",e),offset:o/100})}return i.push({color:this.getControllerVisual(t[1],"color",e),offset:1}),i},_createBarPoints:function(t,e){var i=this.visualMapModel.itemSize;return[[i[0]-e[0],t[0]],[i[0],t[0]],[i[0],t[1]],[i[0]-e[1],t[1]]]},_createBarGroup:function(t){var e=this._orient,i=this.visualMapModel.get("inverse");return new tb("horizontal"!==e||i?"horizontal"===e&&i?{scale:"bottom"===t?[-1,1]:[1,1],rotation:-Math.PI/2}:"vertical"!==e||i?{scale:"left"===t?[1,1]:[-1,1]}:{scale:"left"===t?[1,-1]:[-1,-1]}:{scale:"bottom"===t?[1,1]:[-1,1],rotation:Math.PI/2})},_updateHandle:function(t,e){if(this._useHandle){var i=this._shapes,n=this.visualMapModel,o=i.handleThumbs,a=i.handleLabels;EO([0,1],function(r){var s=o[r];s.setStyle("fill",e.handlesColor[r]),s.position[1]=t[r];var l=Do(i.handleLabelPoints[r],Ao(s,this.group));a[r].setStyle({x:l[0],y:l[1],text:n.formatValueText(this._dataInterval[r]),textVerticalAlign:"middle",textAlign:this._applyTransform("horizontal"===this._orient?0===r?"bottom":"top":"left",i.barGroup)})},this)}},_showIndicator:function(t,e,i,n){var o=this.visualMapModel,a=o.getExtent(),r=o.itemSize,s=[0,r[1]],l=OO(t,a,s,!0),u=this._shapes,h=u.indicator;if(h){h.position[1]=l,h.attr("invisible",!1),h.setShape("points",ox(!!i,n,l,r[1]));var c={convertOpacityToAlpha:!0},d=this.getControllerVisual(t,"color",c);h.setStyle("fill",d);var f=Do(u.indicatorLabelPoint,Ao(h,this.group)),p=u.indicatorLabel;p.attr("invisible",!1);var g=this._applyTransform("left",u.barGroup),m=this._orient;p.setStyle({text:(i||"")+o.formatValueText(e),textVerticalAlign:"horizontal"===m?g:"middle",textAlign:"horizontal"===m?"center":g,x:f[0],y:f[1]})}},_enableHoverLinkToSeries:function(){var t=this;this._shapes.barGroup.on("mousemove",function(e){if(t._hovering=!0,!t._dragging){var i=t.visualMapModel.itemSize,n=t._applyTransform([e.offsetX,e.offsetY],t._shapes.barGroup,!0,!0);n[1]=RO(zO(0,n[1]),i[1]),t._doHoverLinkToSeries(n[1],0<=n[0]&&n[0]<=i[0])}}).on("mouseout",function(){t._hovering=!1,!t._dragging&&t._clearHoverLinkToSeries()})},_enableHoverLinkFromSeries:function(){var t=this.api.getZr();this.visualMapModel.option.hoverLink?(t.on("mouseover",this._hoverLinkFromSeriesMouseOver,this),t.on("mouseout",this._hideIndicator,this)):this._clearHoverLinkFromSeries()},_doHoverLinkToSeries:function(t,e){var i=this.visualMapModel,n=i.itemSize;if(i.option.hoverLink){var o=[0,n[1]],a=i.getExtent();t=RO(zO(o[0],t),o[1]);var r=ax(i,a,o),s=[t-r,t+r],l=OO(t,o,a,!0),u=[OO(s[0],o,a,!0),OO(s[1],o,a,!0)];s[0]o[1]&&(u[1]=1/0),e&&(u[0]===-1/0?this._showIndicator(l,u[1],"< ",r):u[1]===1/0?this._showIndicator(l,u[0],"> ",r):this._showIndicator(l,l,"≈ ",r));var h=this._hoverLinkDataIndices,c=[];(e||rx(i))&&(c=this._hoverLinkDataIndices=i.findTargetDataIndices(u));var d=Ri(h,c);this._dispatchHighDown("downplay",ex(d[0])),this._dispatchHighDown("highlight",ex(d[1]))}},_hoverLinkFromSeriesMouseOver:function(t){var e=t.target,i=this.visualMapModel;if(e&&null!=e.dataIndex){var n=this.ecModel.getSeriesByIndex(e.seriesIndex);if(i.isTargetSeries(n)){var o=n.getData(e.dataType),a=o.get(i.getDataDimension(o),e.dataIndex,!0);isNaN(a)||this._showIndicator(a,a)}}},_hideIndicator:function(){var t=this._shapes;t.indicator&&t.indicator.attr("invisible",!0),t.indicatorLabel&&t.indicatorLabel.attr("invisible",!0)},_clearHoverLinkToSeries:function(){this._hideIndicator();var t=this._hoverLinkDataIndices;this._dispatchHighDown("downplay",ex(t)),t.length=0},_clearHoverLinkFromSeries:function(){this._hideIndicator();var t=this.api.getZr();t.off("mouseover",this._hoverLinkFromSeriesMouseOver),t.off("mouseout",this._hideIndicator)},_applyTransform:function(t,e,i,n){var o=Ao(e,n?null:this.group);return zM[y(t)?"applyTransform":"transformDirection"](t,o,i)},_dispatchHighDown:function(t,e){e&&e.length&&this.api.dispatchAction({type:t,batch:e})},dispose:function(){this._clearHoverLinkFromSeries(),this._clearHoverLinkToSeries()},remove:function(){this._clearHoverLinkFromSeries(),this._clearHoverLinkToSeries()}});Es({type:"selectDataRange",event:"dataRangeSelected",update:"update"},function(t,e){e.eachComponent({mainType:"visualMap",query:t},function(e){e.setSelected(t.selected)})}),Ns(xO);var FO=LO.extend({type:"visualMap.piecewise",defaultOption:{selected:null,minOpen:!1,maxOpen:!1,align:"auto",itemWidth:20,itemHeight:14,itemSymbol:"roundRect",pieceList:null,categories:null,splitNumber:5,selectedMode:"multiple",itemGap:10,hoverLink:!0,showLabel:null},optionUpdated:function(t,e){FO.superApply(this,"optionUpdated",arguments),this._pieceList=[],this.resetExtent();var n=this._mode=this._determineMode();WO[this._mode].call(this),this._resetSelected(t,e);var o=this.option.categories;this.resetVisual(function(t,e){"categories"===n?(t.mappingMethod="category",t.categories=i(o)):(t.dataExtent=this.getExtent(),t.mappingMethod="piecewise",t.pieceList=f(this._pieceList,function(t){var t=i(t);return"inRange"!==e&&(t.visual=null),t}))})},completeVisualOption:function(){function t(t,e,i){return t&&t[e]&&(w(t[e])?t[e].hasOwnProperty(i):t[e]===i)}var e=this.option,i={},n=hL.listVisualTypes(),o=this.isCategory();d(e.pieces,function(t){d(n,function(e){t.hasOwnProperty(e)&&(i[e]=1)})}),d(i,function(i,n){var a=0;d(this.stateList,function(i){a|=t(e,i,n)||t(e.target,i,n)},this),!a&&d(this.stateList,function(t){(e[t]||(e[t]={}))[n]=wO.get(n,"inRange"===t?"active":"inactive",o)})},this),LO.prototype.completeVisualOption.apply(this,arguments)},_resetSelected:function(t,e){var i=this.option,n=this._pieceList,o=(e?i:t).selected||{};if(i.selected=o,d(n,function(t,e){var i=this.getSelectedMapKey(t);o.hasOwnProperty(i)||(o[i]=!0)},this),"single"===i.selectedMode){var a=!1;d(n,function(t,e){var i=this.getSelectedMapKey(t);o[i]&&(a?o[i]=!1:a=!0)},this)}},getSelectedMapKey:function(t){return"categories"===this._mode?t.value+"":t.index+""},getPieceList:function(){return this._pieceList},_determineMode:function(){var t=this.option;return t.pieces&&t.pieces.length>0?"pieces":this.option.categories?"categories":"splitNumber"},setSelected:function(t){this.option.selected=i(t)},getValueState:function(t){var e=hL.findPieceIndex(t,this._pieceList);return null!=e&&this.option.selected[this.getSelectedMapKey(this._pieceList[e])]?"inRange":"outOfRange"},findTargetDataIndices:function(t){var e=[];return this.eachTargetSeries(function(i){var n=[],o=i.getData();o.each(this.getDataDimension(o),function(e,i){hL.findPieceIndex(e,this._pieceList)===t&&n.push(i)},this),e.push({seriesId:i.id,dataIndex:n})},this),e},getRepresentValue:function(t){var e;if(this.isCategory())e=t.value;else if(null!=t.value)e=t.value;else{var i=t.interval||[];e=i[0]===-1/0&&i[1]===1/0?0:(i[0]+i[1])/2}return e},getVisualMeta:function(t){function e(e,a){var r=o.getRepresentValue({interval:e});a||(a=o.getValueState(r));var s=t(r,a);e[0]===-1/0?n[0]=s:e[1]===1/0?n[1]=s:i.push({value:e[0],color:s},{value:e[1],color:s})}if(!this.isCategory()){var i=[],n=[],o=this,a=this._pieceList.slice();if(a.length){var r=a[0].interval[0];r!==-1/0&&a.unshift({interval:[-1/0,r]}),(r=a[a.length-1].interval[1])!==1/0&&a.push({interval:[r,1/0]})}else a.push({interval:[-1/0,1/0]});var s=-1/0;return d(a,function(t){var i=t.interval;i&&(i[0]>s&&e([s,i[0]],"outOfRange"),e(i.slice()),s=i[1])},this),{stops:i,outerColors:n}}}}),WO={splitNumber:function(){var t=this.option,e=this._pieceList,i=Math.min(t.precision,20),n=this.getExtent(),o=t.splitNumber;o=Math.max(parseInt(o,10),1),t.splitNumber=o;for(var a=(n[1]-n[0])/o;+a.toFixed(i)!==a&&i<5;)i++;t.precision=i,a=+a.toFixed(i);var r=0;t.minOpen&&e.push({index:r++,interval:[-1/0,n[0]],close:[0,0]});for(var s=n[0],l=r+o;r","≥"][e[0]]];t.text=t.text||this.formatValueText(null!=t.value?t.value:t.interval,!1,i)},this)}};NO.extend({type:"visualMap.piecewise",doRender:function(){var t=this.group;t.removeAll();var e=this.visualMapModel,i=e.get("textGap"),n=e.textStyleModel,o=n.getFont(),a=n.getTextColor(),r=this._getItemAlign(),s=e.itemSize,l=this._getViewData(),u=l.endsText,h=T(e.get("showLabel",!0),!u);u&&this._renderEndsText(t,u[0],s,h,r),d(l.viewPieceList,function(n){var l=n.piece,u=new tb;u.onclick=m(this._onItemClick,this,l),this._enableHoverLink(u,n.indexInModelPieceList);var c=e.getRepresentValue(l);if(this._createItemSymbol(u,c,[0,0,s[0],s[1]]),h){var d=this.visualMapModel.getValueState(c);u.add(new rM({style:{x:"right"===r?-i:s[0]+i,y:s[1]/2,text:l.text,textVerticalAlign:"middle",textAlign:r,textFont:o,textFill:a,opacity:"outOfRange"===d?.5:1}}))}t.add(u)},this),u&&this._renderEndsText(t,u[1],s,h,r),aI(e.get("orient"),t,e.get("itemGap")),this.renderBackground(t),this.positionGroup(t)},_enableHoverLink:function(t,e){function i(t){var i=this.visualMapModel;i.option.hoverLink&&this.api.dispatchAction({type:t,batch:ex(i.findTargetDataIndices(e))})}t.on("mouseover",m(i,this,"highlight")).on("mouseout",m(i,this,"downplay"))},_getItemAlign:function(){var t=this.visualMapModel,e=t.option;if("vertical"===e.orient)return tx(t,this.api,t.itemSize);var i=e.align;return i&&"auto"!==i||(i="left"),i},_renderEndsText:function(t,e,i,n,o){if(e){var a=new tb,r=this.visualMapModel.textStyleModel;a.add(new rM({style:{x:n?"right"===o?i[0]:0:i[0]/2,y:i[1]/2,textVerticalAlign:"middle",textAlign:n?o:"center",text:e,textFont:r.getFont(),textFill:r.getTextColor()}})),t.add(a)}},_getViewData:function(){var t=this.visualMapModel,e=f(t.getPieceList(),function(t,e){return{piece:t,indexInModelPieceList:e}}),i=t.get("text"),n=t.get("orient"),o=t.get("inverse");return("horizontal"===n?o:!o)?e.reverse():i&&(i=i.slice().reverse()),{viewPieceList:e,endsText:i}},_createItemSymbol:function(t,e,i){t.add(Jl(this.getControllerVisual(e,"symbol"),i[0],i[1],i[2],i[3],this.getControllerVisual(e,"color")))},_onItemClick:function(t){var e=this.visualMapModel,n=e.option,o=i(n.selected),a=e.getSelectedMapKey(t);"single"===n.selectedMode?(o[a]=!0,d(o,function(t,e){o[e]=e===a})):o[a]=!o[a],this.api.dispatchAction({type:"selectDataRange",from:this.uid,visualMapId:this.visualMapModel.id,selected:o})}});Ns(xO);var HO=ta,ZO=ia,UO=Fs({type:"marker",dependencies:["series","grid","polar","geo"],init:function(t,e,i,n){this.mergeDefaultAndTheme(t,i),this.mergeOption(t,i,n.createdBySelf,!0)},isAnimationEnabled:function(){if(U_.node)return!1;var t=this.__hostSeries;return this.getShallow("animation")&&t&&t.isAnimationEnabled()},mergeOption:function(t,e,i,n){var o=this.constructor,r=this.mainType+"Model";i||e.eachSeries(function(t){var i=t.get(this.mainType,!0),s=t[r];i&&i.data?(s?s.mergeOption(i,e,!0):(n&&ux(i),d(i.data,function(t){t instanceof Array?(ux(t[0]),ux(t[1])):ux(t)}),a(s=new o(i,this,e),{mainType:this.mainType,seriesIndex:t.seriesIndex,name:t.name,createdBySelf:!0}),s.__hostSeries=t),t[r]=s):t[r]=null},this)},formatTooltip:function(t){var e=this.getData(),i=this.getRawValue(t),n=y(i)?f(i,HO).join(", "):HO(i),o=e.getName(t),a=ZO(this.name);return(null!=i||o)&&(a+="
      "),o&&(a+=ZO(o),null!=i&&(a+=" : ")),null!=i&&(a+=ZO(n)),a},getData:function(){return this._data},setData:function(t){this._data=t}});h(UO,ZI),UO.extend({type:"markPoint",defaultOption:{zlevel:0,z:5,symbol:"pin",symbolSize:50,tooltip:{trigger:"item"},label:{show:!0,position:"inside"},itemStyle:{borderWidth:2},emphasis:{label:{show:!0}}}});var XO=l,jO=v,YO={min:jO(dx,"min"),max:jO(dx,"max"),average:jO(dx,"average")},qO=Ws({type:"marker",init:function(){this.markerGroupMap=R()},render:function(t,e,i){var n=this.markerGroupMap;n.each(function(t){t.__keep=!1});var o=this.type+"Model";e.eachSeries(function(t){var n=t[o];n&&this.renderSeries(t,n,e,i)},this),n.each(function(t){!t.__keep&&this.group.remove(t.group)},this)},renderSeries:function(){}});qO.extend({type:"markPoint",updateTransform:function(t,e,i){e.eachSeries(function(t){var e=t.markPointModel;e&&(xx(e.getData(),t,i),this.markerGroupMap.get(t.id).updateLayout(e))},this)},renderSeries:function(t,e,i,n){var o=t.coordinateSystem,a=t.id,r=t.getData(),s=this.markerGroupMap,l=s.get(a)||s.set(a,new Du),u=_x(o,t,e);e.setData(u),xx(e.getData(),t,n),u.each(function(t){var i=u.getItemModel(t),n=i.getShallow("symbolSize");"function"==typeof n&&(n=n(e.getRawValue(t),e.getDataParams(t))),u.setItemVisual(t,{symbolSize:n,color:i.get("itemStyle.color")||r.getVisual("color"),symbol:i.getShallow("symbol")})}),l.updateData(u),this.group.add(l.group),u.eachItemGraphicEl(function(t){t.traverse(function(t){t.dataModel=e})}),l.__keep=!0,l.group.silent=e.get("silent")||t.get("silent")}}),Ns(function(t){t.markPoint=t.markPoint||{}}),UO.extend({type:"markLine",defaultOption:{zlevel:0,z:5,symbol:["circle","arrow"],symbolSize:[8,16],precision:2,tooltip:{trigger:"item"},label:{show:!0,position:"end"},lineStyle:{type:"dashed"},emphasis:{label:{show:!0},lineStyle:{width:3}},animationEasing:"linear"}});var KO=function(t,e,o,r){var s=t.getData(),l=r.type;if(!y(r)&&("min"===l||"max"===l||"average"===l||"median"===l||null!=r.xAxis||null!=r.yAxis)){var u,h;if(null!=r.yAxis||null!=r.xAxis)u=null!=r.yAxis?"y":"x",e.getAxis(u),h=T(r.yAxis,r.xAxis);else{var c=px(r,s,e,t);u=c.valueDataDim,c.valueAxis,h=yx(s,u,l)}var d="x"===u?0:1,f=1-d,p=i(r),g={};p.type=null,p.coord=[],g.coord=[],p.coord[f]=-1/0,g.coord[f]=1/0;var m=o.get("precision");m>=0&&"number"==typeof h&&(h=+h.toFixed(Math.min(m,20))),p.coord[d]=g.coord[d]=h,r=[p,g,{type:l,valueIndex:r.valueIndex,value:h}]}return r=[fx(t,r[0]),fx(t,r[1]),a({},r[2])],r[2].type=r[2].type||"",n(r[2],r[0]),n(r[2],r[1]),r};qO.extend({type:"markLine",updateTransform:function(t,e,i){e.eachSeries(function(t){var e=t.markLineModel;if(e){var n=e.getData(),o=e.__from,a=e.__to;o.each(function(e){Ix(o,e,!0,t,i),Ix(a,e,!1,t,i)}),n.each(function(t){n.setItemLayout(t,[o.getItemLayout(t),a.getItemLayout(t)])}),this.markerGroupMap.get(t.id).updateLayout()}},this)},renderSeries:function(t,e,i,n){function o(e,i,o){var a=e.getItemModel(i);Ix(e,i,o,t,n),e.setItemVisual(i,{symbolSize:a.get("symbolSize")||g[o?0:1],symbol:a.get("symbol",!0)||p[o?0:1],color:a.get("itemStyle.color")||s.getVisual("color")})}var a=t.coordinateSystem,r=t.id,s=t.getData(),l=this.markerGroupMap,u=l.get(r)||l.set(r,new sf);this.group.add(u.group);var h=Tx(a,t,e),c=h.from,d=h.to,f=h.line;e.__from=c,e.__to=d,e.setData(f);var p=e.get("symbol"),g=e.get("symbolSize");y(p)||(p=[p,p]),"number"==typeof g&&(g=[g,g]),h.from.each(function(t){o(c,t,!0),o(d,t,!1)}),f.each(function(t){var e=f.getItemModel(t).get("lineStyle.color");f.setItemVisual(t,{color:e||c.getItemVisual(t,"color")}),f.setItemLayout(t,[c.getItemLayout(t),d.getItemLayout(t)]),f.setItemVisual(t,{fromSymbolSize:c.getItemVisual(t,"symbolSize"),fromSymbol:c.getItemVisual(t,"symbol"),toSymbolSize:d.getItemVisual(t,"symbolSize"),toSymbol:d.getItemVisual(t,"symbol")})}),u.updateData(f),h.line.eachItemGraphicEl(function(t,i){t.traverse(function(t){t.dataModel=e})}),u.__keep=!0,u.group.silent=e.get("silent")||t.get("silent")}}),Ns(function(t){t.markLine=t.markLine||{}}),UO.extend({type:"markArea",defaultOption:{zlevel:0,z:1,tooltip:{trigger:"item"},animation:!1,label:{show:!0,position:"top"},itemStyle:{borderWidth:0},emphasis:{label:{show:!0,position:"top"}}}});var $O=function(t,e,i,n){var a=fx(t,n[0]),r=fx(t,n[1]),s=T,l=a.coord,u=r.coord;l[0]=s(l[0],-1/0),l[1]=s(l[1],-1/0),u[0]=s(u[0],1/0),u[1]=s(u[1],1/0);var h=o([{},a,r]);return h.coord=[a.coord,r.coord],h.x0=a.x,h.y0=a.y,h.x1=r.x,h.y1=r.y,h},JO=[["x0","y0"],["x1","y0"],["x1","y1"],["x0","y1"]];qO.extend({type:"markArea",updateTransform:function(t,e,i){e.eachSeries(function(t){var e=t.markAreaModel;if(e){var n=e.getData();n.each(function(e){var o=f(JO,function(o){return Lx(n,e,o,t,i)});n.setItemLayout(e,o),n.getItemGraphicEl(e).setShape("points",o)})}},this)},renderSeries:function(t,e,i,n){var o=t.coordinateSystem,a=t.id,s=t.getData(),l=this.markerGroupMap,u=l.get(a)||l.set(a,{group:new tb});this.group.add(u.group),u.__keep=!0;var h=kx(o,t,e);e.setData(h),h.each(function(e){h.setItemLayout(e,f(JO,function(i){return Lx(h,e,i,t,n)})),h.setItemVisual(e,{color:s.getVisual("color")})}),h.diff(u.__data).add(function(t){var e=new pM({shape:{points:h.getItemLayout(t)}});h.setItemGraphicEl(t,e),u.group.add(e)}).update(function(t,i){var n=u.__data.getItemGraphicEl(i);Io(n,{shape:{points:h.getItemLayout(t)}},e,t),u.group.add(n),h.setItemGraphicEl(t,n)}).remove(function(t){var e=u.__data.getItemGraphicEl(t);u.group.remove(e)}).execute(),h.eachItemGraphicEl(function(t,i){var n=h.getItemModel(i),o=n.getModel("label"),a=n.getModel("emphasis.label"),s=h.getItemVisual(i,"color");t.useStyle(r(n.getModel("itemStyle").getItemStyle(),{fill:Yt(s,.4),stroke:s})),t.hoverStyle=n.getModel("emphasis.itemStyle").getItemStyle(),go(t.style,t.hoverStyle,o,a,{labelFetcher:e,labelDataIndex:i,defaultText:h.getName(i)||"",isRectText:!0,autoColor:s}),fo(t,{}),t.dataModel=e}),u.__data=h,u.group.silent=e.get("silent")||t.get("silent")}}),Ns(function(t){t.markArea=t.markArea||{}});lI.registerSubTypeDefaulter("timeline",function(){return"slider"}),Es({type:"timelineChange",event:"timelineChanged",update:"prepareAndUpdate"},function(t,e){var i=e.getComponent("timeline");return i&&null!=t.currentIndex&&(i.setCurrentIndex(t.currentIndex),!i.get("loop",!0)&&i.isIndexMax()&&i.setPlayState(!1)),e.resetOption("timeline"),r({currentIndex:i.option.currentIndex},t)}),Es({type:"timelinePlayChange",event:"timelinePlayChanged",update:"update"},function(t,e){var i=e.getComponent("timeline");i&&null!=t.playState&&i.setPlayState(t.playState)});var QO=lI.extend({type:"timeline",layoutMode:"box",defaultOption:{zlevel:0,z:4,show:!0,axisType:"time",realtime:!0,left:"20%",top:null,right:"20%",bottom:0,width:null,height:40,padding:5,controlPosition:"left",autoPlay:!1,rewind:!1,loop:!0,playInterval:2e3,currentIndex:0,itemStyle:{},label:{color:"#000"},data:[]},init:function(t,e,i){this._data,this._names,this.mergeDefaultAndTheme(t,i),this._initData()},mergeOption:function(t){QO.superApply(this,"mergeOption",arguments),this._initData()},setCurrentIndex:function(t){null==t&&(t=this.option.currentIndex);var e=this._data.count();this.option.loop?t=(t%e+e)%e:(t>=e&&(t=e-1),t<0&&(t=0)),this.option.currentIndex=t},getCurrentIndex:function(){return this.option.currentIndex},isIndexMax:function(){return this.getCurrentIndex()>=this._data.count()-1},setPlayState:function(t){this.option.autoPlay=!!t},getPlayState:function(){return!!this.option.autoPlay},_initData:function(){var t=this.option,e=t.data||[],n=t.axisType,o=this._names=[];if("category"===n){var a=[];d(e,function(t,e){var n,r=Li(t);w(t)?(n=i(t)).value=e:n=e,a.push(n),_(r)||null!=r&&!isNaN(r)||(r=""),o.push(r+"")}),e=a}var r={category:"ordinal",time:"time"}[n]||"number";(this._data=new vA([{name:"value",type:r}],this)).initData(e,o)},getData:function(){return this._data},getCategories:function(){if("category"===this.get("axisType"))return this._names.slice()}});h(QO.extend({type:"timeline.slider",defaultOption:{backgroundColor:"rgba(0,0,0,0)",borderColor:"#ccc",borderWidth:0,orient:"horizontal",inverse:!1,tooltip:{trigger:"item"},symbol:"emptyCircle",symbolSize:10,lineStyle:{show:!0,width:2,color:"#304654"},label:{position:"auto",show:!0,interval:"auto",rotate:0,color:"#304654"},itemStyle:{color:"#304654",borderWidth:1},checkpointStyle:{symbol:"circle",symbolSize:13,color:"#c23531",borderWidth:5,borderColor:"rgba(194,53,49, 0.5)",animation:!0,animationDuration:300,animationEasing:"quinticInOut"},controlStyle:{show:!0,showPlayBtn:!0,showPrevBtn:!0,showNextBtn:!0,itemSize:22,itemGap:12,position:"left",playIcon:"path://M31.6,53C17.5,53,6,41.5,6,27.4S17.5,1.8,31.6,1.8C45.7,1.8,57.2,13.3,57.2,27.4S45.7,53,31.6,53z M31.6,3.3 C18.4,3.3,7.5,14.1,7.5,27.4c0,13.3,10.8,24.1,24.1,24.1C44.9,51.5,55.7,40.7,55.7,27.4C55.7,14.1,44.9,3.3,31.6,3.3z M24.9,21.3 c0-2.2,1.6-3.1,3.5-2l10.5,6.1c1.899,1.1,1.899,2.9,0,4l-10.5,6.1c-1.9,1.1-3.5,0.2-3.5-2V21.3z",stopIcon:"path://M30.9,53.2C16.8,53.2,5.3,41.7,5.3,27.6S16.8,2,30.9,2C45,2,56.4,13.5,56.4,27.6S45,53.2,30.9,53.2z M30.9,3.5C17.6,3.5,6.8,14.4,6.8,27.6c0,13.3,10.8,24.1,24.101,24.1C44.2,51.7,55,40.9,55,27.6C54.9,14.4,44.1,3.5,30.9,3.5z M36.9,35.8c0,0.601-0.4,1-0.9,1h-1.3c-0.5,0-0.9-0.399-0.9-1V19.5c0-0.6,0.4-1,0.9-1H36c0.5,0,0.9,0.4,0.9,1V35.8z M27.8,35.8 c0,0.601-0.4,1-0.9,1h-1.3c-0.5,0-0.9-0.399-0.9-1V19.5c0-0.6,0.4-1,0.9-1H27c0.5,0,0.9,0.4,0.9,1L27.8,35.8L27.8,35.8z",nextIcon:"path://M18.6,50.8l22.5-22.5c0.2-0.2,0.3-0.4,0.3-0.7c0-0.3-0.1-0.5-0.3-0.7L18.7,4.4c-0.1-0.1-0.2-0.3-0.2-0.5 c0-0.4,0.3-0.8,0.8-0.8c0.2,0,0.5,0.1,0.6,0.3l23.5,23.5l0,0c0.2,0.2,0.3,0.4,0.3,0.7c0,0.3-0.1,0.5-0.3,0.7l-0.1,0.1L19.7,52 c-0.1,0.1-0.3,0.2-0.5,0.2c-0.4,0-0.8-0.3-0.8-0.8C18.4,51.2,18.5,51,18.6,50.8z",prevIcon:"path://M43,52.8L20.4,30.3c-0.2-0.2-0.3-0.4-0.3-0.7c0-0.3,0.1-0.5,0.3-0.7L42.9,6.4c0.1-0.1,0.2-0.3,0.2-0.5 c0-0.4-0.3-0.8-0.8-0.8c-0.2,0-0.5,0.1-0.6,0.3L18.3,28.8l0,0c-0.2,0.2-0.3,0.4-0.3,0.7c0,0.3,0.1,0.5,0.3,0.7l0.1,0.1L41.9,54 c0.1,0.1,0.3,0.2,0.5,0.2c0.4,0,0.8-0.3,0.8-0.8C43.2,53.2,43.1,53,43,52.8z",color:"#304654",borderColor:"#304654",borderWidth:1},emphasis:{label:{show:!0,color:"#c23531"},itemStyle:{color:"#c23531"},controlStyle:{color:"#c23531",borderColor:"#c23531",borderWidth:2}},data:[]}}),ZI);var tE=qI.extend({type:"timeline"}),eE=function(t,e,i,n){aD.call(this,t,e,i),this.type=n||"value",this.model=null};eE.prototype={constructor:eE,getLabelModel:function(){return this.model.getModel("label")},isHorizontal:function(){return"horizontal"===this.model.get("orient")}},u(eE,aD);var iE=m,nE=d,oE=Math.PI;tE.extend({type:"timeline.slider",init:function(t,e){this.api=e,this._axis,this._viewRect,this._timer,this._currentPointer,this._mainGroup,this._labelGroup},render:function(t,e,i,n){if(this.model=t,this.api=i,this.ecModel=e,this.group.removeAll(),t.get("show",!0)){var o=this._layout(t,i),a=this._createGroup("mainGroup"),r=this._createGroup("labelGroup"),s=this._axis=this._createAxis(o,t);t.formatTooltip=function(t){return ia(s.scale.getLabel(t))},nE(["AxisLine","AxisTick","Control","CurrentPointer"],function(e){this["_render"+e](o,a,s,t)},this),this._renderAxisLabel(o,r,s,t),this._position(o,t)}this._doPlayStop()},remove:function(){this._clearTimer(),this.group.removeAll()},dispose:function(){this._clearTimer()},_layout:function(t,e){var i=t.get("label.position"),n=t.get("orient"),o=Ex(t,e);null==i||"auto"===i?i="horizontal"===n?o.y+o.height/2=0||"+"===i?"left":"right"},r={horizontal:i>=0||"+"===i?"top":"bottom",vertical:"middle"},s={horizontal:0,vertical:oE/2},l="vertical"===n?o.height:o.width,u=t.getModel("controlStyle"),h=u.get("show",!0),c=h?u.get("itemSize"):0,d=h?u.get("itemGap"):0,f=c+d,p=t.get("label.rotate")||0;p=p*oE/180;var g,m,v,y,x=u.get("position",!0),_=h&&u.get("showPlayBtn",!0),w=h&&u.get("showPrevBtn",!0),b=h&&u.get("showNextBtn",!0),S=0,M=l;return"left"===x||"bottom"===x?(_&&(g=[0,0],S+=f),w&&(m=[S,0],S+=f),b&&(v=[M-c,0],M-=f)):(_&&(g=[M-c,0],M-=f),w&&(m=[0,0],S+=f),b&&(v=[M-c,0],M-=f)),y=[S,M],t.get("inverse")&&y.reverse(),{viewRect:o,mainLength:l,orient:n,rotation:s[n],labelRotation:p,labelPosOpt:i,labelAlign:t.get("label.align")||a[n],labelBaseline:t.get("label.verticalAlign")||t.get("label.baseline")||r[n],playPosition:g,prevBtnPosition:m,nextBtnPosition:v,axisExtent:y,controlSize:c,controlGap:d}},_position:function(t,e){function i(t){var e=t.position;t.origin=[c[0][0]-e[0],c[1][0]-e[1]]}function n(t){return[[t.x,t.x+t.width],[t.y,t.y+t.height]]}function o(t,e,i,n,o){t[n]+=i[n][o]-e[n][o]}var a=this._mainGroup,r=this._labelGroup,s=t.viewRect;if("vertical"===t.orient){var l=xt(),u=s.x,h=s.y+s.height;St(l,l,[-u,-h]),Mt(l,l,-oE/2),St(l,l,[u,h]),(s=s.clone()).applyTransform(l)}var c=n(s),d=n(a.getBoundingRect()),f=n(r.getBoundingRect()),p=a.position,g=r.position;g[0]=p[0]=c[0][0];var m=t.labelPosOpt;if(isNaN(m))o(p,d,c,1,v="+"===m?0:1),o(g,f,c,1,1-v);else{var v=m>=0?0:1;o(p,d,c,1,v),g[1]=p[1]+m}a.attr("position",p),r.attr("position",g),a.rotation=r.rotation=t.rotation,i(a),i(r)},_createAxis:function(t,e){var i=e.getData(),n=e.get("axisType"),o=Hl(e,n);o.getTicks=function(){return i.mapArray(["value"],function(t){return t})};var a=i.getDataExtent("value");o.setExtent(a[0],a[1]),o.niceTicks();var r=new eE("value",o,t.axisExtent,n);return r.model=e,r},_createGroup:function(t){var e=this["_"+t]=new tb;return this.group.add(e),e},_renderAxisLine:function(t,e,i,n){var o=i.getExtent();n.get("lineStyle.show")&&e.add(new _M({shape:{x1:o[0],y1:0,x2:o[1],y2:0},style:a({lineCap:"round"},n.getModel("lineStyle").getLineStyle()),silent:!0,z2:1}))},_renderAxisTick:function(t,e,i,n){var o=n.getData(),a=i.scale.getTicks();nE(a,function(t){var a=i.dataToCoord(t),r=o.getItemModel(t),s=r.getModel("itemStyle"),l=r.getModel("emphasis.itemStyle"),u={position:[a,0],onclick:iE(this._changeTimeline,this,t)},h=zx(r,s,e,u);fo(h,l.getItemStyle()),r.get("tooltip")?(h.dataIndex=t,h.dataModel=n):h.dataIndex=h.dataModel=null},this)},_renderAxisLabel:function(t,e,i,n){if(i.getLabelModel().get("show")){var o=n.getData(),a=i.getViewLabels();nE(a,function(n){var a=n.tickValue,r=o.getItemModel(a),s=r.getModel("label"),l=r.getModel("emphasis.label"),u=i.dataToCoord(n.tickValue),h=new rM({position:[u,0],rotation:t.labelRotation-t.rotation,onclick:iE(this._changeTimeline,this,a),silent:!1});mo(h.style,s,{text:n.formattedLabel,textAlign:t.labelAlign,textVerticalAlign:t.labelBaseline}),e.add(h),fo(h,mo({},l))},this)}},_renderControl:function(t,e,i,n){function o(t,i,o,h){if(t){var c=Rx(n,i,u,{position:t,origin:[a/2,0],rotation:h?-r:0,rectHover:!0,style:s,onclick:o});e.add(c),fo(c,l)}}var a=t.controlSize,r=t.rotation,s=n.getModel("controlStyle").getItemStyle(),l=n.getModel("emphasis.controlStyle").getItemStyle(),u=[0,-a/2,a,a],h=n.getPlayState(),c=n.get("inverse",!0);o(t.nextBtnPosition,"controlStyle.nextIcon",iE(this._changeTimeline,this,c?"-":"+")),o(t.prevBtnPosition,"controlStyle.prevIcon",iE(this._changeTimeline,this,c?"+":"-")),o(t.playPosition,"controlStyle."+(h?"stopIcon":"playIcon"),iE(this._handlePlayClick,this,!h),!0)},_renderCurrentPointer:function(t,e,i,n){var o=n.getData(),a=n.getCurrentIndex(),r=o.getItemModel(a).getModel("checkpointStyle"),s=this,l={onCreate:function(t){t.draggable=!0,t.drift=iE(s._handlePointerDrag,s),t.ondragend=iE(s._handlePointerDragend,s),Bx(t,a,i,n,!0)},onUpdate:function(t){Bx(t,a,i,n)}};this._currentPointer=zx(r,r,this._mainGroup,{},this._currentPointer,l)},_handlePlayClick:function(t){this._clearTimer(),this.api.dispatchAction({type:"timelinePlayChange",playState:t,from:this.uid})},_handlePointerDrag:function(t,e,i){this._clearTimer(),this._pointerChangeTimeline([i.offsetX,i.offsetY])},_handlePointerDragend:function(t){this._pointerChangeTimeline([t.offsetX,t.offsetY],!0)},_pointerChangeTimeline:function(t,e){var i=this._toAxisCoord(t)[0],n=Fo(this._axis.getExtent().slice());i>n[1]&&(i=n[1]),ii.getHeight()&&(n.textPosition="top",l=!0);var u=l?-5-o.height:s+8;a+o.width/2>i.getWidth()?(n.textPosition=["100%",u],n.textAlign="right"):a-o.width/2<0&&(n.textPosition=[0,u],n.textAlign="left")}})}},updateView:function(t,e,i,n){d(this._features,function(t){t.updateView&&t.updateView(t.model,e,i,n)})},remove:function(t,e){d(this._features,function(i){i.remove&&i.remove(t,e)}),this.group.removeAll()},dispose:function(t,e){d(this._features,function(i){i.dispose&&i.dispose(t,e)})}});var rE=rT.toolbox.saveAsImage;Gx.defaultOption={show:!0,icon:"M4.7,22.9L29.3,45.5L54.7,23.4M4.6,43.6L4.6,58L53.8,58L53.8,43.6M29.2,45.1L29.2,0",title:rE.title,type:"png",name:"",excludeComponents:["toolbox"],pixelRatio:1,lang:rE.lang.slice()},Gx.prototype.unusable=!U_.canvasSupported,Gx.prototype.onclick=function(t,e){var i=this.model,n=i.get("name")||t.get("title.0.text")||"echarts",o=document.createElement("a"),a=i.get("type",!0)||"png";o.download=n+"."+a,o.target="_blank";var r=e.getConnectedDataURL({type:a,backgroundColor:i.get("backgroundColor",!0)||t.get("backgroundColor")||"#fff",excludeComponents:i.get("excludeComponents"),pixelRatio:i.get("pixelRatio")});if(o.href=r,"function"!=typeof MouseEvent||U_.browser.ie||U_.browser.edge)if(window.navigator.msSaveOrOpenBlob){for(var s=atob(r.split(",")[1]),l=s.length,u=new Uint8Array(l);l--;)u[l]=s.charCodeAt(l);var h=new Blob([u]);window.navigator.msSaveOrOpenBlob(h,n+"."+a)}else{var c=i.get("lang"),d='';window.open().document.write(d)}else{var f=new MouseEvent("click",{view:window,bubbles:!0,cancelable:!1});o.dispatchEvent(f)}},Ty("saveAsImage",Gx);var sE=rT.toolbox.magicType;Fx.defaultOption={show:!0,type:[],icon:{line:"M4.1,28.9h7.1l9.3-22l7.4,38l9.7-19.7l3,12.8h14.9M4.1,58h51.4",bar:"M6.7,22.9h10V48h-10V22.9zM24.9,13h10v35h-10V13zM43.2,2h10v46h-10V2zM3.1,58h53.7",stack:"M8.2,38.4l-8.4,4.1l30.6,15.3L60,42.5l-8.1-4.1l-21.5,11L8.2,38.4z M51.9,30l-8.1,4.2l-13.4,6.9l-13.9-6.9L8.2,30l-8.4,4.2l8.4,4.2l22.2,11l21.5-11l8.1-4.2L51.9,30z M51.9,21.7l-8.1,4.2L35.7,30l-5.3,2.8L24.9,30l-8.4-4.1l-8.3-4.2l-8.4,4.2L8.2,30l8.3,4.2l13.9,6.9l13.4-6.9l8.1-4.2l8.1-4.1L51.9,21.7zM30.4,2.2L-0.2,17.5l8.4,4.1l8.3,4.2l8.4,4.2l5.5,2.7l5.3-2.7l8.1-4.2l8.1-4.2l8.1-4.1L30.4,2.2z",tiled:"M2.3,2.2h22.8V25H2.3V2.2z M35,2.2h22.8V25H35V2.2zM2.3,35h22.8v22.8H2.3V35z M35,35h22.8v22.8H35V35z"},title:i(sE.title),option:{},seriesIndex:{}};var lE=Fx.prototype;lE.getIcons=function(){var t=this.model,e=t.get("icon"),i={};return d(t.get("type"),function(t){e[t]&&(i[t]=e[t])}),i};var uE={line:function(t,e,i,o){if("bar"===t)return n({id:e,type:"line",data:i.get("data"),stack:i.get("stack"),markPoint:i.get("markPoint"),markLine:i.get("markLine")},o.get("option.line")||{},!0)},bar:function(t,e,i,o){if("line"===t)return n({id:e,type:"bar",data:i.get("data"),stack:i.get("stack"),markPoint:i.get("markPoint"),markLine:i.get("markLine")},o.get("option.bar")||{},!0)},stack:function(t,e,i,o){if("line"===t||"bar"===t)return n({id:e,stack:"__ec_magicType_stack__"},o.get("option.stack")||{},!0)},tiled:function(t,e,i,o){if("line"===t||"bar"===t)return n({id:e,stack:""},o.get("option.tiled")||{},!0)}},hE=[["line","bar"],["stack","tiled"]];lE.onclick=function(t,e,i){var n=this.model,o=n.get("seriesIndex."+i);if(uE[i]){var a={series:[]};d(hE,function(t){l(t,i)>=0&&d(t,function(t){n.setIconStatus(t,"normal")})}),n.setIconStatus(i,"emphasis"),t.eachComponent({mainType:"series",query:null==o?null:{seriesIndex:o}},function(e){var o=e.subType,s=e.id,l=uE[i](o,s,e,n);l&&(r(l,e.option),a.series.push(l));var u=e.coordinateSystem;if(u&&"cartesian2d"===u.type&&("line"===i||"bar"===i)){var h=u.getAxesByScale("ordinal")[0];if(h){var c=h.dim+"Axis",d=t.queryComponents({mainType:c,index:e.get(name+"Index"),id:e.get(name+"Id")})[0].componentIndex;a[c]=a[c]||[];for(var f=0;f<=d;f++)a[c][d]=a[c][d]||{};a[c][d].boundaryGap="bar"===i}}}),e.dispatchAction({type:"changeMagicType",currentType:i,newOption:a})}},Es({type:"changeMagicType",event:"magicTypeChanged",update:"prepareAndUpdate"},function(t,e){e.mergeOption(t.newOption)}),Ty("magicType",Fx);var cE=rT.toolbox.dataView,dE=new Array(60).join("-"),fE="\t",pE=new RegExp("["+fE+"]+","g");$x.defaultOption={show:!0,readOnly:!1,optionToContent:null,contentToOption:null,icon:"M17.5,17.3H33 M17.5,17.3H33 M45.4,29.5h-28 M11.5,2v56H51V14.8L38.4,2H11.5z M38.4,2.2v12.7H51 M45.4,41.7h-28",title:i(cE.title),lang:i(cE.lang),backgroundColor:"#fff",textColor:"#000",textareaColor:"#fff",textareaBorderColor:"#333",buttonColor:"#c23531",buttonTextColor:"#fff"},$x.prototype.onclick=function(t,e){function i(){n.removeChild(a),x._dom=null}var n=e.getDom(),o=this.model;this._dom&&n.removeChild(this._dom);var a=document.createElement("div");a.style.cssText="position:absolute;left:5px;top:5px;bottom:5px;right:5px;",a.style.backgroundColor=o.get("backgroundColor")||"#fff";var r=document.createElement("h4"),s=o.get("lang")||[];r.innerHTML=s[0]||o.get("title"),r.style.cssText="margin: 10px 20px;",r.style.color=o.get("textColor");var l=document.createElement("div"),u=document.createElement("textarea");l.style.cssText="display:block;width:100%;overflow:auto;";var h=o.get("optionToContent"),c=o.get("contentToOption"),d=Ux(t);if("function"==typeof h){var f=h(e.getOption());"string"==typeof f?l.innerHTML=f:M(f)&&l.appendChild(f)}else l.appendChild(u),u.readOnly=o.get("readOnly"),u.style.cssText="width:100%;height:100%;font-family:monospace;font-size:14px;line-height:1.6rem;",u.style.color=o.get("textColor"),u.style.borderColor=o.get("textareaBorderColor"),u.style.backgroundColor=o.get("textareaColor"),u.value=d.value;var p=d.meta,g=document.createElement("div");g.style.cssText="position:absolute;bottom:0;left:0;right:0;";var m="float:right;margin-right:20px;border:none;cursor:pointer;padding:2px 5px;font-size:12px;border-radius:3px",v=document.createElement("div"),y=document.createElement("div");m+=";background-color:"+o.get("buttonColor"),m+=";color:"+o.get("buttonTextColor");var x=this;ht(v,"click",i),ht(y,"click",function(){var t;try{t="function"==typeof c?c(l,e.getOption()):Kx(u.value,p)}catch(t){throw i(),new Error("Data view format error "+t)}t&&e.dispatchAction({type:"changeDataView",newOption:t}),i()}),v.innerHTML=s[1],y.innerHTML=s[2],y.style.cssText=m,v.style.cssText=m,!o.get("readOnly")&&g.appendChild(y),g.appendChild(v),ht(u,"keydown",function(t){if(9===(t.keyCode||t.which)){var e=this.value,i=this.selectionStart,n=this.selectionEnd;this.value=e.substring(0,i)+fE+e.substring(n),this.selectionStart=this.selectionEnd=i+1,mw(t)}}),a.appendChild(r),a.appendChild(l),a.appendChild(g),l.style.height=n.clientHeight-80+"px",n.appendChild(a),this._dom=a},$x.prototype.remove=function(t,e){this._dom&&e.getDom().removeChild(this._dom)},$x.prototype.dispose=function(t,e){this.remove(t,e)},Ty("dataView",$x),Es({type:"changeDataView",event:"dataViewChanged",update:"prepareAndUpdate"},function(t,e){var i=[];d(t.newOption.series,function(t){var n=e.getSeriesByName(t.name)[0];if(n){var o=n.get("data");i.push({name:t.name,data:Jx(t.data,o)})}else i.push(a({type:"scatter"},t))}),e.mergeOption(r({series:i},t.newOption))});var gE=d,mE="\0_ec_hist_store";iO.extend({type:"dataZoom.select"}),nO.extend({type:"dataZoom.select"});var vE=rT.toolbox.dataZoom,yE=d,xE="\0_ec_\0toolbox-dataZoom_";o_.defaultOption={show:!0,icon:{zoom:"M0,13.5h26.9 M13.5,26.9V0 M32.1,13.5H58V58H13.5 V32.1",back:"M22,1.4L9.9,13.5l12.3,12.3 M10.3,13.5H54.9v44.6 H10.3v-26"},title:i(vE.title)};var _E=o_.prototype;_E.render=function(t,e,i,n){this.model=t,this.ecModel=e,this.api=i,s_(t,e,this,n,i),r_(t,e)},_E.onclick=function(t,e,i){wE[i].call(this)},_E.remove=function(t,e){this._brushController.unmount()},_E.dispose=function(t,e){this._brushController.dispose()};var wE={zoom:function(){var t=!this._isZoomActive;this.api.dispatchAction({type:"takeGlobalCursor",key:"dataZoomSelect",dataZoomSelectActive:t})},back:function(){this._dispatchZoomAction(t_(this.ecModel))}};_E._onBrush=function(t,e){function i(t,e,i){var r=e.getAxis(t),s=r.model,l=n(t,s,a),u=l.findRepresentativeAxisProxy(s).getMinMaxSpan();null==u.minValueSpan&&null==u.maxValueSpan||(i=QL(0,i.slice(),r.scale.getExtent(),0,u.minValueSpan,u.maxValueSpan)),l&&(o[l.id]={dataZoomId:l.id,startValue:i[0],endValue:i[1]})}function n(t,e,i){var n;return i.eachComponent({mainType:"dataZoom",subType:"select"},function(i){i.getAxisModel(t,e.componentIndex)&&(n=i)}),n}if(e.isEnd&&t.length){var o={},a=this.ecModel;this._brushController.updateCovers([]),new hy(a_(this.model.option),a,{include:["grid"]}).matchOutputRanges(t,a,function(t,e,n){if("cartesian2d"===n.type){var o=t.brushType;"rect"===o?(i("x",n,e[0]),i("y",n,e[1])):i({lineX:"x",lineY:"y"}[o],n,e)}}),Qx(a,o),this._dispatchZoomAction(o)}},_E._dispatchZoomAction=function(t){var e=[];yE(t,function(t,n){e.push(i(t))}),e.length&&this.api.dispatchAction({type:"dataZoom",from:this.uid,batch:e})},Ty("dataZoom",o_),Ns(function(t){function e(t,e){if(e){var o=t+"Index",a=e[o];null==a||"all"===a||y(a)||(a=!1===a||"none"===a?[]:[a]),i(t,function(e,i){if(null==a||"all"===a||-1!==l(a,i)){var r={type:"select",$fromToolbox:!0,id:xE+t+i};r[o]=i,n.push(r)}})}}function i(e,i){var n=t[e];y(n)||(n=n?[n]:[]),yE(n,i)}if(t){var n=t.dataZoom||(t.dataZoom=[]);y(n)||(t.dataZoom=n=[n]);var o=t.toolbox;if(o&&(y(o)&&(o=o[0]),o&&o.feature)){var a=o.feature.dataZoom;e("xAxis",a),e("yAxis",a)}}});var bE=rT.toolbox.restore;l_.defaultOption={show:!0,icon:"M3.8,33.4 M47,18.9h9.8V8.7 M56.3,20.1 C52.1,9,40.5,0.6,26.8,2.1C12.6,3.7,1.6,16.2,2.1,30.6 M13,41.1H3.1v10.2 M3.7,39.9c4.2,11.1,15.8,19.5,29.5,18 c14.2-1.6,25.2-14.1,24.7-28.5",title:bE.title},l_.prototype.onclick=function(t,e,i){e_(t),e.dispatchAction({type:"restore",from:this.uid})},Ty("restore",l_),Es({type:"restore",event:"restore",update:"prepareAndUpdate"},function(t,e){e.resetOption("recreate")});var SE,ME="urn:schemas-microsoft-com:vml",IE="undefined"==typeof window?null:window,TE=!1,AE=IE&&IE.document;if(AE&&!U_.canvasSupported)try{!AE.namespaces.zrvml&&AE.namespaces.add("zrvml",ME),SE=function(t){return AE.createElement("')}}catch(t){SE=function(t){return AE.createElement("<"+t+' xmlns="'+ME+'" class="zrvml">')}}var DE=ES.CMD,CE=Math.round,LE=Math.sqrt,kE=Math.abs,PE=Math.cos,NE=Math.sin,OE=Math.max;if(!U_.canvasSupported){var EE=21600,RE=EE/2,zE=function(t){t.style.cssText="position:absolute;left:0;top:0;width:1px;height:1px;",t.coordsize=EE+","+EE,t.coordorigin="0,0"},BE=function(t){return String(t).replace(/&/g,"&").replace(/"/g,""")},VE=function(t,e,i){return"rgb("+[t,e,i].join(",")+")"},GE=function(t,e){e&&t&&e.parentNode!==t&&t.appendChild(e)},FE=function(t,e){e&&t&&e.parentNode===t&&t.removeChild(e)},WE=function(t,e,i){return 1e5*(parseFloat(t)||0)+1e3*(parseFloat(e)||0)+i},HE=function(t,e){return"string"==typeof t?t.lastIndexOf("%")>=0?parseFloat(t)/100*e:parseFloat(t):t},ZE=function(t,e,i){var n=Gt(e);i=+i,isNaN(i)&&(i=1),n&&(t.color=VE(n[0],n[1],n[2]),t.opacity=i*n[3])},UE=function(t){var e=Gt(t);return[VE(e[0],e[1],e[2]),e[3]]},XE=function(t,e,i){var n=e.fill;if(null!=n)if(n instanceof IM){var o,a=0,r=[0,0],s=0,l=1,u=i.getBoundingRect(),h=u.width,c=u.height;if("linear"===n.type){o="gradient";var d=i.transform,f=[n.x*h,n.y*c],p=[n.x2*h,n.y2*c];d&&(Q(f,f,d),Q(p,p,d));var g=p[0]-f[0],m=p[1]-f[1];(a=180*Math.atan2(g,m)/Math.PI)<0&&(a+=360),a<1e-6&&(a=0)}else{o="gradientradial";var f=[n.x*h,n.y*c],d=i.transform,v=i.scale,y=h,x=c;r=[(f[0]-u.x)/y,(f[1]-u.y)/x],d&&Q(f,f,d),y/=v[0]*EE,x/=v[1]*EE;var _=OE(y,x);s=0/_,l=2*n.r/_-s}var w=n.colorStops.slice();w.sort(function(t,e){return t.offset-e.offset});for(var b=w.length,S=[],M=[],I=0;I=2){var D=S[0][0],C=S[1][0],L=S[0][1]*e.opacity,k=S[1][1]*e.opacity;t.type=o,t.method="none",t.focus="100%",t.angle=a,t.color=D,t.color2=C,t.colors=M.join(","),t.opacity=k,t.opacity2=L}"radial"===o&&(t.focusposition=r.join(","))}else ZE(t,n,e.opacity)},jE=function(t,e){null!=e.lineDash&&(t.dashstyle=e.lineDash.join(" ")),null==e.stroke||e.stroke instanceof IM||ZE(t,e.stroke,e.opacity)},YE=function(t,e,i,n){var o="fill"===e,a=t.getElementsByTagName(e)[0];null!=i[e]&&"none"!==i[e]&&(o||!o&&i.lineWidth)?(t[o?"filled":"stroked"]="true",i[e]instanceof IM&&FE(t,a),a||(a=u_(e)),o?XE(a,i,n):jE(a,i),GE(t,a)):(t[o?"filled":"stroked"]="false",FE(t,a))},qE=[[],[],[]],KE=function(t,e){var i,n,o,a,r,s,l=DE.M,u=DE.C,h=DE.L,c=DE.A,d=DE.Q,f=[],p=t.data,g=t.len();for(a=0;a.01?N&&(O+=.0125):Math.abs(E-D)<1e-4?N&&OA?x-=.0125:x+=.0125:N&&ED?y+=.0125:y-=.0125),f.push(R,CE(((A-C)*M+b)*EE-RE),",",CE(((D-L)*I+S)*EE-RE),",",CE(((A+C)*M+b)*EE-RE),",",CE(((D+L)*I+S)*EE-RE),",",CE((O*M+b)*EE-RE),",",CE((E*I+S)*EE-RE),",",CE((y*M+b)*EE-RE),",",CE((x*I+S)*EE-RE)),r=y,s=x;break;case DE.R:var z=qE[0],B=qE[1];z[0]=p[a++],z[1]=p[a++],B[0]=z[0]+p[a++],B[1]=z[1]+p[a++],e&&(Q(z,z,e),Q(B,B,e)),z[0]=CE(z[0]*EE-RE),B[0]=CE(B[0]*EE-RE),z[1]=CE(z[1]*EE-RE),B[1]=CE(B[1]*EE-RE),f.push(" m ",z[0],",",z[1]," l ",B[0],",",z[1]," l ",B[0],",",B[1]," l ",z[0],",",B[1]);break;case DE.Z:f.push(" x ")}if(i>0){f.push(n);for(var V=0;V100&&(tR=0,QE={});var i,n=eR.style;try{n.font=t,i=n.fontFamily.split(",")[0]}catch(t){}e={style:n.fontStyle||"normal",variant:n.fontVariant||"normal",weight:n.fontWeight||"normal",size:0|parseFloat(n.fontSize||12),family:i||"Microsoft YaHei"},QE[t]=e,tR++}return e};!function(t,e){bb[t]=e}("measureText",function(t,e){var i=AE;JE||((JE=i.createElement("div")).style.cssText="position:absolute;top:-20000px;left:0;padding:0;margin:0;border:none;white-space:pre;",AE.body.appendChild(JE));try{JE.style.font=e}catch(t){}return JE.innerHTML="",JE.appendChild(i.createTextNode(t)),{width:JE.offsetWidth}});for(var nR=new de,oR=[Db,di,fi,Pn,rM],aR=0;aR=o&&u+1>=a){for(var h=[],c=0;c=o&&c+1>=a)return T_(0,s.components);l[i]=s}else l[i]=void 0}r++}();if(d)return d}},pushComponent:function(t,e,i){var n=t[t.length-1];n&&n.added===e&&n.removed===i?t[t.length-1]={count:n.count+1,added:e,removed:i}:t.push({count:1,added:e,removed:i})},extractCommon:function(t,e,i,n){for(var o=e.length,a=i.length,r=t.newPos,s=r-n,l=0;r+1=0;--n)if(e[n]===t)return!0;return!1}),i):null:i[0]},D_.prototype.update=function(t,e){if(t){var i=this.getDefs(!1);if(t[this._domName]&&i.contains(t[this._domName]))"function"==typeof e&&e(t);else{var n=this.add(t);n&&(t[this._domName]=n)}}},D_.prototype.addDom=function(t){this.getDefs(!0).appendChild(t)},D_.prototype.removeDom=function(t){var e=this.getDefs(!1);e&&t[this._domName]&&(e.removeChild(t[this._domName]),t[this._domName]=null)},D_.prototype.getDoms=function(){var t=this.getDefs(!1);if(!t)return[];var e=[];return d(this._tagNames,function(i){var n=t.getElementsByTagName(i);e=e.concat([].slice.call(n))}),e},D_.prototype.markAllUnused=function(){var t=this;d(this.getDoms(),function(e){e[t._markLabel]="0"})},D_.prototype.markUsed=function(t){t&&(t[this._markLabel]="1")},D_.prototype.removeUnused=function(){var t=this.getDefs(!1);if(t){var e=this;d(this.getDoms(),function(i){"1"!==i[e._markLabel]&&t.removeChild(i)})}},D_.prototype.getSvgProxy=function(t){return t instanceof Pn?yR:t instanceof fi?xR:t instanceof rM?_R:yR},D_.prototype.getTextSvgElement=function(t){return t.__textSvgEl},D_.prototype.getSvgElement=function(t){return t.__svgEl},u(C_,D_),C_.prototype.addWithoutUpdate=function(t,e){if(e&&e.style){var i=this;d(["fill","stroke"],function(n){if(e.style[n]&&("linear"===e.style[n].type||"radial"===e.style[n].type)){var o,a=e.style[n],r=i.getDefs(!0);a._dom?(o=a._dom,r.contains(a._dom)||i.addDom(o)):o=i.add(a),i.markUsed(e);var s=o.getAttribute("id");t.setAttribute(n,"url(#"+s+")")}})}},C_.prototype.add=function(t){var e;if("linear"===t.type)e=this.createElement("linearGradient");else{if("radial"!==t.type)return Yw("Illegal gradient type."),null;e=this.createElement("radialGradient")}return t.id=t.id||this.nextId++,e.setAttribute("id","zr"+this._zrId+"-gradient-"+t.id),this.updateDom(t,e),this.addDom(e),e},C_.prototype.update=function(t){var e=this;D_.prototype.update.call(this,t,function(){var i=t.type,n=t._dom.tagName;"linear"===i&&"linearGradient"===n||"radial"===i&&"radialGradient"===n?e.updateDom(t,t._dom):(e.removeDom(t),e.add(t))})},C_.prototype.updateDom=function(t,e){if("linear"===t.type)e.setAttribute("x1",t.x),e.setAttribute("y1",t.y),e.setAttribute("x2",t.x2),e.setAttribute("y2",t.y2);else{if("radial"!==t.type)return void Yw("Illegal gradient type.");e.setAttribute("cx",t.x),e.setAttribute("cy",t.y),e.setAttribute("r",t.r)}t.global?e.setAttribute("gradientUnits","userSpaceOnUse"):e.setAttribute("gradientUnits","objectBoundingBox"),e.innerHTML="";for(var i=t.colorStops,n=0,o=i.length;n0){var n,o,a=this.getDefs(!0),r=e[0],s=i?"_textDom":"_dom";r[s]?(o=r[s].getAttribute("id"),n=r[s],a.contains(n)||a.appendChild(n)):(o="zr"+this._zrId+"-clip-"+this.nextId,++this.nextId,(n=this.createElement("clipPath")).setAttribute("id",o),a.appendChild(n),r[s]=n);var l=this.getSvgProxy(r);if(r.transform&&r.parent.invTransform&&!i){var u=Array.prototype.slice.call(r.transform);bt(r.transform,r.parent.invTransform,r.transform),l.brush(r),r.transform=u}else l.brush(r);var h=this.getSvgElement(r);n.innerHTML="",n.appendChild(h.cloneNode()),t.setAttribute("clip-path","url(#"+o+")"),e.length>1&&this.updateDom(n,e.slice(1),i)}else t&&t.setAttribute("clip-path","none")},L_.prototype.markUsed=function(t){var e=this;t.__clipPaths&&t.__clipPaths.length>0&&d(t.__clipPaths,function(t){t._dom&&D_.prototype.markUsed.call(e,t._dom),t._textDom&&D_.prototype.markUsed.call(e,t._textDom)})},u(k_,D_),k_.prototype.addWithoutUpdate=function(t,e){if(e&&P_(e.style)){var i,n=e.style;n._shadowDom?(i=n._shadowDom,this.getDefs(!0).contains(n._shadowDom)||this.addDom(i)):i=this.add(e),this.markUsed(e);var o=i.getAttribute("id");t.style.filter="url(#"+o+")"}},k_.prototype.add=function(t){var e=this.createElement("filter"),i=t.style;return i._shadowDomId=i._shadowDomId||this.nextId++,e.setAttribute("id","zr"+this._zrId+"-shadow-"+i._shadowDomId),this.updateDom(t,e),this.addDom(e),e},k_.prototype.update=function(t,e){var i=e.style;if(P_(i)){var n=this;D_.prototype.update.call(this,e,function(t){n.updateDom(e,t._shadowDom)})}else this.remove(t,i)},k_.prototype.remove=function(t,e){null!=e._shadowDomId&&(this.removeDom(e),t.style.filter="")},k_.prototype.updateDom=function(t,e){var i=e.getElementsByTagName("feDropShadow");i=0===i.length?this.createElement("feDropShadow"):i[0];var n,o,a,r,s=t.style,l=t.scale?t.scale[0]||1:1,u=t.scale?t.scale[1]||1:1;if(s.shadowBlur||s.shadowOffsetX||s.shadowOffsetY)n=s.shadowOffsetX||0,o=s.shadowOffsetY||0,a=s.shadowBlur,r=s.shadowColor;else{if(!s.textShadowBlur)return void this.removeDom(e,s);n=s.textShadowOffsetX||0,o=s.textShadowOffsetY||0,a=s.textShadowBlur,r=s.textShadowColor}i.setAttribute("dx",n/l),i.setAttribute("dy",o/u),i.setAttribute("flood-color",r);var h=a/2/l+" "+a/2/u;i.setAttribute("stdDeviation",h),e.setAttribute("x","-100%"),e.setAttribute("y","-100%"),e.setAttribute("width",Math.ceil(a/2*200)+"%"),e.setAttribute("height",Math.ceil(a/2*200)+"%"),e.appendChild(i),s._shadowDom=e},k_.prototype.markUsed=function(t){var e=t.style;e&&e._shadowDom&&D_.prototype.markUsed.call(this,e._shadowDom)};var IR=function(t,e,i,n){this.root=t,this.storage=e,this._opts=i=a({},i||{});var o=p_("svg");o.setAttribute("xmlns","http://www.w3.org/2000/svg"),o.setAttribute("version","1.1"),o.setAttribute("baseProfile","full"),o.style.cssText="user-select:none;position:absolute;left:0;top:0;",this.gradientManager=new C_(n,o),this.clipPathManager=new L_(n,o),this.shadowManager=new k_(n,o);var r=document.createElement("div");r.style.cssText="overflow:hidden;position:relative",this._svgRoot=o,this._viewport=r,t.appendChild(r),r.appendChild(o),this.resize(i.width,i.height),this._visibleList=[]};IR.prototype={constructor:IR,getType:function(){return"svg"},getViewportRoot:function(){return this._viewport},getViewportRootOffset:function(){var t=this.getViewportRoot();if(t)return{offsetLeft:t.offsetLeft||0,offsetTop:t.offsetTop||0}},refresh:function(){var t=this.storage.getDisplayList(!0);this._paintList(t)},setBackgroundColor:function(t){this._viewport.style.background=t},_paintList:function(t){this.gradientManager.markAllUnused(),this.clipPathManager.markAllUnused(),this.shadowManager.markAllUnused();var e,i=this._svgRoot,n=this._visibleList,o=t.length,a=[];for(e=0;e=0;--n)if(e[n]===t)return!0;return!1}),i):null:i[0]},resize:function(t,e){var i=this._viewport;i.style.display="none";var n=this._opts;if(null!=t&&(n.width=t),null!=e&&(n.height=e),t=this._getSize(0),e=this._getSize(1),i.style.display="",this._width!==t||this._height!==e){this._width=t,this._height=e;var o=i.style;o.width=t+"px",o.height=e+"px";var a=this._svgRoot;a.setAttribute("width",t),a.setAttribute("height",e)}},getWidth:function(){return this._width},getHeight:function(){return this._height},_getSize:function(t){var e=this._opts,i=["width","height"][t],n=["clientWidth","clientHeight"][t],o=["paddingLeft","paddingTop"][t],a=["paddingRight","paddingBottom"][t];if(null!=e[i]&&"auto"!==e[i])return parseFloat(e[i]);var r=this.root,s=document.defaultView.getComputedStyle(r);return(r[n]||N_(s[i])||N_(r.style[i]))-(N_(s[o])||0)-(N_(s[a])||0)|0},dispose:function(){this.root.innerHTML="",this._svgRoot=this._viewport=this.storage=null},clear:function(){this._viewport&&this.root.removeChild(this._viewport)},pathToDataUrl:function(){return this.refresh(),"data:image/svg+xml;charset=UTF-8,"+this._svgRoot.outerHTML}},d(["getLayer","insertLayer","eachLayer","eachBuiltinLayer","eachOtherLayer","getLayers","modLayer","delLayer","clearLayer","toDataURL","pathToImage"],function(t){IR.prototype[t]=F_(t)}),Ti("svg",IR),t.version="4.2.1",t.dependencies=ET,t.PRIORITY=VT,t.init=function(t,e,i){var n=ks(t);if(n)return n;var o=new us(t,e,i);return o.id="ec_"+iA++,tA[o.id]=o,Fi(t,oA,o.id),Cs(o),o},t.connect=function(t){if(y(t)){var e=t;t=null,kT(e,function(e){null!=e.group&&(t=e.group)}),t=t||"g_"+nA++,kT(e,function(e){e.group=t})}return eA[t]=!0,t},t.disConnect=Ls,t.disconnect=aA,t.dispose=function(t){"string"==typeof t?t=tA[t]:t instanceof us||(t=ks(t)),t instanceof us&&!t.isDisposed()&&t.dispose()},t.getInstanceByDom=ks,t.getInstanceById=function(t){return tA[t]},t.registerTheme=Ps,t.registerPreprocessor=Ns,t.registerProcessor=Os,t.registerPostUpdate=function(t){KT.push(t)},t.registerAction=Es,t.registerCoordinateSystem=Rs,t.getCoordinateSystemDimensions=function(t){var e=Fa.get(t);if(e)return e.getDimensionsInfo?e.getDimensionsInfo():e.dimensions.slice()},t.registerLayout=zs,t.registerVisual=Bs,t.registerLoading=Gs,t.extendComponentModel=Fs,t.extendComponentView=Ws,t.extendSeriesModel=Hs,t.extendChartView=Zs,t.setCanvasCreator=function(t){e("createCanvas",t)},t.registerMap=function(t,e,i){DT.registerMap(t,e,i)},t.getMap=function(t){var e=DT.retrieveMap(t);return e&&e[0]&&{geoJson:e[0].geoJSON,specialAreas:e[0].specialAreas}},t.dataTool=rA,t.zrender=Hb,t.number=YM,t.format=eI,t.throttle=Pr,t.helper=tD,t.matrix=Sw,t.vector=cw,t.color=Ww,t.parseGeoJSON=iD,t.parseGeoJson=rD,t.util=sD,t.graphic=lD,t.List=vA,t.Model=No,t.Axis=aD,t.env=U_}); diff --git a/static/js/jquery.min.js b/static/js/jquery.min.js new file mode 100755 index 00000000..644d35e2 --- /dev/null +++ b/static/js/jquery.min.js @@ -0,0 +1,4 @@ +/*! jQuery v3.2.1 | (c) JS Foundation and other contributors | jquery.org/license */ +!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.2.1",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null==a?f.call(this):a<0?this[a+this.length]:this[a]},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0&&("form"in a||"label"in a)},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"form"in b?b.parentNode&&b.disabled===!1?"label"in b?"label"in b.parentNode?b.parentNode.disabled===a:b.disabled===a:b.isDisabled===a||b.isDisabled!==!a&&ea(b)===a:b.disabled===a:"label"in b&&b.disabled===a}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}}):(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c,d,e,f=b.getElementById(a);if(f){if(c=f.getAttributeNode("id"),c&&c.value===a)return[f];e=b.getElementsByName(a),d=0;while(f=e[d++])if(c=f.getAttributeNode("id"),c&&c.value===a)return[f]}return[]}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,c,e){var f,i,j,k,l,m="function"==typeof a&&a,n=!e&&g(a=m.selector||a);if(c=c||[],1===n.length){if(i=n[0]=n[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&9===b.nodeType&&p&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(_,aa),b)||[])[0],!b)return c;m&&(b=b.parentNode),a=a.slice(i.shift().value.length)}f=V.needsContext.test(a)?0:i.length;while(f--){if(j=i[f],d.relative[k=j.type])break;if((l=d.find[k])&&(e=l(j.matches[0].replace(_,aa),$.test(i[0].type)&&qa(b.parentNode)||b))){if(i.splice(f,1),a=e.length&&sa(i),!a)return G.apply(c,e),c;break}}}return(m||h(a,n))(e,b,!p,c,!b||$.test(a)&&qa(b.parentNode)||b),c},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext;function B(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()}var C=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,D=/^.[^:#\[\.,]*$/;function E(a,b,c){return r.isFunction(b)?r.grep(a,function(a,d){return!!b.call(a,d,a)!==c}):b.nodeType?r.grep(a,function(a){return a===b!==c}):"string"!=typeof b?r.grep(a,function(a){return i.call(b,a)>-1!==c}):D.test(b)?r.filter(b,a,c):(b=r.filter(b,a),r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType}))}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(E(this,a||[],!1))},not:function(a){return this.pushStack(E(this,a||[],!0))},is:function(a){return!!E(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var F,G=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,H=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||F,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:G.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),C.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};H.prototype=r.fn,F=r(d);var I=/^(?:parents|prev(?:Until|All))/,J={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function K(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return K(a,"nextSibling")},prev:function(a){return K(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return B(a,"iframe")?a.contentDocument:(B(a,"template")&&(a=a.content||a),r.merge([],a.childNodes))}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(J[a]||r.uniqueSort(e),I.test(a)&&e.reverse()),this.pushStack(e)}});var L=/[^\x20\t\r\n\f]+/g;function M(a){var b={};return r.each(a.match(L)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?M(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=e||a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function N(a){return a}function O(a){throw a}function P(a,b,c,d){var e;try{a&&r.isFunction(e=a.promise)?e.call(a).done(b).fail(c):a&&r.isFunction(e=a.then)?e.call(a,b,c):b.apply(void 0,[a].slice(d))}catch(a){c.apply(void 0,[a])}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b=f&&(d!==O&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:N,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:N)),c[2][3].add(g(0,a,r.isFunction(d)?d:O))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(P(a,g.done(h(c)).resolve,g.reject,!b),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)P(e[c],h(c),g.reject);return g.promise()}});var Q=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&Q.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var R=r.Deferred();r.fn.ready=function(a){return R.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||R.resolveWith(d,[r]))}}),r.ready.then=R.then;function S(){d.removeEventListener("DOMContentLoaded",S), +a.removeEventListener("load",S),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",S),a.addEventListener("load",S));var T=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)T(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h1,null,!0)},removeData:function(a){return this.each(function(){X.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=W.get(a,b),c&&(!d||Array.isArray(c)?d=W.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return W.get(a,c)||W.access(a,c,{empty:r.Callbacks("once memory").add(function(){W.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length\x20\t\r\n\f]+)/i,la=/^$|\/(?:java|ecma)script/i,ma={option:[1,""],thead:[1,"","
      "],col:[2,"","
      "],tr:[2,"","
      "],td:[3,"","
      "],_default:[0,"",""]};ma.optgroup=ma.option,ma.tbody=ma.tfoot=ma.colgroup=ma.caption=ma.thead,ma.th=ma.td;function na(a,b){var c;return c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[],void 0===b||b&&B(a,b)?r.merge([a],c):c}function oa(a,b){for(var c=0,d=a.length;c-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=na(l.appendChild(f),"script"),j&&oa(g),c){k=0;while(f=g[k++])la.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var ra=d.documentElement,sa=/^key/,ta=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ua=/^([^.]*)(?:\.(.+)|)/;function va(){return!0}function wa(){return!1}function xa(){try{return d.activeElement}catch(a){}}function ya(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ya(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=wa;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(ra,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(L)||[""],j=b.length;while(j--)h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=W.hasData(a)&&W.get(a);if(q&&(i=q.events)){b=(b||"").match(L)||[""],j=b.length;while(j--)if(h=ua.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&W.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(W.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c=1))for(;j!==this;j=j.parentNode||this)if(1===j.nodeType&&("click"!==a.type||j.disabled!==!0)){for(f=[],g={},c=0;c-1:r.find(e,this,null,[j]).length),g[e]&&f.push(d);f.length&&h.push({elem:j,handlers:f})}return j=this,i\x20\t\r\n\f]*)[^>]*)\/>/gi,Aa=/\s*$/g;function Ea(a,b){return B(a,"table")&&B(11!==b.nodeType?b:b.firstChild,"tr")?r(">tbody",a)[0]||a:a}function Fa(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Ga(a){var b=Ca.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ha(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(W.hasData(a)&&(f=W.access(a),g=W.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c1&&"string"==typeof q&&!o.checkClone&&Ba.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ja(f,b,c,d)});if(m&&(e=qa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(na(e,"script"),Fa),i=h.length;l")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=na(h),f=na(a),d=0,e=f.length;d0&&oa(g,!i&&na(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(U(c)){if(b=c[W.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[W.expando]=void 0}c[X.expando]&&(c[X.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ka(this,a,!0)},remove:function(a){return Ka(this,a)},text:function(a){return T(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.appendChild(a)}})},prepend:function(){return Ja(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ea(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ja(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(na(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return T(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!Aa.test(a)&&!ma[(ka.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c1)}});function _a(a,b,c,d,e){return new _a.prototype.init(a,b,c,d,e)}r.Tween=_a,_a.prototype={constructor:_a,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=_a.propHooks[this.prop];return a&&a.get?a.get(this):_a.propHooks._default.get(this)},run:function(a){var b,c=_a.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):_a.propHooks._default.set(this),this}},_a.prototype.init.prototype=_a.prototype,_a.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},_a.propHooks.scrollTop=_a.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=_a.prototype.init,r.fx.step={};var ab,bb,cb=/^(?:toggle|show|hide)$/,db=/queueHooks$/;function eb(){bb&&(d.hidden===!1&&a.requestAnimationFrame?a.requestAnimationFrame(eb):a.setTimeout(eb,r.fx.interval),r.fx.tick())}function fb(){return a.setTimeout(function(){ab=void 0}),ab=r.now()}function gb(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=ca[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function hb(a,b,c){for(var d,e=(kb.tweeners[b]||[]).concat(kb.tweeners["*"]),f=0,g=e.length;f1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?lb:void 0)),void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b), +null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&B(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(L);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),lb={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=mb[b]||r.find.attr;mb[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=mb[g],mb[g]=e,e=null!=c(a,b,d)?g:null,mb[g]=f),e}});var nb=/^(?:input|select|textarea|button)$/i,ob=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return T(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):nb.test(a.nodeName)||ob.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function pb(a){var b=a.match(L)||[];return b.join(" ")}function qb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,qb(this)))});if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,qb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(L)||[];while(c=this[i++])if(e=qb(c),d=1===c.nodeType&&" "+pb(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=pb(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,qb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(L)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=qb(this),b&&W.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":W.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+pb(qb(c))+" ").indexOf(b)>-1)return!0;return!1}});var rb=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":Array.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(rb,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:pb(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(Array.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var sb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!sb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,sb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(W.get(h,"events")||{})[b.type]&&W.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&U(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!U(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=W.access(d,b);e||d.addEventListener(a,c,!0),W.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=W.access(d,b)-1;e?W.access(d,b,e):(d.removeEventListener(a,c,!0),W.remove(d,b))}}});var tb=a.location,ub=r.now(),vb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var wb=/\[\]$/,xb=/\r?\n/g,yb=/^(?:submit|button|image|reset|file)$/i,zb=/^(?:input|select|textarea|keygen)/i;function Ab(a,b,c,d){var e;if(Array.isArray(b))r.each(b,function(b,e){c||wb.test(a)?d(a,e):Ab(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)Ab(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(Array.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)Ab(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&zb.test(this.nodeName)&&!yb.test(a)&&(this.checked||!ja.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:Array.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(xb,"\r\n")}}):{name:b.name,value:c.replace(xb,"\r\n")}}).get()}});var Bb=/%20/g,Cb=/#.*$/,Db=/([?&])_=[^&]*/,Eb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Fb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Gb=/^(?:GET|HEAD)$/,Hb=/^\/\//,Ib={},Jb={},Kb="*/".concat("*"),Lb=d.createElement("a");Lb.href=tb.href;function Mb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(L)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Nb(a,b,c,d){var e={},f=a===Jb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Ob(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Pb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Qb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:tb.href,type:"GET",isLocal:Fb.test(tb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Kb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Ob(Ob(a,r.ajaxSettings),b):Ob(r.ajaxSettings,a)},ajaxPrefilter:Mb(Ib),ajaxTransport:Mb(Jb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Eb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||tb.href)+"").replace(Hb,tb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(L)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Lb.protocol+"//"+Lb.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Nb(Ib,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Gb.test(o.type),f=o.url.replace(Cb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(Bb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(vb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Db,"$1"),n=(vb.test(f)?"&":"?")+"_="+ub++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Kb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Nb(Jb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Pb(o,y,d)),v=Qb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Rb={0:200,1223:204},Sb=r.ajaxSettings.xhr();o.cors=!!Sb&&"withCredentials"in Sb,o.ajax=Sb=!!Sb,r.ajaxTransport(function(b){var c,d;if(o.cors||Sb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Rb[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r(" + + + + +
      + +
      +
      +
      欢迎管理员: + admin!当前时间: +
      +
      +
      +
      +
      +
      +
      最新一周新增用户
      +
      +
      + +
      +
      +
      +
      +
      +
      最新一周PV/UV量
      +
      +
      + +
      +
      +
      +
      +
      +
      用户来源
      +
      +
      + +
      +
      +
      +
      +
      +
      硬盘使用量
      +
      +
      + +
      +
      +
      +
      +
      + + + +
      + +
      + +
      LPeg
      +
      + Parsing Expression Grammars For Lua, version 1.0 +
      +
      + +
      + + + +
      + + +

      Introduction

      + +

      +LPeg is a new pattern-matching library for Lua, +based on + +Parsing Expression Grammars (PEGs). +This text is a reference manual for the library. +For a more formal treatment of LPeg, +as well as some discussion about its implementation, +see + +A Text Pattern-Matching Tool based on Parsing Expression Grammars. +(You may also be interested in my +talk about LPeg +given at the III Lua Workshop.) +

      + +

      +Following the Snobol tradition, +LPeg defines patterns as first-class objects. +That is, patterns are regular Lua values +(represented by userdata). +The library offers several functions to create +and compose patterns. +With the use of metamethods, +several of these functions are provided as infix or prefix +operators. +On the one hand, +the result is usually much more verbose than the typical +encoding of patterns using the so called +regular expressions +(which typically are not regular expressions in the formal sense). +On the other hand, +first-class patterns allow much better documentation +(as it is easy to comment the code, +to break complex definitions in smaller parts, etc.) +and are extensible, +as we can define new functions to create and compose patterns. +

      + +

      +For a quick glance of the library, +the following table summarizes its basic operations +for creating patterns: +

      + + + + + + + + + + + + + + + + + + + + + + + + + + +
      OperatorDescription
      lpeg.P(string)Matches string literally
      lpeg.P(n)Matches exactly n characters
      lpeg.S(string)Matches any character in string (Set)
      lpeg.R("xy")Matches any character between x and y (Range)
      patt^nMatches at least n repetitions of patt
      patt^-nMatches at most n repetitions of patt
      patt1 * patt2Matches patt1 followed by patt2
      patt1 + patt2Matches patt1 or patt2 + (ordered choice)
      patt1 - patt2Matches patt1 if patt2 does not match
      -pattEquivalent to ("" - patt)
      #pattMatches patt but consumes no input
      lpeg.B(patt)Matches patt behind the current position, + consuming no input
      + +

      As a very simple example, +lpeg.R("09")^1 creates a pattern that +matches a non-empty sequence of digits. +As a not so simple example, +-lpeg.P(1) +(which can be written as lpeg.P(-1), +or simply -1 for operations expecting a pattern) +matches an empty string only if it cannot match a single character; +so, it succeeds only at the end of the subject. +

      + +

      +LPeg also offers the re module, +which implements patterns following a regular-expression style +(e.g., [09]+). +(This module is 260 lines of Lua code, +and of course it uses LPeg to parse regular expressions and +translate them to regular LPeg patterns.) +

      + + +

      Functions

      + + +

      lpeg.match (pattern, subject [, init])

      +

      +The matching function. +It attempts to match the given pattern against the subject string. +If the match succeeds, +returns the index in the subject of the first character after the match, +or the captured values +(if the pattern captured any value). +

      + +

      +An optional numeric argument init makes the match +start at that position in the subject string. +As usual in Lua libraries, +a negative value counts from the end. +

      + +

      +Unlike typical pattern-matching functions, +match works only in anchored mode; +that is, it tries to match the pattern with a prefix of +the given subject string (at position init), +not with an arbitrary substring of the subject. +So, if we want to find a pattern anywhere in a string, +we must either write a loop in Lua or write a pattern that +matches anywhere. +This second approach is easy and quite efficient; +see examples. +

      + +

      lpeg.type (value)

      +

      +If the given value is a pattern, +returns the string "pattern". +Otherwise returns nil. +

      + +

      lpeg.version ()

      +

      +Returns a string with the running version of LPeg. +

      + +

      lpeg.setmaxstack (max)

      +

      +Sets a limit for the size of the backtrack stack used by LPeg to +track calls and choices. +(The default limit is 400.) +Most well-written patterns need little backtrack levels and +therefore you seldom need to change this limit; +before changing it you should try to rewrite your +pattern to avoid the need for extra space. +Nevertheless, a few useful patterns may overflow. +Also, with recursive grammars, +subjects with deep recursion may also need larger limits. +

      + + +

      Basic Constructions

      + +

      +The following operations build patterns. +All operations that expect a pattern as an argument +may receive also strings, tables, numbers, booleans, or functions, +which are translated to patterns according to +the rules of function lpeg.P. +

      + + + +

      lpeg.P (value)

      +

      +Converts the given value into a proper pattern, +according to the following rules: +

      +
        + +
      • +If the argument is a pattern, +it is returned unmodified. +

      • + +
      • +If the argument is a string, +it is translated to a pattern that matches the string literally. +

      • + +
      • +If the argument is a non-negative number n, +the result is a pattern that matches exactly n characters. +

      • + +
      • +If the argument is a negative number -n, +the result is a pattern that +succeeds only if the input string has less than n characters left: +lpeg.P(-n) +is equivalent to -lpeg.P(n) +(see the unary minus operation). +

      • + +
      • +If the argument is a boolean, +the result is a pattern that always succeeds or always fails +(according to the boolean value), +without consuming any input. +

      • + +
      • +If the argument is a table, +it is interpreted as a grammar +(see Grammars). +

      • + +
      • +If the argument is a function, +returns a pattern equivalent to a +match-time capture over the empty string. +

      • + +
      + + +

      lpeg.B(patt)

      +

      +Returns a pattern that +matches only if the input string at the current position +is preceded by patt. +Pattern patt must match only strings +with some fixed length, +and it cannot contain captures. +

      + +

      +Like the and predicate, +this pattern never consumes any input, +independently of success or failure. +

      + + +

      lpeg.R ({range})

      +

      +Returns a pattern that matches any single character +belonging to one of the given ranges. +Each range is a string xy of length 2, +representing all characters with code +between the codes of x and y +(both inclusive). +

      + +

      +As an example, the pattern +lpeg.R("09") matches any digit, +and lpeg.R("az", "AZ") matches any ASCII letter. +

      + + +

      lpeg.S (string)

      +

      +Returns a pattern that matches any single character that +appears in the given string. +(The S stands for Set.) +

      + +

      +As an example, the pattern +lpeg.S("+-*/") matches any arithmetic operator. +

      + +

      +Note that, if s is a character +(that is, a string of length 1), +then lpeg.P(s) is equivalent to lpeg.S(s) +which is equivalent to lpeg.R(s..s). +Note also that both lpeg.S("") and lpeg.R() +are patterns that always fail. +

      + + +

      lpeg.V (v)

      +

      +This operation creates a non-terminal (a variable) +for a grammar. +The created non-terminal refers to the rule indexed by v +in the enclosing grammar. +(See Grammars for details.) +

      + + +

      lpeg.locale ([table])

      +

      +Returns a table with patterns for matching some character classes +according to the current locale. +The table has fields named +alnum, +alpha, +cntrl, +digit, +graph, +lower, +print, +punct, +space, +upper, and +xdigit, +each one containing a correspondent pattern. +Each pattern matches any single character that belongs to its class. +

      + +

      +If called with an argument table, +then it creates those fields inside the given table and +returns that table. +

      + + +

      #patt

      +

      +Returns a pattern that +matches only if the input string matches patt, +but without consuming any input, +independently of success or failure. +(This pattern is called an and predicate +and it is equivalent to +&patt in the original PEG notation.) +

      + + +

      +This pattern never produces any capture. +

      + + +

      -patt

      +

      +Returns a pattern that +matches only if the input string does not match patt. +It does not consume any input, +independently of success or failure. +(This pattern is equivalent to +!patt in the original PEG notation.) +

      + +

      +As an example, the pattern +-lpeg.P(1) matches only the end of string. +

      + +

      +This pattern never produces any captures, +because either patt fails +or -patt fails. +(A failing pattern never produces captures.) +

      + + +

      patt1 + patt2

      +

      +Returns a pattern equivalent to an ordered choice +of patt1 and patt2. +(This is denoted by patt1 / patt2 in the original PEG notation, +not to be confused with the / operation in LPeg.) +It matches either patt1 or patt2, +with no backtracking once one of them succeeds. +The identity element for this operation is the pattern +lpeg.P(false), +which always fails. +

      + +

      +If both patt1 and patt2 are +character sets, +this operation is equivalent to set union. +

      +
      +lower = lpeg.R("az")
      +upper = lpeg.R("AZ")
      +letter = lower + upper
      +
      + + +

      patt1 - patt2

      +

      +Returns a pattern equivalent to !patt2 patt1. +This pattern asserts that the input does not match +patt2 and then matches patt1. +

      + +

      +When successful, +this pattern produces all captures from patt1. +It never produces any capture from patt2 +(as either patt2 fails or +patt1 - patt2 fails). +

      + +

      +If both patt1 and patt2 are +character sets, +this operation is equivalent to set difference. +Note that -patt is equivalent to "" - patt +(or 0 - patt). +If patt is a character set, +1 - patt is its complement. +

      + + +

      patt1 * patt2

      +

      +Returns a pattern that matches patt1 +and then matches patt2, +starting where patt1 finished. +The identity element for this operation is the +pattern lpeg.P(true), +which always succeeds. +

      + +

      +(LPeg uses the * operator +[instead of the more obvious ..] +both because it has +the right priority and because in formal languages it is +common to use a dot for denoting concatenation.) +

      + + +

      patt^n

      +

      +If n is nonnegative, +this pattern is +equivalent to pattn patt*: +It matches n or more occurrences of patt. +

      + +

      +Otherwise, when n is negative, +this pattern is equivalent to (patt?)-n: +It matches at most |n| +occurrences of patt. +

      + +

      +In particular, patt^0 is equivalent to patt*, +patt^1 is equivalent to patt+, +and patt^-1 is equivalent to patt? +in the original PEG notation. +

      + +

      +In all cases, +the resulting pattern is greedy with no backtracking +(also called a possessive repetition). +That is, it matches only the longest possible sequence +of matches for patt. +

      + + + +

      Grammars

      + +

      +With the use of Lua variables, +it is possible to define patterns incrementally, +with each new pattern using previously defined ones. +However, this technique does not allow the definition of +recursive patterns. +For recursive patterns, +we need real grammars. +

      + +

      +LPeg represents grammars with tables, +where each entry is a rule. +

      + +

      +The call lpeg.V(v) +creates a pattern that represents the nonterminal +(or variable) with index v in a grammar. +Because the grammar still does not exist when +this function is evaluated, +the result is an open reference to the respective rule. +

      + +

      +A table is fixed when it is converted to a pattern +(either by calling lpeg.P or by using it wherein a +pattern is expected). +Then every open reference created by lpeg.V(v) +is corrected to refer to the rule indexed by v in the table. +

      + +

      +When a table is fixed, +the result is a pattern that matches its initial rule. +The entry with index 1 in the table defines its initial rule. +If that entry is a string, +it is assumed to be the name of the initial rule. +Otherwise, LPeg assumes that the entry 1 itself is the initial rule. +

      + +

      +As an example, +the following grammar matches strings of a's and b's that +have the same number of a's and b's: +

      +
      +equalcount = lpeg.P{
      +  "S";   -- initial rule name
      +  S = "a" * lpeg.V"B" + "b" * lpeg.V"A" + "",
      +  A = "a" * lpeg.V"S" + "b" * lpeg.V"A" * lpeg.V"A",
      +  B = "b" * lpeg.V"S" + "a" * lpeg.V"B" * lpeg.V"B",
      +} * -1
      +
      +

      +It is equivalent to the following grammar in standard PEG notation: +

      +
      +  S <- 'a' B / 'b' A / ''
      +  A <- 'a' S / 'b' A A
      +  B <- 'b' S / 'a' B B
      +
      + + +

      Captures

      + +

      +A capture is a pattern that produces values +(the so called semantic information) +according to what it matches. +LPeg offers several kinds of captures, +which produces values based on matches and combine these values to +produce new values. +Each capture may produce zero or more values. +

      + +

      +The following table summarizes the basic captures: +

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      OperationWhat it Produces
      lpeg.C(patt)the match for patt plus all captures + made by patt
      lpeg.Carg(n)the value of the nth extra argument to + lpeg.match (matches the empty string)
      lpeg.Cb(name)the values produced by the previous + group capture named name + (matches the empty string)
      lpeg.Cc(values)the given values (matches the empty string)
      lpeg.Cf(patt, func)a folding of the captures from patt
      lpeg.Cg(patt [, name])the values produced by patt, + optionally tagged with name
      lpeg.Cp()the current position (matches the empty string)
      lpeg.Cs(patt)the match for patt + with the values from nested captures replacing their matches
      lpeg.Ct(patt)a table with all captures from patt
      patt / stringstring, with some marks replaced by captures + of patt
      patt / numberthe n-th value captured by patt, +or no value when number is zero.
      patt / tabletable[c], where c is the (first) + capture of patt
      patt / functionthe returns of function applied to the captures + of patt
      lpeg.Cmt(patt, function)the returns of function applied to the captures + of patt; the application is done at match time
      + +

      +A capture pattern produces its values only when it succeeds. +For instance, +the pattern lpeg.C(lpeg.P"a"^-1) +produces the empty string when there is no "a" +(because the pattern "a"? succeeds), +while the pattern lpeg.C("a")^-1 +does not produce any value when there is no "a" +(because the pattern "a" fails). +A pattern inside a loop or inside a recursive structure +produces values for each match. +

      + +

      +Usually, +LPeg does not specify when (and if) it evaluates its captures. +(As an example, +consider the pattern lpeg.P"a" / func / 0. +Because the "division" by 0 instructs LPeg to throw away the +results from the pattern, +LPeg may or may not call func.) +Therefore, captures should avoid side effects. +Moreover, +most captures cannot affect the way a pattern matches a subject. +The only exception to this rule is the +so-called match-time capture. +When a match-time capture matches, +it forces the immediate evaluation of all its nested captures +and then calls its corresponding function, +which defines whether the match succeeds and also +what values are produced. +

      + +

      lpeg.C (patt)

      +

      +Creates a simple capture, +which captures the substring of the subject that matches patt. +The captured value is a string. +If patt has other captures, +their values are returned after this one. +

      + + +

      lpeg.Carg (n)

      +

      +Creates an argument capture. +This pattern matches the empty string and +produces the value given as the nth extra +argument given in the call to lpeg.match. +

      + + +

      lpeg.Cb (name)

      +

      +Creates a back capture. +This pattern matches the empty string and +produces the values produced by the most recent +group capture named name +(where name can be any Lua value). +

      + +

      +Most recent means the last +complete +outermost +group capture with the given name. +A Complete capture means that the entire pattern +corresponding to the capture has matched. +An Outermost capture means that the capture is not inside +another complete capture. +

      + +

      +In the same way that LPeg does not specify when it evaluates captures, +it does not specify whether it reuses +values previously produced by the group +or re-evaluates them. +

      + +

      lpeg.Cc ([value, ...])

      +

      +Creates a constant capture. +This pattern matches the empty string and +produces all given values as its captured values. +

      + + +

      lpeg.Cf (patt, func)

      +

      +Creates a fold capture. +If patt produces a list of captures +C1 C2 ... Cn, +this capture will produce the value +func(...func(func(C1, C2), C3)..., + Cn), +that is, it will fold +(or accumulate, or reduce) +the captures from patt using function func. +

      + +

      +This capture assumes that patt should produce +at least one capture with at least one value (of any type), +which becomes the initial value of an accumulator. +(If you need a specific initial value, +you may prefix a constant capture to patt.) +For each subsequent capture, +LPeg calls func +with this accumulator as the first argument and all values produced +by the capture as extra arguments; +the first result from this call +becomes the new value for the accumulator. +The final value of the accumulator becomes the captured value. +

      + +

      +As an example, +the following pattern matches a list of numbers separated +by commas and returns their addition: +

      +
      +-- matches a numeral and captures its numerical value
      +number = lpeg.R"09"^1 / tonumber
      +
      +-- matches a list of numbers, capturing their values
      +list = number * ("," * number)^0
      +
      +-- auxiliary function to add two numbers
      +function add (acc, newvalue) return acc + newvalue end
      +
      +-- folds the list of numbers adding them
      +sum = lpeg.Cf(list, add)
      +
      +-- example of use
      +print(sum:match("10,30,43"))   --> 83
      +
      + + +

      lpeg.Cg (patt [, name])

      +

      +Creates a group capture. +It groups all values returned by patt +into a single capture. +The group may be anonymous (if no name is given) +or named with the given name +(which can be any non-nil Lua value). +

      + +

      +An anonymous group serves to join values from several captures into +a single capture. +A named group has a different behavior. +In most situations, a named group returns no values at all. +Its values are only relevant for a following +back capture or when used +inside a table capture. +

      + + +

      lpeg.Cp ()

      +

      +Creates a position capture. +It matches the empty string and +captures the position in the subject where the match occurs. +The captured value is a number. +

      + + +

      lpeg.Cs (patt)

      +

      +Creates a substitution capture, +which captures the substring of the subject that matches patt, +with substitutions. +For any capture inside patt with a value, +the substring that matched the capture is replaced by the capture value +(which should be a string). +The final captured value is the string resulting from +all replacements. +

      + + +

      lpeg.Ct (patt)

      +

      +Creates a table capture. +This capture returns a table with all values from all anonymous captures +made by patt inside this table in successive integer keys, +starting at 1. +Moreover, +for each named capture group created by patt, +the first value of the group is put into the table +with the group name as its key. +The captured value is only the table. +

      + + +

      patt / string

      +

      +Creates a string capture. +It creates a capture string based on string. +The captured value is a copy of string, +except that the character % works as an escape character: +any sequence in string of the form %n, +with n between 1 and 9, +stands for the match of the n-th capture in patt. +The sequence %0 stands for the whole match. +The sequence %% stands for a single %. +

      + + +

      patt / number

      +

      +Creates a numbered capture. +For a non-zero number, +the captured value is the n-th value +captured by patt. +When number is zero, +there are no captured values. +

      + + +

      patt / table

      +

      +Creates a query capture. +It indexes the given table using as key the first value captured by +patt, +or the whole match if patt produced no value. +The value at that index is the final value of the capture. +If the table does not have that key, +there is no captured value. +

      + + +

      patt / function

      +

      +Creates a function capture. +It calls the given function passing all captures made by +patt as arguments, +or the whole match if patt made no capture. +The values returned by the function +are the final values of the capture. +In particular, +if function returns no value, +there is no captured value. +

      + + +

      lpeg.Cmt(patt, function)

      +

      +Creates a match-time capture. +Unlike all other captures, +this one is evaluated immediately when a match occurs +(even if it is part of a larger pattern that fails later). +It forces the immediate evaluation of all its nested captures +and then calls function. +

      + +

      +The given function gets as arguments the entire subject, +the current position (after the match of patt), +plus any capture values produced by patt. +

      + +

      +The first value returned by function +defines how the match happens. +If the call returns a number, +the match succeeds +and the returned number becomes the new current position. +(Assuming a subject s and current position i, +the returned number must be in the range [i, len(s) + 1].) +If the call returns true, +the match succeeds without consuming any input. +(So, to return true is equivalent to return i.) +If the call returns false, nil, or no value, +the match fails. +

      + +

      +Any extra values returned by the function become the +values produced by the capture. +

      + + + + +

      Some Examples

      + +

      Using a Pattern

      +

      +This example shows a very simple but complete program +that builds and uses a pattern: +

      +
      +local lpeg = require "lpeg"
      +
      +-- matches a word followed by end-of-string
      +p = lpeg.R"az"^1 * -1
      +
      +print(p:match("hello"))        --> 6
      +print(lpeg.match(p, "hello"))  --> 6
      +print(p:match("1 hello"))      --> nil
      +
      +

      +The pattern is simply a sequence of one or more lower-case letters +followed by the end of string (-1). +The program calls match both as a method +and as a function. +In both sucessful cases, +the match returns +the index of the first character after the match, +which is the string length plus one. +

      + + +

      Name-value lists

      +

      +This example parses a list of name-value pairs and returns a table +with those pairs: +

      +
      +lpeg.locale(lpeg)   -- adds locale entries into 'lpeg' table
      +
      +local space = lpeg.space^0
      +local name = lpeg.C(lpeg.alpha^1) * space
      +local sep = lpeg.S(",;") * space
      +local pair = lpeg.Cg(name * "=" * space * name) * sep^-1
      +local list = lpeg.Cf(lpeg.Ct("") * pair^0, rawset)
      +t = list:match("a=b, c = hi; next = pi")  --> { a = "b", c = "hi", next = "pi" }
      +
      +

      +Each pair has the format name = name followed by +an optional separator (a comma or a semicolon). +The pair pattern encloses the pair in a group pattern, +so that the names become the values of a single capture. +The list pattern then folds these captures. +It starts with an empty table, +created by a table capture matching an empty string; +then for each capture (a pair of names) it applies rawset +over the accumulator (the table) and the capture values (the pair of names). +rawset returns the table itself, +so the accumulator is always the table. +

      + +

      Splitting a string

      +

      +The following code builds a pattern that +splits a string using a given pattern +sep as a separator: +

      +
      +function split (s, sep)
      +  sep = lpeg.P(sep)
      +  local elem = lpeg.C((1 - sep)^0)
      +  local p = elem * (sep * elem)^0
      +  return lpeg.match(p, s)
      +end
      +
      +

      +First the function ensures that sep is a proper pattern. +The pattern elem is a repetition of zero of more +arbitrary characters as long as there is not a match against +the separator. +It also captures its match. +The pattern p matches a list of elements separated +by sep. +

      + +

      +If the split results in too many values, +it may overflow the maximum number of values +that can be returned by a Lua function. +In this case, +we can collect these values in a table: +

      +
      +function split (s, sep)
      +  sep = lpeg.P(sep)
      +  local elem = lpeg.C((1 - sep)^0)
      +  local p = lpeg.Ct(elem * (sep * elem)^0)   -- make a table capture
      +  return lpeg.match(p, s)
      +end
      +
      + + +

      Searching for a pattern

      +

      +The primitive match works only in anchored mode. +If we want to find a pattern anywhere in a string, +we must write a pattern that matches anywhere. +

      + +

      +Because patterns are composable, +we can write a function that, +given any arbitrary pattern p, +returns a new pattern that searches for p +anywhere in a string. +There are several ways to do the search. +One way is like this: +

      +
      +function anywhere (p)
      +  return lpeg.P{ p + 1 * lpeg.V(1) }
      +end
      +
      +

      +This grammar has a straight reading: +it matches p or skips one character and tries again. +

      + +

      +If we want to know where the pattern is in the string +(instead of knowing only that it is there somewhere), +we can add position captures to the pattern: +

      +
      +local I = lpeg.Cp()
      +function anywhere (p)
      +  return lpeg.P{ I * p * I + 1 * lpeg.V(1) }
      +end
      +
      +print(anywhere("world"):match("hello world!"))   -> 7   12
      +
      + +

      +Another option for the search is like this: +

      +
      +local I = lpeg.Cp()
      +function anywhere (p)
      +  return (1 - lpeg.P(p))^0 * I * p * I
      +end
      +
      +

      +Again the pattern has a straight reading: +it skips as many characters as possible while not matching p, +and then matches p (plus appropriate captures). +

      + +

      +If we want to look for a pattern only at word boundaries, +we can use the following transformer: +

      + +
      +local t = lpeg.locale()
      +
      +function atwordboundary (p)
      +  return lpeg.P{
      +    [1] = p + t.alpha^0 * (1 - t.alpha)^1 * lpeg.V(1)
      +  }
      +end
      +
      + + +

      Balanced parentheses

      +

      +The following pattern matches only strings with balanced parentheses: +

      +
      +b = lpeg.P{ "(" * ((1 - lpeg.S"()") + lpeg.V(1))^0 * ")" }
      +
      +

      +Reading the first (and only) rule of the given grammar, +we have that a balanced string is +an open parenthesis, +followed by zero or more repetitions of either +a non-parenthesis character or +a balanced string (lpeg.V(1)), +followed by a closing parenthesis. +

      + + +

      Global substitution

      +

      +The next example does a job somewhat similar to string.gsub. +It receives a pattern and a replacement value, +and substitutes the replacement value for all occurrences of the pattern +in a given string: +

      +
      +function gsub (s, patt, repl)
      +  patt = lpeg.P(patt)
      +  patt = lpeg.Cs((patt / repl + 1)^0)
      +  return lpeg.match(patt, s)
      +end
      +
      +

      +As in string.gsub, +the replacement value can be a string, +a function, or a table. +

      + + +

      Comma-Separated Values (CSV)

      +

      +This example breaks a string into comma-separated values, +returning all fields: +

      +
      +local field = '"' * lpeg.Cs(((lpeg.P(1) - '"') + lpeg.P'""' / '"')^0) * '"' +
      +                    lpeg.C((1 - lpeg.S',\n"')^0)
      +
      +local record = field * (',' * field)^0 * (lpeg.P'\n' + -1)
      +
      +function csv (s)
      +  return lpeg.match(record, s)
      +end
      +
      +

      +A field is either a quoted field +(which may contain any character except an individual quote, +which may be written as two quotes that are replaced by one) +or an unquoted field +(which cannot contain commas, newlines, or quotes). +A record is a list of fields separated by commas, +ending with a newline or the string end (-1). +

      + +

      +As it is, +the previous pattern returns each field as a separated result. +If we add a table capture in the definition of record, +the pattern will return instead a single table +containing all fields: +

      +
      +local record = lpeg.Ct(field * (',' * field)^0) * (lpeg.P'\n' + -1)
      +
      + + +

      UTF-8 and Latin 1

      +

      +It is not difficult to use LPeg to convert a string from +UTF-8 encoding to Latin 1 (ISO 8859-1): +

      + +
      +-- convert a two-byte UTF-8 sequence to a Latin 1 character
      +local function f2 (s)
      +  local c1, c2 = string.byte(s, 1, 2)
      +  return string.char(c1 * 64 + c2 - 12416)
      +end
      +
      +local utf8 = lpeg.R("\0\127")
      +           + lpeg.R("\194\195") * lpeg.R("\128\191") / f2
      +
      +local decode_pattern = lpeg.Cs(utf8^0) * -1
      +
      +

      +In this code, +the definition of UTF-8 is already restricted to the +Latin 1 range (from 0 to 255). +Any encoding outside this range (as well as any invalid encoding) +will not match that pattern. +

      + +

      +As the definition of decode_pattern demands that +the pattern matches the whole input (because of the -1 at its end), +any invalid string will simply fail to match, +without any useful information about the problem. +We can improve this situation redefining decode_pattern +as follows: +

      +
      +local function er (_, i) error("invalid encoding at position " .. i) end
      +
      +local decode_pattern = lpeg.Cs(utf8^0) * (-1 + lpeg.P(er))
      +
      +

      +Now, if the pattern utf8^0 stops +before the end of the string, +an appropriate error function is called. +

      + + +

      UTF-8 and Unicode

      +

      +We can extend the previous patterns to handle all Unicode code points. +Of course, +we cannot translate them to Latin 1 or any other one-byte encoding. +Instead, our translation results in a array with the code points +represented as numbers. +The full code is here: +

      +
      +-- decode a two-byte UTF-8 sequence
      +local function f2 (s)
      +  local c1, c2 = string.byte(s, 1, 2)
      +  return c1 * 64 + c2 - 12416
      +end
      +
      +-- decode a three-byte UTF-8 sequence
      +local function f3 (s)
      +  local c1, c2, c3 = string.byte(s, 1, 3)
      +  return (c1 * 64 + c2) * 64 + c3 - 925824
      +end
      +
      +-- decode a four-byte UTF-8 sequence
      +local function f4 (s)
      +  local c1, c2, c3, c4 = string.byte(s, 1, 4)
      +  return ((c1 * 64 + c2) * 64 + c3) * 64 + c4 - 63447168
      +end
      +
      +local cont = lpeg.R("\128\191")   -- continuation byte
      +
      +local utf8 = lpeg.R("\0\127") / string.byte
      +           + lpeg.R("\194\223") * cont / f2
      +           + lpeg.R("\224\239") * cont * cont / f3
      +           + lpeg.R("\240\244") * cont * cont * cont / f4
      +
      +local decode_pattern = lpeg.Ct(utf8^0) * -1
      +
      + + +

      Lua's long strings

      +

      +A long string in Lua starts with the pattern [=*[ +and ends at the first occurrence of ]=*] with +exactly the same number of equal signs. +If the opening brackets are followed by a newline, +this newline is discarded +(that is, it is not part of the string). +

      + +

      +To match a long string in Lua, +the pattern must capture the first repetition of equal signs and then, +whenever it finds a candidate for closing the string, +check whether it has the same number of equal signs. +

      + +
      +equals = lpeg.P"="^0
      +open = "[" * lpeg.Cg(equals, "init") * "[" * lpeg.P"\n"^-1
      +close = "]" * lpeg.C(equals) * "]"
      +closeeq = lpeg.Cmt(close * lpeg.Cb("init"), function (s, i, a, b) return a == b end)
      +string = open * lpeg.C((lpeg.P(1) - closeeq)^0) * close / 1
      +
      + +

      +The open pattern matches [=*[, +capturing the repetitions of equal signs in a group named init; +it also discharges an optional newline, if present. +The close pattern matches ]=*], +also capturing the repetitions of equal signs. +The closeeq pattern first matches close; +then it uses a back capture to recover the capture made +by the previous open, +which is named init; +finally it uses a match-time capture to check +whether both captures are equal. +The string pattern starts with an open, +then it goes as far as possible until matching closeeq, +and then matches the final close. +The final numbered capture simply discards +the capture made by close. +

      + + +

      Arithmetic expressions

      +

      +This example is a complete parser and evaluator for simple +arithmetic expressions. +We write it in two styles. +The first approach first builds a syntax tree and then +traverses this tree to compute the expression value: +

      +
      +-- Lexical Elements
      +local Space = lpeg.S(" \n\t")^0
      +local Number = lpeg.C(lpeg.P"-"^-1 * lpeg.R("09")^1) * Space
      +local TermOp = lpeg.C(lpeg.S("+-")) * Space
      +local FactorOp = lpeg.C(lpeg.S("*/")) * Space
      +local Open = "(" * Space
      +local Close = ")" * Space
      +
      +-- Grammar
      +local Exp, Term, Factor = lpeg.V"Exp", lpeg.V"Term", lpeg.V"Factor"
      +G = lpeg.P{ Exp,
      +  Exp = lpeg.Ct(Term * (TermOp * Term)^0);
      +  Term = lpeg.Ct(Factor * (FactorOp * Factor)^0);
      +  Factor = Number + Open * Exp * Close;
      +}
      +
      +G = Space * G * -1
      +
      +-- Evaluator
      +function eval (x)
      +  if type(x) == "string" then
      +    return tonumber(x)
      +  else
      +    local op1 = eval(x[1])
      +    for i = 2, #x, 2 do
      +      local op = x[i]
      +      local op2 = eval(x[i + 1])
      +      if (op == "+") then op1 = op1 + op2
      +      elseif (op == "-") then op1 = op1 - op2
      +      elseif (op == "*") then op1 = op1 * op2
      +      elseif (op == "/") then op1 = op1 / op2
      +      end
      +    end
      +    return op1
      +  end
      +end
      +
      +-- Parser/Evaluator
      +function evalExp (s)
      +  local t = lpeg.match(G, s)
      +  if not t then error("syntax error", 2) end
      +  return eval(t)
      +end
      +
      +-- small example
      +print(evalExp"3 + 5*9 / (1+1) - 12")   --> 13.5
      +
      + +

      +The second style computes the expression value on the fly, +without building the syntax tree. +The following grammar takes this approach. +(It assumes the same lexical elements as before.) +

      +
      +-- Auxiliary function
      +function eval (v1, op, v2)
      +  if (op == "+") then return v1 + v2
      +  elseif (op == "-") then return v1 - v2
      +  elseif (op == "*") then return v1 * v2
      +  elseif (op == "/") then return v1 / v2
      +  end
      +end
      +
      +-- Grammar
      +local V = lpeg.V
      +G = lpeg.P{ "Exp",
      +  Exp = lpeg.Cf(V"Term" * lpeg.Cg(TermOp * V"Term")^0, eval);
      +  Term = lpeg.Cf(V"Factor" * lpeg.Cg(FactorOp * V"Factor")^0, eval);
      +  Factor = Number / tonumber + Open * V"Exp" * Close;
      +}
      +
      +-- small example
      +print(lpeg.match(G, "3 + 5*9 / (1+1) - 12"))   --> 13.5
      +
      +

      +Note the use of the fold (accumulator) capture. +To compute the value of an expression, +the accumulator starts with the value of the first term, +and then applies eval over +the accumulator, the operator, +and the new term for each repetition. +

      + + + +

      Download

      + +

      LPeg +source code.

      + + +

      License

      + +

      +Copyright © 2007-2019 Lua.org, PUC-Rio. +

      +

      +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and +associated documentation files (the "Software"), +to deal in the Software without restriction, +including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, +and to permit persons to whom the Software is +furnished to do so, +subject to the following conditions: +

      + +

      +The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. +

      + +

      +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +

      + +
      + +
      + +