Compare commits
230 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49da63c146 | ||
|
|
e45361d9d3 | ||
|
|
f7941c2251 | ||
|
|
b72eeb29a9 | ||
|
|
b4dace1253 | ||
|
|
4edec8b20b | ||
|
|
797abbd4e6 | ||
|
|
bce8176866 | ||
|
|
005e25c1b8 | ||
|
|
711e36d0bf | ||
|
|
78b52e7b35 | ||
|
|
89638a5473 | ||
|
|
3755a1c970 | ||
|
|
21963ac738 | ||
|
|
b67ea18471 | ||
|
|
6e27530dec | ||
|
|
c9d5e38db2 | ||
|
|
996adc37ff | ||
|
|
4f6e0f9c65 | ||
|
|
5d5d129604 | ||
|
|
dcf02f4509 | ||
|
|
cbaa344467 | ||
|
|
7da5feada0 | ||
|
|
d901de0063 | ||
|
|
9b730e2a71 | ||
|
|
facd386cf7 | ||
|
|
f72902cd8d | ||
|
|
70e1ef0fdb | ||
|
|
74680a83a9 | ||
|
|
1d9b15ad90 | ||
|
|
9c9132ad72 | ||
|
|
69ec57f1bf | ||
|
|
d11adf3fe6 | ||
|
|
faa34bc216 | ||
|
|
76451c9d27 | ||
|
|
7e5802dcd1 | ||
|
|
2b6d1c70a8 | ||
|
|
faaef3b353 | ||
|
|
6ffa1d4a1d | ||
|
|
10f0e0dcca | ||
|
|
0afad2e192 | ||
|
|
6a1e7266b9 | ||
|
|
6ffc552bd8 | ||
|
|
8d8245971a | ||
|
|
a2f730d57e | ||
|
|
bf282c41fd | ||
|
|
3e3f7b2cc6 | ||
|
|
0580e063be | ||
|
|
2924aad25e | ||
|
|
39907299cd | ||
|
|
77705895d5 | ||
|
|
3fb85edf0b | ||
|
|
0e7a968cac | ||
|
|
195a9dc771 | ||
|
|
ca227a846a | ||
|
|
e1057fc76e | ||
|
|
b5168c3174 | ||
|
|
4ced00de50 | ||
|
|
750c929506 | ||
|
|
8b46a5a5af | ||
|
|
10d606a101 | ||
|
|
45d36dadcb | ||
|
|
61484d5cb8 | ||
|
|
b7775a99ef | ||
|
|
c45ba38ffa | ||
|
|
e97d9fb8a0 | ||
|
|
bb56ec9ec5 | ||
|
|
6d8f659558 | ||
|
|
7e9047c8fe | ||
|
|
b5af560b67 | ||
|
|
5622636870 | ||
|
|
372c276afc | ||
|
|
250b5fdd10 | ||
|
|
d1fb0bf8b7 | ||
|
|
490c273d36 | ||
|
|
0773dbaa28 | ||
|
|
4d04964740 | ||
|
|
537e6c9a5e | ||
|
|
082a6721f3 | ||
|
|
d515a20129 | ||
|
|
a66d3ef61a | ||
|
|
3b05b1a933 | ||
|
|
105479ce4b | ||
|
|
1a950e3aad | ||
|
|
79fa76e039 | ||
|
|
9db9a70a68 | ||
|
|
632862c742 | ||
|
|
76d0783f6f | ||
|
|
fc6a0e9813 | ||
|
|
038e289057 | ||
|
|
4b2a4af1e7 | ||
|
|
0fb2555bea | ||
|
|
bd8a809065 | ||
|
|
fc6fb7dce2 | ||
|
|
bacb94abe6 | ||
|
|
eb04421412 | ||
|
|
2fbd5a785e | ||
|
|
d8ae1525be | ||
|
|
daaa165d25 | ||
|
|
27ea02be5e | ||
|
|
8eac1ef307 | ||
|
|
1181974ea5 | ||
|
|
5f57fa0470 | ||
|
|
c0ad74ab7b | ||
|
|
c27905e1cc | ||
|
|
cadb65a85f | ||
|
|
b3c152685a | ||
|
|
16ce0726f8 | ||
|
|
5be306e087 | ||
|
|
0d21fc27ab | ||
|
|
7deaa678bc | ||
|
|
0d230a9237 | ||
|
|
8453d754f1 | ||
|
|
05f77f7a0d | ||
|
|
4334b6f148 | ||
|
|
72955759d7 | ||
|
|
18dc52996d | ||
|
|
0b16d09330 | ||
|
|
027d81a8cd | ||
|
|
d833158784 | ||
|
|
75024c9364 | ||
|
|
1dd5db3c42 | ||
|
|
faba80e315 | ||
|
|
1b7dc8eb96 | ||
|
|
7c0af45dd0 | ||
|
|
daea270132 | ||
|
|
a42e3ac4a6 | ||
|
|
c94c526b43 | ||
|
|
e531787773 | ||
|
|
0eda84a99a | ||
|
|
89b791448a | ||
|
|
2239328b2e | ||
|
|
e440a46080 | ||
|
|
4a0fec6e35 | ||
|
|
a9b46987bc | ||
|
|
6771afead5 | ||
|
|
553fca2981 | ||
|
|
8ef1e36b8b | ||
|
|
9e6c49b67c | ||
|
|
170632ddbc | ||
|
|
4a74488cac | ||
|
|
5fd6a08988 | ||
|
|
c86b91ead6 | ||
|
|
aeebb763c1 | ||
|
|
b05630e3e0 | ||
|
|
51905a0ee3 | ||
|
|
869ebd59d1 | ||
|
|
8a6aedca16 | ||
|
|
cf98c3453a | ||
|
|
1b6f234f39 | ||
|
|
26c7f0f566 | ||
|
|
617ad9b16e | ||
|
|
04a6792dbf | ||
|
|
f2bf8f2dbf | ||
|
|
3aba0abd24 | ||
|
|
b56ccc7cef | ||
|
|
aeebf55e60 | ||
|
|
9a9e0d25f3 | ||
|
|
6b3033e240 | ||
|
|
2859a59340 | ||
|
|
45367dd579 | ||
|
|
fee3fd743f | ||
|
|
ba4cfea47c | ||
|
|
5c11f07f8e | ||
|
|
ffac080611 | ||
|
|
f7a1e60735 | ||
|
|
f22651821c | ||
|
|
b2a5a9ac9b | ||
|
|
245af2bd77 | ||
|
|
9dd2b1d35c | ||
|
|
bdbfd3e055 | ||
|
|
1518c03d2b | ||
|
|
45920d4df2 | ||
|
|
1c106ac6eb | ||
|
|
9f4a32b4ac | ||
|
|
2565b10491 | ||
|
|
34d632a401 | ||
|
|
a9df27c0e1 | ||
|
|
313011a168 | ||
|
|
5c595aad83 | ||
|
|
2c67eac63c | ||
|
|
d20c20cb33 | ||
|
|
fa76f032be | ||
|
|
3f74910640 | ||
|
|
22bd2a57e9 | ||
|
|
e45a1d0367 | ||
|
|
ee7acdc6a0 | ||
|
|
32264f2700 | ||
|
|
249ea32973 | ||
|
|
9c31b69a58 | ||
|
|
6e985cf6cc | ||
|
|
0f35b45704 | ||
|
|
ba9245e724 | ||
|
|
6e806f2822 | ||
|
|
7c6842a859 | ||
|
|
39f9fa0e6b | ||
|
|
0c8ae8960d | ||
|
|
ffd6a20829 | ||
|
|
2ee3e2782c | ||
|
|
61324c28c6 | ||
|
|
e9f70ecb90 | ||
|
|
cc3b802575 | ||
|
|
9b780ff4a3 | ||
|
|
deb1a33e78 | ||
|
|
5021350aa0 | ||
|
|
41b3dab135 | ||
|
|
ecf94170c8 | ||
|
|
56acdf3ce3 | ||
|
|
5d6d970d49 | ||
|
|
b501cf3c88 | ||
|
|
deac2baa18 | ||
|
|
b1e3f54055 | ||
|
|
8ebf79ffbe | ||
|
|
8d7307cddf | ||
|
|
c547837ae3 | ||
|
|
b408996e72 | ||
|
|
6b5866524d | ||
|
|
4994dfb488 | ||
|
|
5d5040651e | ||
|
|
5252188a68 | ||
|
|
3f664882d5 | ||
|
|
15f2a2d517 | ||
|
|
6637b0fb8f | ||
|
|
a06eeb9129 | ||
|
|
a24fce2c22 | ||
|
|
ddbdb64fc4 | ||
|
|
38c6d545ec | ||
|
|
fe880abbb0 | ||
|
|
081ed76c39 | ||
|
|
613a00686f |
@@ -46,7 +46,7 @@
|
||||
* [开发者指南](https://goedge.cn/docs/Developer/Build.md)
|
||||
|
||||
## 架构
|
||||

|
||||

|
||||
|
||||
其中的组件源码地址如下:
|
||||
* [边缘节点](https://github.com/TeaOSLab/EdgeNode)
|
||||
|
||||
@@ -53,7 +53,7 @@ function build() {
|
||||
|
||||
# generate files
|
||||
echo "generating files ..."
|
||||
go run -tags $TAG "$ROOT"/../cmd/edge-admin/main.go generate
|
||||
env CGO_ENABLED=0 go run -tags $TAG "$ROOT"/../cmd/edge-admin/main.go generate
|
||||
if [ "$(which uglifyjs)" ]; then
|
||||
echo "compress to component.js ..."
|
||||
uglifyjs --compress --mangle -- "${JS_ROOT}"/components.src.js > "${JS_ROOT}"/components.js
|
||||
@@ -99,9 +99,36 @@ function build() {
|
||||
rm -f "$(basename "$EDGE_API_ZIP_FILE")"
|
||||
cd - || exit
|
||||
|
||||
# find gcc
|
||||
GCC_DIR=""
|
||||
CC_PATH=""
|
||||
CXX_PATH=""
|
||||
if [ "${ARCH}" == "amd64" ]; then
|
||||
GCC_DIR="/usr/local/gcc/x86_64-unknown-linux-gnu/bin"
|
||||
CC_PATH="x86_64-unknown-linux-gnu-gcc"
|
||||
CXX_PATH="x86_64-unknown-linux-gnu-g++"
|
||||
fi
|
||||
if [ "${ARCH}" == "arm64" ]; then
|
||||
GCC_DIR="/usr/local/gcc/aarch64-unknown-linux-gnu/bin"
|
||||
CC_PATH="aarch64-unknown-linux-gnu-gcc"
|
||||
CXX_PATH="aarch64-unknown-linux-gnu-g++"
|
||||
fi
|
||||
|
||||
# build
|
||||
echo "building ${NAME} ..."
|
||||
env GOOS="$OS" GOARCH="$ARCH" go build -trimpath -tags $TAG -ldflags="-s -w" -o "$DIST"/bin/${NAME} "$ROOT"/../cmd/edge-admin/main.go
|
||||
if [ -f "${GCC_DIR}/${CC_PATH}" ]; then
|
||||
echo " building ${NAME} with gcc ..."
|
||||
env CC="${GCC_DIR}/${CC_PATH}" \
|
||||
CXX="${GCC_DIR}/${CXX_PATH}" \
|
||||
CGO_ENABLED=1 \
|
||||
GOOS="$OS" GOARCH="$ARCH" go build -trimpath -tags "${TAG} gcc" -ldflags="-linkmode external -extldflags -static -s -w" -o "$DIST"/bin/${NAME} "$ROOT"/../cmd/edge-admin/main.go
|
||||
else
|
||||
GOOS="$OS" GOARCH="$ARCH" go build -trimpath -tags $TAG -ldflags="-s -w" -o "$DIST"/bin/${NAME} "$ROOT"/../cmd/edge-admin/main.go
|
||||
fi
|
||||
if [ ! -f "${DIST}/bin/${NAME}" ]; then
|
||||
echo "build '${NAME}' failed!"
|
||||
exit
|
||||
fi
|
||||
|
||||
# delete hidden files
|
||||
find "$DIST" -name ".DS_Store" -delete
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
JS_ROOT=../web/public/js
|
||||
|
||||
echo "generating component.src.js ..."
|
||||
go run -tags=community ../cmd/edge-admin/main.go generate
|
||||
env CGO_ENABLED=0 go run -tags=community ../cmd/edge-admin/main.go generate
|
||||
|
||||
if [ "$(which uglifyjs)" ]; then
|
||||
echo "compress to component.js ..."
|
||||
|
||||
@@ -40,7 +40,8 @@ func main() {
|
||||
Option("demo", "switch to demo mode").
|
||||
Option("dev", "switch to 'dev' mode").
|
||||
Option("prod", "switch to 'prod' mode").
|
||||
Option("upgrade [--url=URL]", "upgrade from official site or an url")
|
||||
Option("upgrade [--url=URL]", "upgrade from official site or an url").
|
||||
Option("install-local-node", "install a local node")
|
||||
|
||||
app.On("daemon", func() {
|
||||
nodes.NewAdminNode().Daemon()
|
||||
@@ -76,7 +77,7 @@ func main() {
|
||||
fmt.Println("done")
|
||||
})
|
||||
app.On("recover", func() {
|
||||
sock := gosock.NewTmpSock(teaconst.ProcessName)
|
||||
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
if !sock.IsListening() {
|
||||
fmt.Println("[ERROR]the service not started yet, you should start the service first")
|
||||
return
|
||||
@@ -89,7 +90,7 @@ func main() {
|
||||
fmt.Println("enter recovery mode successfully")
|
||||
})
|
||||
app.On("demo", func() {
|
||||
sock := gosock.NewTmpSock(teaconst.ProcessName)
|
||||
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
if !sock.IsListening() {
|
||||
fmt.Println("[ERROR]the service not started yet, you should start the service first")
|
||||
return
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 72 KiB |
BIN
doc/architect-zh.png
Normal file
BIN
doc/architect-zh.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 MiB |
@@ -1,11 +1,15 @@
|
||||
FROM --platform=linux/amd64 alpine:latest
|
||||
LABEL maintainer="goedge.cdn@gmail.com"
|
||||
ENV TZ "Asia/Shanghai"
|
||||
ENV VERSION 1.2.8
|
||||
ENV VERSION 1.3.4
|
||||
ENV ROOT_DIR /usr/local/goedge
|
||||
ENV TAR_FILE edge-admin-linux-amd64-plus-v${VERSION}.zip
|
||||
|
||||
# remote official repository
|
||||
ENV TAR_URL "https://dl.goedge.cn/edge/v${VERSION}/edge-admin-linux-amd64-plus-v${VERSION}.zip"
|
||||
#ENV TAR_URL "http://192.168.2.61:8080/edge-admin-linux-amd64-plus-v${VERSION}.zip" # your local repository
|
||||
|
||||
# your local repository
|
||||
#ENV TAR_URL "http://192.168.2.61:8080/edge-admin-linux-amd64-plus-v${VERSION}.zip"
|
||||
|
||||
RUN apk add --no-cache tzdata
|
||||
|
||||
|
||||
29
go.mod
29
go.mod
@@ -8,16 +8,16 @@ require (
|
||||
github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000
|
||||
github.com/cespare/xxhash v1.1.0
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/iwind/TeaGo v0.0.0-20230623080147-cd1e53b4915f
|
||||
github.com/iwind/TeaGo v0.0.0-20231214114820-1bfb0013bcf6
|
||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4
|
||||
github.com/miekg/dns v1.1.43
|
||||
github.com/quic-go/quic-go v0.36.0
|
||||
github.com/quic-go/quic-go v0.40.0
|
||||
github.com/shirou/gopsutil/v3 v3.22.5
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/tealeg/xlsx/v3 v3.2.3
|
||||
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119
|
||||
golang.org/x/crypto v0.10.0
|
||||
golang.org/x/sys v0.9.0
|
||||
golang.org/x/crypto v0.16.0
|
||||
golang.org/x/sys v0.15.0
|
||||
google.golang.org/grpc v1.45.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
@@ -26,29 +26,28 @@ require (
|
||||
github.com/frankban/quicktest v1.11.3 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/btree v1.0.0 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20231203200248-ad67f76aa53d // indirect
|
||||
github.com/kr/pretty v0.2.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.13.2 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||
github.com/rogpeppe/fastuuid v1.2.0 // indirect
|
||||
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa // indirect
|
||||
github.com/tdewolff/minify/v2 v2.12.7 // indirect
|
||||
github.com/tdewolff/parse/v2 v2.6.6 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
||||
golang.org/x/mod v0.11.0 // indirect
|
||||
golang.org/x/net v0.11.0 // indirect
|
||||
golang.org/x/text v0.10.0 // indirect
|
||||
golang.org/x/tools v0.10.0 // indirect
|
||||
go.uber.org/mock v0.3.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231127185646-65229373498e // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/net v0.19.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.16.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
|
||||
69
go.sum
69
go.sum
@@ -41,7 +41,7 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-redis/redis/v8 v8.0.0-beta.7/go.mod h1:FGJAWDWFht1sQ4qxyJHZZbVyvnVcKQN0E3u5/5lRz+g=
|
||||
@@ -51,8 +51,6 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEe
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
@@ -79,17 +77,19 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs=
|
||||
github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
|
||||
github.com/google/pprof v0.0.0-20231203200248-ad67f76aa53d h1:bM3Cipp7U7Sdn0DpYngVaLWFV9yonxQ1IB7ZPQ41OLw=
|
||||
github.com/google/pprof v0.0.0-20231203200248-ad67f76aa53d/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/iwind/TeaGo v0.0.0-20210411134150-ddf57e240c2f/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
|
||||
github.com/iwind/TeaGo v0.0.0-20230623080147-cd1e53b4915f h1:xo6XmXLtveKcwcZAXV6VMxkWNzy/2dStfHEnyowsGAE=
|
||||
github.com/iwind/TeaGo v0.0.0-20230623080147-cd1e53b4915f/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s=
|
||||
github.com/iwind/TeaGo v0.0.0-20231214114820-1bfb0013bcf6 h1:l8MW552d+gLzRd+MAtM/2X+80n/uS5nJAlBTYWHroRI=
|
||||
github.com/iwind/TeaGo v0.0.0-20231214114820-1bfb0013bcf6/go.mod h1:Ng3xWekHSVy0E/6/jYqJ7Htydm/H+mWIl0AS+Eg3H2M=
|
||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4 h1:VWGsCqTzObdlbf7UUE3oceIpcEKi4C/YBUszQXk118A=
|
||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
@@ -112,12 +112,12 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
|
||||
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
|
||||
github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs=
|
||||
github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
|
||||
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
|
||||
github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
@@ -130,12 +130,10 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:Om
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
|
||||
github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
|
||||
github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
|
||||
github.com/quic-go/quic-go v0.36.0 h1:JIrO7p7Ug6hssFcARjWDiqS2RAKJHCiwPxBAA989rbI=
|
||||
github.com/quic-go/quic-go v0.36.0/go.mod h1:zPetvwDlILVxt15n3hr3Gf/I3mDf7LpLKPhR4Ez0AZQ=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k=
|
||||
github.com/quic-go/quic-go v0.40.0 h1:GYd1iznlKm7dpHD7pOVpUvItgMPo/jrMgDWZhMCecqw=
|
||||
github.com/quic-go/quic-go v0.40.0/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c=
|
||||
github.com/rogpeppe/fastuuid v1.2.0 h1:Ppwyp6VYCF1nvBTXL3trRso7mXMlRrw9ooo375wvi2s=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/shabbyrobe/xmlwriter v0.0.0-20200208144257-9fca06d00ffa h1:2cO3RojjYl3hVTbEvJVqrMaFmORhL6O06qdW42toftk=
|
||||
@@ -167,21 +165,22 @@ github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYa
|
||||
github.com/tklauser/numcpus v0.4.0/go.mod h1:1+UI3pD8NW14VMwdgJNJ1ESk2UnwhAnz5hMwiKKqXCQ=
|
||||
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119 h1:YyPWX3jLOtYKulBR6AScGIs74lLrJcgeKRwcbAuQOG4=
|
||||
github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119/go.mod h1:/nuTSlK+okRfR/vnIPqR89fFKonnWPiZymN5ydRJkX8=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.opentelemetry.io/otel v0.7.0/go.mod h1:aZMyHG5TqDOXEgH2tyLiXSUKly1jT3yqE9PmrzIeCdo=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
|
||||
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
|
||||
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
|
||||
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
|
||||
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20200513190911-00229845015e/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/exp v0.0.0-20231127185646-65229373498e h1:Gvh4YaCaXNs6dKTlfgismwWZKyjVZXwOPfIyUaqU3No=
|
||||
golang.org/x/exp v0.0.0-20231127185646-65229373498e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@@ -190,9 +189,8 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
|
||||
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -206,8 +204,8 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
|
||||
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
|
||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -215,7 +213,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -237,27 +235,24 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
|
||||
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
|
||||
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM=
|
||||
golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -174,6 +174,17 @@ func FindAdminLang(adminId int64) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// UpdateAdminLang 修改某个管理员选择的语言
|
||||
func UpdateAdminLang(adminId int64, langCode string) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
list, ok := sharedAdminModuleMapping[adminId]
|
||||
if ok {
|
||||
list.Lang = langCode
|
||||
}
|
||||
}
|
||||
|
||||
func FindAdminLangForAction(actionPtr actions.ActionWrapper) (langCode langs.LangCode) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
@@ -88,6 +88,8 @@ func loadSecurityConfig() (*systemconfigs.SecurityConfig, error) {
|
||||
AllowLocal: true,
|
||||
CheckClientFingerprint: false,
|
||||
CheckClientRegion: true,
|
||||
DenySearchEngines: true,
|
||||
DenySpiders: true,
|
||||
}
|
||||
err = json.Unmarshal(resp.ValueJSON, config)
|
||||
if err != nil {
|
||||
@@ -109,5 +111,7 @@ func defaultSecurityConfig() *systemconfigs.SecurityConfig {
|
||||
AllowLocal: true,
|
||||
CheckClientFingerprint: false,
|
||||
CheckClientRegion: true,
|
||||
DenySearchEngines: true,
|
||||
DenySpiders: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ func LoadAPIConfig() (*APIConfig, error) {
|
||||
|
||||
// 自动生成新配置文件
|
||||
if isFromOld {
|
||||
config.OldRPC.Endpoints = nil
|
||||
_ = config.WriteFile(Tea.ConfigFile(ConfigFileName))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "1.2.8"
|
||||
Version = "1.3.4"
|
||||
|
||||
APINodeVersion = "1.2.8"
|
||||
APINodeVersion = "1.3.4"
|
||||
|
||||
ProductName = "Edge Admin"
|
||||
ProcessName = "edge-admin"
|
||||
@@ -14,7 +14,7 @@ const (
|
||||
EncryptKey = "8f983f4d69b83aaa0d74b21a212f6967"
|
||||
EncryptMethod = "aes-256-cfb"
|
||||
|
||||
CookieSID = "edgesid"
|
||||
CookieSID = "geadsid"
|
||||
SessionAdminId = "adminId"
|
||||
|
||||
SystemdServiceName = "edge-admin"
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/files"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -63,7 +64,7 @@ func generateComponentsJSFile() error {
|
||||
} else {
|
||||
buffer.WriteString("window.REQUEST_COND_COMPONENTS = ")
|
||||
buffer.Write(typesJSON)
|
||||
buffer.Write([]byte{'\n', '\n'})
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// 条件操作符
|
||||
@@ -73,7 +74,7 @@ func generateComponentsJSFile() error {
|
||||
} else {
|
||||
buffer.WriteString("window.REQUEST_COND_OPERATORS = ")
|
||||
buffer.Write(requestOperatorsJSON)
|
||||
buffer.Write([]byte{'\n', '\n'})
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// 请求变量
|
||||
@@ -83,7 +84,7 @@ func generateComponentsJSFile() error {
|
||||
} else {
|
||||
buffer.WriteString("window.REQUEST_VARIABLES = ")
|
||||
buffer.Write(requestVariablesJSON)
|
||||
buffer.Write([]byte{'\n', '\n'})
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// 指标
|
||||
@@ -93,7 +94,7 @@ func generateComponentsJSFile() error {
|
||||
} else {
|
||||
buffer.WriteString("window.METRIC_HTTP_KEYS = ")
|
||||
buffer.Write(metricHTTPKeysJSON)
|
||||
buffer.Write([]byte{'\n', '\n'})
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// IP地址阈值项目
|
||||
@@ -103,7 +104,7 @@ func generateComponentsJSFile() error {
|
||||
} else {
|
||||
buffer.WriteString("window.IP_ADDR_THRESHOLD_ITEMS = ")
|
||||
buffer.Write(ipAddrThresholdItemsJSON)
|
||||
buffer.Write([]byte{'\n', '\n'})
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// IP地址阈值动作
|
||||
@@ -113,7 +114,25 @@ func generateComponentsJSFile() error {
|
||||
} else {
|
||||
buffer.WriteString("window.IP_ADDR_THRESHOLD_ACTIONS = ")
|
||||
buffer.Write(ipAddrThresholdActionsJSON)
|
||||
buffer.Write([]byte{'\n', '\n'})
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// WAF checkpoints
|
||||
var wafCheckpointsMaps = []maps.Map{}
|
||||
for _, checkpoint := range firewallconfigs.AllCheckpoints {
|
||||
wafCheckpointsMaps = append(wafCheckpointsMaps, maps.Map{
|
||||
"name": checkpoint.Name,
|
||||
"prefix": checkpoint.Prefix,
|
||||
"description": checkpoint.Description,
|
||||
})
|
||||
}
|
||||
wafCheckpointsJSON, err := json.Marshal(wafCheckpointsMaps)
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal waf rule checkpoints failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.WAF_RULE_CHECKPOINTS = ")
|
||||
buffer.Write(wafCheckpointsJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// WAF操作符
|
||||
@@ -123,7 +142,17 @@ func generateComponentsJSFile() error {
|
||||
} else {
|
||||
buffer.WriteString("window.WAF_RULE_OPERATORS = ")
|
||||
buffer.Write(wafOperatorsJSON)
|
||||
buffer.Write([]byte{'\n', '\n'})
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
// WAF验证码类型
|
||||
captchaTypesJSON, err := json.Marshal(firewallconfigs.FindAllCaptchaTypes())
|
||||
if err != nil {
|
||||
logs.Println("ComponentsAction marshal captcha types failed: " + err.Error())
|
||||
} else {
|
||||
buffer.WriteString("window.WAF_CAPTCHA_TYPES = ")
|
||||
buffer.Write(captchaTypesJSON)
|
||||
buffer.Write([]byte{';', '\n', '\n'})
|
||||
}
|
||||
|
||||
fp, err := os.OpenFile(filepath.Clean(Tea.PublicFile("/js/components.src.js")), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0777)
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/encoding/gzip"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"net"
|
||||
"net/url"
|
||||
@@ -280,34 +281,6 @@ func (this *RPCClient) MessageRPC() pb.MessageServiceClient {
|
||||
return pb.NewMessageServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageRecipientGroupRPC() pb.MessageRecipientGroupServiceClient {
|
||||
return pb.NewMessageRecipientGroupServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageRecipientRPC() pb.MessageRecipientServiceClient {
|
||||
return pb.NewMessageRecipientServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageMediaRPC() pb.MessageMediaServiceClient {
|
||||
return pb.NewMessageMediaServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageMediaInstanceRPC() pb.MessageMediaInstanceServiceClient {
|
||||
return pb.NewMessageMediaInstanceServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageTaskRPC() pb.MessageTaskServiceClient {
|
||||
return pb.NewMessageTaskServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageTaskLogRPC() pb.MessageTaskLogServiceClient {
|
||||
return pb.NewMessageTaskLogServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) MessageReceiverRPC() pb.MessageReceiverServiceClient {
|
||||
return pb.NewMessageReceiverServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) IPLibraryRPC() pb.IPLibraryServiceClient {
|
||||
return pb.NewIPLibraryServiceClient(this.pickConn())
|
||||
}
|
||||
@@ -416,14 +389,6 @@ func (this *RPCClient) NodeTaskRPC() pb.NodeTaskServiceClient {
|
||||
return pb.NewNodeTaskServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) AuthorityKeyRPC() pb.AuthorityKeyServiceClient {
|
||||
return pb.NewAuthorityKeyServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) AuthorityNodeRPC() pb.AuthorityNodeServiceClient {
|
||||
return pb.NewAuthorityNodeServiceClient(this.pickConn())
|
||||
}
|
||||
|
||||
func (this *RPCClient) LatestItemRPC() pb.LatestItemServiceClient {
|
||||
return pb.NewLatestItemServiceClient(this.pickConn())
|
||||
}
|
||||
@@ -550,12 +515,15 @@ func (this *RPCClient) init() error {
|
||||
grpc.MaxCallSendMsgSize(128<<20),
|
||||
grpc.UseCompressor(gzip.Name),
|
||||
)
|
||||
var keepaliveParams = grpc.WithKeepaliveParams(keepalive.ClientParameters{
|
||||
Time: 30 * time.Second,
|
||||
})
|
||||
if u.Scheme == "http" {
|
||||
conn, err = grpc.Dial(apiHost, grpc.WithTransportCredentials(insecure.NewCredentials()), callOptions)
|
||||
conn, err = grpc.Dial(apiHost, grpc.WithTransportCredentials(insecure.NewCredentials()), callOptions, keepaliveParams)
|
||||
} else if u.Scheme == "https" {
|
||||
conn, err = grpc.Dial(apiHost, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})), callOptions)
|
||||
})), callOptions, keepaliveParams)
|
||||
} else {
|
||||
return errors.New("parse endpoint failed: invalid scheme '" + u.Scheme + "'")
|
||||
}
|
||||
|
||||
@@ -34,14 +34,9 @@ func TestRPCClient_NodeRPC(t *testing.T) {
|
||||
|
||||
func TestRPC_Dial_HTTP(t *testing.T) {
|
||||
client, err := NewRPCClient(&configs.APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate"`
|
||||
}{
|
||||
Endpoints: []string{"http://127.0.0.1:8004"},
|
||||
},
|
||||
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
|
||||
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
|
||||
RPCEndpoints: []string{"https://127.0.0.1:8003"},
|
||||
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
|
||||
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
|
||||
}, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -56,14 +51,9 @@ func TestRPC_Dial_HTTP(t *testing.T) {
|
||||
|
||||
func TestRPC_Dial_HTTP_2(t *testing.T) {
|
||||
client, err := NewRPCClient(&configs.APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate"`
|
||||
}{
|
||||
Endpoints: []string{"https://127.0.0.1:8003"},
|
||||
},
|
||||
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
|
||||
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
|
||||
RPCEndpoints: []string{"https://127.0.0.1:8003"},
|
||||
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
|
||||
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
|
||||
}, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -78,14 +68,9 @@ func TestRPC_Dial_HTTP_2(t *testing.T) {
|
||||
|
||||
func TestRPC_Dial_HTTPS(t *testing.T) {
|
||||
client, err := NewRPCClient(&configs.APIConfig{
|
||||
RPC: struct {
|
||||
Endpoints []string `yaml:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate"`
|
||||
}{
|
||||
Endpoints: []string{"https://127.0.0.1:8004"},
|
||||
},
|
||||
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
|
||||
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
|
||||
RPCEndpoints: []string{"https://127.0.0.1:8004"},
|
||||
NodeId: "a7e55782dab39bce0901058a1e14a0e6",
|
||||
Secret: "lvyPobI3BszkJopz5nPTocOs0OLkEJ7y",
|
||||
}, true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package ttlcache
|
||||
|
||||
import "github.com/cespare/xxhash"
|
||||
import "github.com/cespare/xxhash/v2"
|
||||
|
||||
func HashKey(key []byte) uint64 {
|
||||
return xxhash.Sum64(key)
|
||||
|
||||
162
internal/utils/exec/cmd.go
Normal file
162
internal/utils/exec/cmd.go
Normal file
@@ -0,0 +1,162 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package executils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Cmd struct {
|
||||
name string
|
||||
args []string
|
||||
env []string
|
||||
dir string
|
||||
|
||||
ctx context.Context
|
||||
timeout time.Duration
|
||||
cancelFunc func()
|
||||
|
||||
captureStdout bool
|
||||
captureStderr bool
|
||||
|
||||
stdout *bytes.Buffer
|
||||
stderr *bytes.Buffer
|
||||
|
||||
rawCmd *exec.Cmd
|
||||
}
|
||||
|
||||
func NewCmd(name string, args ...string) *Cmd {
|
||||
return &Cmd{
|
||||
name: name,
|
||||
args: args,
|
||||
}
|
||||
}
|
||||
|
||||
func NewTimeoutCmd(timeout time.Duration, name string, args ...string) *Cmd {
|
||||
return (&Cmd{
|
||||
name: name,
|
||||
args: args,
|
||||
}).WithTimeout(timeout)
|
||||
}
|
||||
|
||||
func (this *Cmd) WithTimeout(timeout time.Duration) *Cmd {
|
||||
this.timeout = timeout
|
||||
|
||||
ctx, cancelFunc := context.WithTimeout(context.Background(), timeout)
|
||||
this.ctx = ctx
|
||||
this.cancelFunc = cancelFunc
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *Cmd) WithStdout() *Cmd {
|
||||
this.captureStdout = true
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *Cmd) WithStderr() *Cmd {
|
||||
this.captureStderr = true
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *Cmd) WithEnv(env []string) *Cmd {
|
||||
this.env = env
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *Cmd) WithDir(dir string) *Cmd {
|
||||
this.dir = dir
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *Cmd) Start() error {
|
||||
var cmd = this.compose()
|
||||
return cmd.Start()
|
||||
}
|
||||
|
||||
func (this *Cmd) Wait() error {
|
||||
var cmd = this.compose()
|
||||
return cmd.Wait()
|
||||
}
|
||||
|
||||
func (this *Cmd) Run() error {
|
||||
if this.cancelFunc != nil {
|
||||
defer this.cancelFunc()
|
||||
}
|
||||
|
||||
var cmd = this.compose()
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func (this *Cmd) RawStdout() string {
|
||||
if this.stdout != nil {
|
||||
return this.stdout.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (this *Cmd) Stdout() string {
|
||||
return strings.TrimSpace(this.RawStdout())
|
||||
}
|
||||
|
||||
func (this *Cmd) RawStderr() string {
|
||||
if this.stderr != nil {
|
||||
return this.stderr.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (this *Cmd) Stderr() string {
|
||||
return strings.TrimSpace(this.RawStderr())
|
||||
}
|
||||
|
||||
func (this *Cmd) String() string {
|
||||
if this.rawCmd != nil {
|
||||
return this.rawCmd.String()
|
||||
}
|
||||
var newCmd = exec.Command(this.name, this.args...)
|
||||
return newCmd.String()
|
||||
}
|
||||
|
||||
func (this *Cmd) Process() *os.Process {
|
||||
if this.rawCmd != nil {
|
||||
return this.rawCmd.Process
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Cmd) compose() *exec.Cmd {
|
||||
if this.rawCmd != nil {
|
||||
return this.rawCmd
|
||||
}
|
||||
|
||||
if this.ctx != nil {
|
||||
this.rawCmd = exec.CommandContext(this.ctx, this.name, this.args...)
|
||||
} else {
|
||||
this.rawCmd = exec.Command(this.name, this.args...)
|
||||
}
|
||||
|
||||
if this.env != nil {
|
||||
this.rawCmd.Env = this.env
|
||||
}
|
||||
|
||||
if len(this.dir) > 0 {
|
||||
this.rawCmd.Dir = this.dir
|
||||
}
|
||||
|
||||
if this.captureStdout {
|
||||
this.stdout = &bytes.Buffer{}
|
||||
this.rawCmd.Stdout = this.stdout
|
||||
}
|
||||
if this.captureStderr {
|
||||
this.stderr = &bytes.Buffer{}
|
||||
this.rawCmd.Stderr = this.stderr
|
||||
}
|
||||
|
||||
return this.rawCmd
|
||||
}
|
||||
61
internal/utils/exec/cmd_test.go
Normal file
61
internal/utils/exec/cmd_test.go
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package executils_test
|
||||
|
||||
import (
|
||||
executils "github.com/TeaOSLab/EdgeAdmin/internal/utils/exec"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewTimeoutCmd_Sleep(t *testing.T) {
|
||||
var cmd = executils.NewTimeoutCmd(1*time.Second, "sleep", "3")
|
||||
cmd.WithStdout()
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
t.Log("error:", err)
|
||||
t.Log("stdout:", cmd.Stdout())
|
||||
t.Log("stderr:", cmd.Stderr())
|
||||
}
|
||||
|
||||
func TestNewTimeoutCmd_Echo(t *testing.T) {
|
||||
var cmd = executils.NewTimeoutCmd(10*time.Second, "echo", "-n", "hello")
|
||||
cmd.WithStdout()
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
t.Log("error:", err)
|
||||
t.Log("stdout:", cmd.Stdout())
|
||||
t.Log("stderr:", cmd.Stderr())
|
||||
}
|
||||
|
||||
func TestNewTimeoutCmd_Echo2(t *testing.T) {
|
||||
var cmd = executils.NewCmd("echo", "hello")
|
||||
cmd.WithStdout()
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
t.Log("error:", err)
|
||||
t.Log("stdout:", cmd.Stdout())
|
||||
t.Log("raw stdout:", cmd.RawStdout())
|
||||
t.Log("stderr:", cmd.Stderr())
|
||||
t.Log("raw stderr:", cmd.RawStderr())
|
||||
}
|
||||
|
||||
func TestNewTimeoutCmd_Echo3(t *testing.T) {
|
||||
var cmd = executils.NewCmd("echo", "-n", "hello")
|
||||
err := cmd.Run()
|
||||
t.Log("error:", err)
|
||||
t.Log("stdout:", cmd.Stdout())
|
||||
t.Log("stderr:", cmd.Stderr())
|
||||
}
|
||||
|
||||
func TestCmd_Process(t *testing.T) {
|
||||
var cmd = executils.NewCmd("echo", "-n", "hello")
|
||||
err := cmd.Run()
|
||||
t.Log("error:", err)
|
||||
t.Log(cmd.Process())
|
||||
}
|
||||
|
||||
func TestNewTimeoutCmd_String(t *testing.T) {
|
||||
var cmd = executils.NewCmd("echo", "-n", "hello")
|
||||
t.Log("stdout:", cmd.String())
|
||||
}
|
||||
58
internal/utils/exec/look_linux.go
Normal file
58
internal/utils/exec/look_linux.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build linux
|
||||
|
||||
package executils
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// LookPath customize our LookPath() function, to work in broken $PATH environment variable
|
||||
func LookPath(file string) (string, error) {
|
||||
result, err := exec.LookPath(file)
|
||||
if err == nil && len(result) > 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// add common dirs contains executable files these may be excluded in $PATH environment variable
|
||||
var binPaths = []string{
|
||||
"/usr/sbin",
|
||||
"/usr/bin",
|
||||
"/usr/local/sbin",
|
||||
"/usr/local/bin",
|
||||
}
|
||||
|
||||
for _, binPath := range binPaths {
|
||||
var fullPath = binPath + string(os.PathSeparator) + file
|
||||
|
||||
stat, err := os.Stat(fullPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if stat.IsDir() {
|
||||
return "", syscall.EISDIR
|
||||
}
|
||||
|
||||
var mode = stat.Mode()
|
||||
if mode.IsDir() {
|
||||
return "", syscall.EISDIR
|
||||
}
|
||||
err = syscall.Faccessat(unix.AT_FDCWD, fullPath, unix.X_OK, unix.AT_EACCESS)
|
||||
if err == nil || (err != syscall.ENOSYS && err != syscall.EPERM) {
|
||||
return fullPath, err
|
||||
}
|
||||
if mode&0111 != 0 {
|
||||
return fullPath, nil
|
||||
}
|
||||
return "", fs.ErrPermission
|
||||
}
|
||||
|
||||
return "", &exec.Error{
|
||||
Name: file,
|
||||
Err: exec.ErrNotFound,
|
||||
}
|
||||
}
|
||||
10
internal/utils/exec/look_others.go
Normal file
10
internal/utils/exec/look_others.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build !linux
|
||||
|
||||
package executils
|
||||
|
||||
import "os/exec"
|
||||
|
||||
func LookPath(file string) (string, error) {
|
||||
return exec.LookPath(file)
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
executils "github.com/TeaOSLab/EdgeAdmin/internal/utils/exec"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"os/exec"
|
||||
@@ -14,7 +15,7 @@ func AddPortsToFirewall(ports []int) {
|
||||
// Linux
|
||||
if runtime.GOOS == "linux" {
|
||||
// firewalld
|
||||
firewallCmd, _ := exec.LookPath("firewall-cmd")
|
||||
firewallCmd, _ := executils.LookPath("firewall-cmd")
|
||||
if len(firewallCmd) > 0 {
|
||||
err := exec.Command(firewallCmd, "--add-port="+types.String(port)+"/tcp").Run()
|
||||
if err == nil {
|
||||
|
||||
@@ -3,8 +3,10 @@ package utils
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/miekg/dns"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var sharedDNSClient *dns.Client
|
||||
@@ -33,17 +35,47 @@ func LookupCNAME(host string) (string, error) {
|
||||
m.RecursionDesired = true
|
||||
|
||||
var lastErr error
|
||||
for _, serverAddr := range sharedDNSConfig.Servers {
|
||||
r, _, err := sharedDNSClient.Exchange(m, configutils.QuoteIP(serverAddr)+":"+sharedDNSConfig.Port)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
if len(r.Answer) == 0 {
|
||||
continue
|
||||
}
|
||||
var success = false
|
||||
var result = ""
|
||||
|
||||
return r.Answer[0].(*dns.CNAME).Target, nil
|
||||
var serverAddrs = sharedDNSConfig.Servers
|
||||
|
||||
{
|
||||
var publicDNSHosts = []string{"8.8.8.8" /** Google **/, "8.8.4.4" /** Google **/}
|
||||
for _, publicDNSHost := range publicDNSHosts {
|
||||
if !lists.ContainsString(serverAddrs, publicDNSHost) {
|
||||
serverAddrs = append(serverAddrs, publicDNSHost)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var wg = &sync.WaitGroup{}
|
||||
|
||||
for _, serverAddr := range serverAddrs {
|
||||
wg.Add(1)
|
||||
|
||||
go func(serverAddr string) {
|
||||
defer wg.Done()
|
||||
r, _, err := sharedDNSClient.Exchange(m, configutils.QuoteIP(serverAddr)+":"+sharedDNSConfig.Port)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
return
|
||||
}
|
||||
|
||||
success = true
|
||||
|
||||
if len(r.Answer) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
result = r.Answer[0].(*dns.CNAME).Target
|
||||
}(serverAddr)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if success {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
return "", lastErr
|
||||
}
|
||||
|
||||
@@ -8,5 +8,8 @@ import (
|
||||
)
|
||||
|
||||
func TestLookupCNAME(t *testing.T) {
|
||||
t.Log(utils.LookupCNAME("www.yun4s.cn"))
|
||||
for _, domain := range []string{"www.yun4s.cn", "example.com"} {
|
||||
result, err := utils.LookupCNAME(domain)
|
||||
t.Log(domain, "=>", result, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,17 +30,17 @@ func FormatBytes(bytes int64) string {
|
||||
if bytes < Pow1024(1) {
|
||||
return FormatInt64(bytes) + "B"
|
||||
} else if bytes < Pow1024(2) {
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.2fKB", float64(bytes)/float64(Pow1024(1))))
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.2fKiB", float64(bytes)/float64(Pow1024(1))))
|
||||
} else if bytes < Pow1024(3) {
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.2fMB", float64(bytes)/float64(Pow1024(2))))
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.2fMiB", float64(bytes)/float64(Pow1024(2))))
|
||||
} else if bytes < Pow1024(4) {
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.2fGB", float64(bytes)/float64(Pow1024(3))))
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.2fGiB", float64(bytes)/float64(Pow1024(3))))
|
||||
} else if bytes < Pow1024(5) {
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.2fTB", float64(bytes)/float64(Pow1024(4))))
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.2fTiB", float64(bytes)/float64(Pow1024(4))))
|
||||
} else if bytes < Pow1024(6) {
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.2fPB", float64(bytes)/float64(Pow1024(5))))
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.2fPiB", float64(bytes)/float64(Pow1024(5))))
|
||||
} else {
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.2fEB", float64(bytes)/float64(Pow1024(6))))
|
||||
return TrimZeroSuffix(fmt.Sprintf("%.2fEiB", float64(bytes)/float64(Pow1024(6))))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ package utils
|
||||
import (
|
||||
"errors"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
executils "github.com/TeaOSLab/EdgeAdmin/internal/utils/exec"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/files"
|
||||
"os"
|
||||
@@ -21,7 +22,7 @@ func (this *ServiceManager) Install(exePath string, args []string) error {
|
||||
return errors.New("only root users can install the service")
|
||||
}
|
||||
|
||||
systemd, err := exec.LookPath("systemctl")
|
||||
systemd, err := executils.LookPath("systemctl")
|
||||
if err != nil {
|
||||
return this.installInitService(exePath, args)
|
||||
}
|
||||
@@ -36,7 +37,7 @@ func (this *ServiceManager) Start() error {
|
||||
}
|
||||
|
||||
if files.NewFile(systemdServiceFile).Exists() {
|
||||
systemd, err := exec.LookPath("systemctl")
|
||||
systemd, err := executils.LookPath("systemctl")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -53,7 +54,7 @@ func (this *ServiceManager) Uninstall() error {
|
||||
}
|
||||
|
||||
if files.NewFile(systemdServiceFile).Exists() {
|
||||
systemd, err := exec.LookPath("systemctl")
|
||||
systemd, err := executils.LookPath("systemctl")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -93,7 +94,7 @@ func (this *ServiceManager) installInitService(exePath string, args []string) er
|
||||
return err
|
||||
}
|
||||
|
||||
chkCmd, err := exec.LookPath("chkconfig")
|
||||
chkCmd, err := executils.LookPath("chkconfig")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -111,6 +112,12 @@ func (this *ServiceManager) installSystemdService(systemd, exePath string, args
|
||||
shortName := teaconst.SystemdServiceName
|
||||
longName := "GoEdge Admin" // TODO 将来可以修改
|
||||
|
||||
var startCmd = exePath + " daemon"
|
||||
bashPath, _ := executils.LookPath("bash")
|
||||
if len(bashPath) > 0 {
|
||||
startCmd = bashPath + " -c \"" + startCmd + "\""
|
||||
}
|
||||
|
||||
desc := `### BEGIN INIT INFO
|
||||
# Provides: ` + shortName + `
|
||||
# Required-Start: $all
|
||||
@@ -129,7 +136,7 @@ After=network-online.target
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=5s
|
||||
ExecStart=` + exePath + ` daemon
|
||||
ExecStart=` + startCmd + `
|
||||
ExecStop=` + exePath + ` stop
|
||||
ExecReload=` + exePath + ` reload
|
||||
|
||||
|
||||
95
internal/utils/unzip.go
Normal file
95
internal/utils/unzip.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Unzip struct {
|
||||
zipFile string
|
||||
targetDir string
|
||||
}
|
||||
|
||||
func NewUnzip(zipFile string, targetDir string) *Unzip {
|
||||
return &Unzip{
|
||||
zipFile: zipFile,
|
||||
targetDir: targetDir,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Unzip) Run() error {
|
||||
if len(this.zipFile) == 0 {
|
||||
return errors.New("zip file should not be empty")
|
||||
}
|
||||
if len(this.targetDir) == 0 {
|
||||
return errors.New("target dir should not be empty")
|
||||
}
|
||||
|
||||
reader, err := zip.OpenReader(this.zipFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = reader.Close()
|
||||
}()
|
||||
|
||||
for _, file := range reader.File {
|
||||
var info = file.FileInfo()
|
||||
var target = this.targetDir + "/" + file.Name
|
||||
|
||||
// 目录
|
||||
if info.IsDir() {
|
||||
stat, err := os.Stat(target)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
} else {
|
||||
err = os.MkdirAll(target, info.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if !stat.IsDir() {
|
||||
err = os.MkdirAll(target, info.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// 文件
|
||||
err = func(file *zip.File, target string) error {
|
||||
fileReader, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = fileReader.Close()
|
||||
}()
|
||||
|
||||
// remove old
|
||||
_ = os.Remove(target)
|
||||
|
||||
// create new
|
||||
fileWriter, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, file.FileInfo().Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = fileWriter.Close()
|
||||
}()
|
||||
|
||||
_, err = io.Copy(fileWriter, fileReader)
|
||||
return err
|
||||
}(file, target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
executils "github.com/TeaOSLab/EdgeAdmin/internal/utils/exec"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
@@ -87,14 +88,10 @@ func (this *UpgradeManager) Start() error {
|
||||
}()
|
||||
|
||||
// 检查unzip
|
||||
unzipExe, _ := exec.LookPath("unzip")
|
||||
if len(unzipExe) == 0 {
|
||||
// TODO install unzip automatically or pack with a static 'unzip' file
|
||||
return errors.New("can not find 'unzip' command")
|
||||
}
|
||||
unzipExe, _ := executils.LookPath("unzip")
|
||||
|
||||
// 检查cp
|
||||
cpExe, _ := exec.LookPath("cp")
|
||||
cpExe, _ := executils.LookPath("cp")
|
||||
if len(cpExe) == 0 {
|
||||
return errors.New("can not find 'cp' command")
|
||||
}
|
||||
@@ -232,12 +229,24 @@ func (this *UpgradeManager) Start() error {
|
||||
return fmt.Errorf("remove old dir '%s' failed: %w", unzipDir, err)
|
||||
}
|
||||
}
|
||||
var unzipCmd = exec.Command(unzipExe, "-q", "-o", destFile, "-d", unzipDir)
|
||||
var unzipStderr = &bytes.Buffer{}
|
||||
unzipCmd.Stderr = unzipStderr
|
||||
err = unzipCmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unzip installation file failed: %w: %s", err, unzipStderr.String())
|
||||
|
||||
if len(unzipExe) > 0 {
|
||||
var unzipCmd = exec.Command(unzipExe, "-q", "-o", destFile, "-d", unzipDir)
|
||||
var unzipStderr = &bytes.Buffer{}
|
||||
unzipCmd.Stderr = unzipStderr
|
||||
err = unzipCmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unzip installation file failed: %w: %s", err, unzipStderr.String())
|
||||
}
|
||||
} else {
|
||||
var unzipCmd = &Unzip{
|
||||
zipFile: destFile,
|
||||
targetDir: unzipDir,
|
||||
}
|
||||
err = unzipCmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unzip installation file failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
installationFiles, err := filepath.Glob(unzipDir + "/edge-" + this.component + "/*")
|
||||
|
||||
32
internal/waf/injectionutils/libinjection/COPYING
Normal file
32
internal/waf/injectionutils/libinjection/COPYING
Normal file
@@ -0,0 +1,32 @@
|
||||
Copyright (c) 2012-2016, Nick Galbreath
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
https://github.com/client9/libinjection
|
||||
http://opensource.org/licenses/BSD-3-Clause
|
||||
1
internal/waf/injectionutils/libinjection/README.md
Normal file
1
internal/waf/injectionutils/libinjection/README.md
Normal file
@@ -0,0 +1 @@
|
||||
copy from https://github.com/libinjection/libinjection
|
||||
65
internal/waf/injectionutils/libinjection/src/libinjection.h
Normal file
65
internal/waf/injectionutils/libinjection/src/libinjection.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright 2012-2016 Nick Galbreath
|
||||
* nickg@client9.com
|
||||
* BSD License -- see COPYING.txt for details
|
||||
*
|
||||
* https://libinjection.client9.com/
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LIBINJECTION_H
|
||||
#define LIBINJECTION_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
# define LIBINJECTION_BEGIN_DECLS extern "C" {
|
||||
# define LIBINJECTION_END_DECLS }
|
||||
#else
|
||||
# define LIBINJECTION_BEGIN_DECLS
|
||||
# define LIBINJECTION_END_DECLS
|
||||
#endif
|
||||
|
||||
LIBINJECTION_BEGIN_DECLS
|
||||
|
||||
/*
|
||||
* Pull in size_t
|
||||
*/
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* Version info.
|
||||
*
|
||||
* This is moved into a function to allow SWIG and other auto-generated
|
||||
* binding to not be modified during minor release changes. We change
|
||||
* change the version number in the c source file, and not regenerated
|
||||
* the binding
|
||||
*
|
||||
* See python's normalized version
|
||||
* http://www.python.org/dev/peps/pep-0386/#normalizedversion
|
||||
*/
|
||||
const char* libinjection_version(void);
|
||||
|
||||
/**
|
||||
* Simple API for SQLi detection - returns a SQLi fingerprint or NULL
|
||||
* is benign input
|
||||
*
|
||||
* \param[in] s input string, may contain nulls, does not need to be null-terminated
|
||||
* \param[in] slen input string length
|
||||
* \param[out] fingerprint buffer of 8+ characters. c-string,
|
||||
* \return 1 if SQLi, 0 if benign. fingerprint will be set or set to empty string.
|
||||
*/
|
||||
int libinjection_sqli(const char* s, size_t slen, char fingerprint[]);
|
||||
|
||||
/** ALPHA version of xss detector.
|
||||
*
|
||||
* NOT DONE.
|
||||
*
|
||||
* \param[in] s input string, may contain nulls, does not need to be null-terminated
|
||||
* \param[in] slen input string length
|
||||
* \return 1 if XSS found, 0 if benign
|
||||
*
|
||||
*/
|
||||
int libinjection_xss(const char* s, size_t slen, int strictMode);
|
||||
|
||||
LIBINJECTION_END_DECLS
|
||||
|
||||
#endif /* LIBINJECTION_H */
|
||||
@@ -0,0 +1,868 @@
|
||||
#include "libinjection_html5.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef DEBUG
|
||||
#include <stdio.h>
|
||||
#define TRACE() printf("%s:%d\n", __FUNCTION__, __LINE__)
|
||||
#else
|
||||
#define TRACE()
|
||||
#endif
|
||||
|
||||
|
||||
#define CHAR_EOF -1
|
||||
#define CHAR_NULL 0
|
||||
#define CHAR_BANG 33
|
||||
#define CHAR_DOUBLE 34
|
||||
#define CHAR_PERCENT 37
|
||||
#define CHAR_SINGLE 39
|
||||
#define CHAR_DASH 45
|
||||
#define CHAR_SLASH 47
|
||||
#define CHAR_LT 60
|
||||
#define CHAR_EQUALS 61
|
||||
#define CHAR_GT 62
|
||||
#define CHAR_QUESTION 63
|
||||
#define CHAR_RIGHTB 93
|
||||
#define CHAR_TICK 96
|
||||
|
||||
/* prototypes */
|
||||
|
||||
static int h5_skip_white(h5_state_t* hs);
|
||||
static int h5_is_white(char ch);
|
||||
static int h5_state_eof(h5_state_t* hs);
|
||||
static int h5_state_data(h5_state_t* hs);
|
||||
static int h5_state_tag_open(h5_state_t* hs);
|
||||
static int h5_state_tag_name(h5_state_t* hs);
|
||||
static int h5_state_tag_name_close(h5_state_t* hs);
|
||||
static int h5_state_end_tag_open(h5_state_t* hs);
|
||||
static int h5_state_self_closing_start_tag(h5_state_t* hs);
|
||||
static int h5_state_attribute_name(h5_state_t* hs);
|
||||
static int h5_state_after_attribute_name(h5_state_t* hs);
|
||||
static int h5_state_before_attribute_name(h5_state_t* hs);
|
||||
static int h5_state_before_attribute_value(h5_state_t* hs);
|
||||
static int h5_state_attribute_value_double_quote(h5_state_t* hs);
|
||||
static int h5_state_attribute_value_single_quote(h5_state_t* hs);
|
||||
static int h5_state_attribute_value_back_quote(h5_state_t* hs);
|
||||
static int h5_state_attribute_value_no_quote(h5_state_t* hs);
|
||||
static int h5_state_after_attribute_value_quoted_state(h5_state_t* hs);
|
||||
static int h5_state_comment(h5_state_t* hs);
|
||||
static int h5_state_cdata(h5_state_t* hs);
|
||||
|
||||
|
||||
/* 12.2.4.44 */
|
||||
static int h5_state_bogus_comment(h5_state_t* hs);
|
||||
static int h5_state_bogus_comment2(h5_state_t* hs);
|
||||
|
||||
/* 12.2.4.45 */
|
||||
static int h5_state_markup_declaration_open(h5_state_t* hs);
|
||||
|
||||
/* 8.2.4.52 */
|
||||
static int h5_state_doctype(h5_state_t* hs);
|
||||
|
||||
/**
|
||||
* public function
|
||||
*/
|
||||
void libinjection_h5_init(h5_state_t* hs, const char* s, size_t len, enum html5_flags flags)
|
||||
{
|
||||
memset(hs, 0, sizeof(h5_state_t));
|
||||
hs->s = s;
|
||||
hs->len = len;
|
||||
|
||||
switch (flags) {
|
||||
case DATA_STATE:
|
||||
hs->state = h5_state_data;
|
||||
break;
|
||||
case VALUE_NO_QUOTE:
|
||||
hs->state = h5_state_before_attribute_name;
|
||||
break;
|
||||
case VALUE_SINGLE_QUOTE:
|
||||
hs->state = h5_state_attribute_value_single_quote;
|
||||
break;
|
||||
case VALUE_DOUBLE_QUOTE:
|
||||
hs->state = h5_state_attribute_value_double_quote;
|
||||
break;
|
||||
case VALUE_BACK_QUOTE:
|
||||
hs->state = h5_state_attribute_value_back_quote;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* public function
|
||||
*/
|
||||
int libinjection_h5_next(h5_state_t* hs)
|
||||
{
|
||||
assert(hs->state != NULL);
|
||||
return (*hs->state)(hs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Everything below here is private
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
static int h5_is_white(char ch)
|
||||
{
|
||||
/*
|
||||
* \t = horizontal tab = 0x09
|
||||
* \n = newline = 0x0A
|
||||
* \v = vertical tab = 0x0B
|
||||
* \f = form feed = 0x0C
|
||||
* \r = cr = 0x0D
|
||||
*/
|
||||
return strchr(" \t\n\v\f\r", ch) != NULL;
|
||||
}
|
||||
|
||||
static int h5_skip_white(h5_state_t* hs)
|
||||
{
|
||||
char ch;
|
||||
while (hs->pos < hs->len) {
|
||||
ch = hs->s[hs->pos];
|
||||
switch (ch) {
|
||||
case 0x00: /* IE only */
|
||||
case 0x20:
|
||||
case 0x09:
|
||||
case 0x0A:
|
||||
case 0x0B: /* IE only */
|
||||
case 0x0C:
|
||||
case 0x0D: /* IE only */
|
||||
hs->pos += 1;
|
||||
break;
|
||||
default:
|
||||
return ch;
|
||||
}
|
||||
}
|
||||
return CHAR_EOF;
|
||||
}
|
||||
|
||||
static int h5_state_eof(h5_state_t* hs)
|
||||
{
|
||||
/* eliminate unused function argument warning */
|
||||
(void)hs;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int h5_state_data(h5_state_t* hs)
|
||||
{
|
||||
const char* idx;
|
||||
|
||||
TRACE();
|
||||
assert(hs->len >= hs->pos);
|
||||
idx = (const char*) memchr(hs->s + hs->pos, CHAR_LT, hs->len - hs->pos);
|
||||
if (idx == NULL) {
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = hs->len - hs->pos;
|
||||
hs->token_type = DATA_TEXT;
|
||||
hs->state = h5_state_eof;
|
||||
if (hs->token_len == 0) {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_type = DATA_TEXT;
|
||||
hs->token_len = (size_t)(idx - hs->s) - hs->pos;
|
||||
hs->pos = (size_t)(idx - hs->s) + 1;
|
||||
hs->state = h5_state_tag_open;
|
||||
if (hs->token_len == 0) {
|
||||
return h5_state_tag_open(hs);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 12 2.4.8
|
||||
*/
|
||||
static int h5_state_tag_open(h5_state_t* hs)
|
||||
{
|
||||
char ch;
|
||||
|
||||
TRACE();
|
||||
if (hs->pos >= hs->len) {
|
||||
return 0;
|
||||
}
|
||||
ch = hs->s[hs->pos];
|
||||
if (ch == CHAR_BANG) {
|
||||
hs->pos += 1;
|
||||
return h5_state_markup_declaration_open(hs);
|
||||
} else if (ch == CHAR_SLASH) {
|
||||
hs->pos += 1;
|
||||
hs->is_close = 1;
|
||||
return h5_state_end_tag_open(hs);
|
||||
} else if (ch == CHAR_QUESTION) {
|
||||
hs->pos += 1;
|
||||
return h5_state_bogus_comment(hs);
|
||||
} else if (ch == CHAR_PERCENT) {
|
||||
/* this is not in spec.. alternative comment format used
|
||||
by IE <= 9 and Safari < 4.0.3 */
|
||||
hs->pos += 1;
|
||||
return h5_state_bogus_comment2(hs);
|
||||
} else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
|
||||
return h5_state_tag_name(hs);
|
||||
} else if (ch == CHAR_NULL) {
|
||||
/* IE-ism NULL characters are ignored */
|
||||
return h5_state_tag_name(hs);
|
||||
} else {
|
||||
/* user input mistake in configuring state */
|
||||
if (hs->pos == 0) {
|
||||
return h5_state_data(hs);
|
||||
}
|
||||
hs->token_start = hs->s + hs->pos - 1;
|
||||
hs->token_len = 1;
|
||||
hs->token_type = DATA_TEXT;
|
||||
hs->state = h5_state_data;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 12.2.4.9
|
||||
*/
|
||||
static int h5_state_end_tag_open(h5_state_t* hs)
|
||||
{
|
||||
char ch;
|
||||
|
||||
TRACE();
|
||||
|
||||
if (hs->pos >= hs->len) {
|
||||
return 0;
|
||||
}
|
||||
ch = hs->s[hs->pos];
|
||||
if (ch == CHAR_GT) {
|
||||
return h5_state_data(hs);
|
||||
} else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
|
||||
return h5_state_tag_name(hs);
|
||||
}
|
||||
|
||||
hs->is_close = 0;
|
||||
return h5_state_bogus_comment(hs);
|
||||
}
|
||||
/*
|
||||
*
|
||||
*/
|
||||
static int h5_state_tag_name_close(h5_state_t* hs)
|
||||
{
|
||||
TRACE();
|
||||
hs->is_close = 0;
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = 1;
|
||||
hs->token_type = TAG_NAME_CLOSE;
|
||||
hs->pos += 1;
|
||||
if (hs->pos < hs->len) {
|
||||
hs->state = h5_state_data;
|
||||
} else {
|
||||
hs->state = h5_state_eof;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 12.2.4.10
|
||||
*/
|
||||
static int h5_state_tag_name(h5_state_t* hs)
|
||||
{
|
||||
char ch;
|
||||
size_t pos;
|
||||
|
||||
TRACE();
|
||||
pos = hs->pos;
|
||||
while (pos < hs->len) {
|
||||
ch = hs->s[pos];
|
||||
if (ch == 0) {
|
||||
/* special non-standard case */
|
||||
/* allow nulls in tag name */
|
||||
/* some old browsers apparently allow and ignore them */
|
||||
pos += 1;
|
||||
} else if (h5_is_white(ch)) {
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = pos - hs->pos;
|
||||
hs->token_type = TAG_NAME_OPEN;
|
||||
hs->pos = pos + 1;
|
||||
hs->state = h5_state_before_attribute_name;
|
||||
return 1;
|
||||
} else if (ch == CHAR_SLASH) {
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = pos - hs->pos;
|
||||
hs->token_type = TAG_NAME_OPEN;
|
||||
hs->pos = pos + 1;
|
||||
hs->state = h5_state_self_closing_start_tag;
|
||||
return 1;
|
||||
} else if (ch == CHAR_GT) {
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = pos - hs->pos;
|
||||
if (hs->is_close) {
|
||||
hs->pos = pos + 1;
|
||||
hs->is_close = 0;
|
||||
hs->token_type = TAG_CLOSE;
|
||||
hs->state = h5_state_data;
|
||||
} else {
|
||||
hs->pos = pos;
|
||||
hs->token_type = TAG_NAME_OPEN;
|
||||
hs->state = h5_state_tag_name_close;
|
||||
}
|
||||
return 1;
|
||||
} else {
|
||||
pos += 1;
|
||||
}
|
||||
}
|
||||
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = hs->len - hs->pos;
|
||||
hs->token_type = TAG_NAME_OPEN;
|
||||
hs->state = h5_state_eof;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 12.2.4.34
|
||||
*/
|
||||
static int h5_state_before_attribute_name(h5_state_t* hs)
|
||||
{
|
||||
int ch;
|
||||
|
||||
TRACE();
|
||||
|
||||
/* for manual tail call optimization, see comment below */
|
||||
tail_call:;
|
||||
|
||||
ch = h5_skip_white(hs);
|
||||
switch (ch) {
|
||||
case CHAR_EOF: {
|
||||
return 0;
|
||||
}
|
||||
case CHAR_SLASH: {
|
||||
hs->pos += 1;
|
||||
/* Logically, We want to call h5_state_self_closing_start_tag(hs) here.
|
||||
|
||||
As this function may call us back and the compiler
|
||||
might not implement automatic tail call optimization,
|
||||
this might result in a deep recursion.
|
||||
|
||||
We detect this case here and start over with the current state.
|
||||
*/
|
||||
|
||||
if (hs->pos < hs->len && hs->s[hs->pos] != CHAR_GT) {
|
||||
goto tail_call;
|
||||
}
|
||||
return h5_state_self_closing_start_tag(hs);
|
||||
}
|
||||
case CHAR_GT: {
|
||||
hs->state = h5_state_data;
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = 1;
|
||||
hs->token_type = TAG_NAME_CLOSE;
|
||||
hs->pos += 1;
|
||||
return 1;
|
||||
}
|
||||
default: {
|
||||
return h5_state_attribute_name(hs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int h5_state_attribute_name(h5_state_t* hs)
|
||||
{
|
||||
char ch;
|
||||
size_t pos;
|
||||
|
||||
TRACE();
|
||||
pos = hs->pos + 1;
|
||||
while (pos < hs->len) {
|
||||
ch = hs->s[pos];
|
||||
if (h5_is_white(ch)) {
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = pos - hs->pos;
|
||||
hs->token_type = ATTR_NAME;
|
||||
hs->state = h5_state_after_attribute_name;
|
||||
hs->pos = pos + 1;
|
||||
return 1;
|
||||
} else if (ch == CHAR_SLASH) {
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = pos - hs->pos;
|
||||
hs->token_type = ATTR_NAME;
|
||||
hs->state = h5_state_self_closing_start_tag;
|
||||
hs->pos = pos + 1;
|
||||
return 1;
|
||||
} else if (ch == CHAR_EQUALS) {
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = pos - hs->pos;
|
||||
hs->token_type = ATTR_NAME;
|
||||
hs->state = h5_state_before_attribute_value;
|
||||
hs->pos = pos + 1;
|
||||
return 1;
|
||||
} else if (ch == CHAR_GT) {
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = pos - hs->pos;
|
||||
hs->token_type = ATTR_NAME;
|
||||
hs->state = h5_state_tag_name_close;
|
||||
hs->pos = pos;
|
||||
return 1;
|
||||
} else {
|
||||
pos += 1;
|
||||
}
|
||||
}
|
||||
/* EOF */
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = hs->len - hs->pos;
|
||||
hs->token_type = ATTR_NAME;
|
||||
hs->state = h5_state_eof;
|
||||
hs->pos = hs->len;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 12.2.4.36
|
||||
*/
|
||||
static int h5_state_after_attribute_name(h5_state_t* hs)
|
||||
{
|
||||
int c;
|
||||
|
||||
TRACE();
|
||||
c = h5_skip_white(hs);
|
||||
switch (c) {
|
||||
case CHAR_EOF: {
|
||||
return 0;
|
||||
}
|
||||
case CHAR_SLASH: {
|
||||
hs->pos += 1;
|
||||
return h5_state_self_closing_start_tag(hs);
|
||||
}
|
||||
case CHAR_EQUALS: {
|
||||
hs->pos += 1;
|
||||
return h5_state_before_attribute_value(hs);
|
||||
}
|
||||
case CHAR_GT: {
|
||||
return h5_state_tag_name_close(hs);
|
||||
}
|
||||
default: {
|
||||
return h5_state_attribute_name(hs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 12.2.4.37
|
||||
*/
|
||||
static int h5_state_before_attribute_value(h5_state_t* hs)
|
||||
{
|
||||
int c;
|
||||
TRACE();
|
||||
|
||||
c = h5_skip_white(hs);
|
||||
|
||||
if (c == CHAR_EOF) {
|
||||
hs->state = h5_state_eof;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (c == CHAR_DOUBLE) {
|
||||
return h5_state_attribute_value_double_quote(hs);
|
||||
} else if (c == CHAR_SINGLE) {
|
||||
return h5_state_attribute_value_single_quote(hs);
|
||||
} else if (c == CHAR_TICK) {
|
||||
/* NON STANDARD IE */
|
||||
return h5_state_attribute_value_back_quote(hs);
|
||||
} else {
|
||||
return h5_state_attribute_value_no_quote(hs);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int h5_state_attribute_value_quote(h5_state_t* hs, char qchar)
|
||||
{
|
||||
const char* idx;
|
||||
|
||||
TRACE();
|
||||
|
||||
/* skip initial quote in normal case.
|
||||
* don't do this "if (pos == 0)" since it means we have started
|
||||
* in a non-data state. given an input of '><foo
|
||||
* we want to make 0-length attribute name
|
||||
*/
|
||||
if (hs->pos > 0) {
|
||||
hs->pos += 1;
|
||||
}
|
||||
|
||||
|
||||
idx = (const char*) memchr(hs->s + hs->pos, qchar, hs->len - hs->pos);
|
||||
if (idx == NULL) {
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = hs->len - hs->pos;
|
||||
hs->token_type = ATTR_VALUE;
|
||||
hs->state = h5_state_eof;
|
||||
} else {
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = (size_t)(idx - hs->s) - hs->pos;
|
||||
hs->token_type = ATTR_VALUE;
|
||||
hs->state = h5_state_after_attribute_value_quoted_state;
|
||||
hs->pos += hs->token_len + 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static
|
||||
int h5_state_attribute_value_double_quote(h5_state_t* hs)
|
||||
{
|
||||
TRACE();
|
||||
return h5_state_attribute_value_quote(hs, CHAR_DOUBLE);
|
||||
}
|
||||
|
||||
static
|
||||
int h5_state_attribute_value_single_quote(h5_state_t* hs)
|
||||
{
|
||||
TRACE();
|
||||
return h5_state_attribute_value_quote(hs, CHAR_SINGLE);
|
||||
}
|
||||
|
||||
static
|
||||
int h5_state_attribute_value_back_quote(h5_state_t* hs)
|
||||
{
|
||||
TRACE();
|
||||
return h5_state_attribute_value_quote(hs, CHAR_TICK);
|
||||
}
|
||||
|
||||
static int h5_state_attribute_value_no_quote(h5_state_t* hs)
|
||||
{
|
||||
char ch;
|
||||
size_t pos;
|
||||
|
||||
TRACE();
|
||||
pos = hs->pos;
|
||||
while (pos < hs->len) {
|
||||
ch = hs->s[pos];
|
||||
if (h5_is_white(ch)) {
|
||||
hs->token_type = ATTR_VALUE;
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = pos - hs->pos;
|
||||
hs->pos = pos + 1;
|
||||
hs->state = h5_state_before_attribute_name;
|
||||
return 1;
|
||||
} else if (ch == CHAR_GT) {
|
||||
hs->token_type = ATTR_VALUE;
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = pos - hs->pos;
|
||||
hs->pos = pos;
|
||||
hs->state = h5_state_tag_name_close;
|
||||
return 1;
|
||||
}
|
||||
pos += 1;
|
||||
}
|
||||
TRACE();
|
||||
/* EOF */
|
||||
hs->state = h5_state_eof;
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = hs->len - hs->pos;
|
||||
hs->token_type = ATTR_VALUE;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 12.2.4.41
|
||||
*/
|
||||
static int h5_state_after_attribute_value_quoted_state(h5_state_t* hs)
|
||||
{
|
||||
char ch;
|
||||
|
||||
TRACE();
|
||||
if (hs->pos >= hs->len) {
|
||||
return 0;
|
||||
}
|
||||
ch = hs->s[hs->pos];
|
||||
if (h5_is_white(ch)) {
|
||||
hs->pos += 1;
|
||||
return h5_state_before_attribute_name(hs);
|
||||
} else if (ch == CHAR_SLASH) {
|
||||
hs->pos += 1;
|
||||
return h5_state_self_closing_start_tag(hs);
|
||||
} else if (ch == CHAR_GT) {
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = 1;
|
||||
hs->token_type = TAG_NAME_CLOSE;
|
||||
hs->pos += 1;
|
||||
hs->state = h5_state_data;
|
||||
return 1;
|
||||
} else {
|
||||
return h5_state_before_attribute_name(hs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 12.2.4.43
|
||||
*
|
||||
* WARNING: This function is partially inlined into h5_state_before_attribute_name()
|
||||
*/
|
||||
static int h5_state_self_closing_start_tag(h5_state_t* hs)
|
||||
{
|
||||
char ch;
|
||||
|
||||
TRACE();
|
||||
if (hs->pos >= hs->len) {
|
||||
return 0;
|
||||
}
|
||||
ch = hs->s[hs->pos];
|
||||
if (ch == CHAR_GT) {
|
||||
assert(hs->pos > 0);
|
||||
hs->token_start = hs->s + hs->pos -1;
|
||||
hs->token_len = 2;
|
||||
hs->token_type = TAG_NAME_SELFCLOSE;
|
||||
hs->state = h5_state_data;
|
||||
hs->pos += 1;
|
||||
return 1;
|
||||
} else {
|
||||
return h5_state_before_attribute_name(hs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 12.2.4.44
|
||||
*/
|
||||
static int h5_state_bogus_comment(h5_state_t* hs)
|
||||
{
|
||||
const char* idx;
|
||||
|
||||
TRACE();
|
||||
idx = (const char*) memchr(hs->s + hs->pos, CHAR_GT, hs->len - hs->pos);
|
||||
if (idx == NULL) {
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = hs->len - hs->pos;
|
||||
hs->pos = hs->len;
|
||||
hs->state = h5_state_eof;
|
||||
} else {
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = (size_t)(idx - hs->s) - hs->pos;
|
||||
hs->pos = (size_t)(idx - hs->s) + 1;
|
||||
hs->state = h5_state_data;
|
||||
}
|
||||
|
||||
hs->token_type = TAG_COMMENT;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 12.2.4.44 ALT
|
||||
*/
|
||||
static int h5_state_bogus_comment2(h5_state_t* hs)
|
||||
{
|
||||
const char* idx;
|
||||
size_t pos;
|
||||
|
||||
TRACE();
|
||||
pos = hs->pos;
|
||||
while (1) {
|
||||
idx = (const char*) memchr(hs->s + pos, CHAR_PERCENT, hs->len - pos);
|
||||
if (idx == NULL || (idx + 1 >= hs->s + hs->len)) {
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = hs->len - hs->pos;
|
||||
hs->pos = hs->len;
|
||||
hs->token_type = TAG_COMMENT;
|
||||
hs->state = h5_state_eof;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (*(idx +1) != CHAR_GT) {
|
||||
pos = (size_t)(idx - hs->s) + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* ends in %> */
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = (size_t)(idx - hs->s) - hs->pos;
|
||||
hs->pos = (size_t)(idx - hs->s) + 2;
|
||||
hs->state = h5_state_data;
|
||||
hs->token_type = TAG_COMMENT;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 8.2.4.45
|
||||
*/
|
||||
static int h5_state_markup_declaration_open(h5_state_t* hs)
|
||||
{
|
||||
size_t remaining;
|
||||
|
||||
TRACE();
|
||||
remaining = hs->len - hs->pos;
|
||||
if (remaining >= 7 &&
|
||||
/* case insensitive */
|
||||
(hs->s[hs->pos + 0] == 'D' || hs->s[hs->pos + 0] == 'd') &&
|
||||
(hs->s[hs->pos + 1] == 'O' || hs->s[hs->pos + 1] == 'o') &&
|
||||
(hs->s[hs->pos + 2] == 'C' || hs->s[hs->pos + 2] == 'c') &&
|
||||
(hs->s[hs->pos + 3] == 'T' || hs->s[hs->pos + 3] == 't') &&
|
||||
(hs->s[hs->pos + 4] == 'Y' || hs->s[hs->pos + 4] == 'y') &&
|
||||
(hs->s[hs->pos + 5] == 'P' || hs->s[hs->pos + 5] == 'p') &&
|
||||
(hs->s[hs->pos + 6] == 'E' || hs->s[hs->pos + 6] == 'e')
|
||||
) {
|
||||
return h5_state_doctype(hs);
|
||||
} else if (remaining >= 7 &&
|
||||
/* upper case required */
|
||||
hs->s[hs->pos + 0] == '[' &&
|
||||
hs->s[hs->pos + 1] == 'C' &&
|
||||
hs->s[hs->pos + 2] == 'D' &&
|
||||
hs->s[hs->pos + 3] == 'A' &&
|
||||
hs->s[hs->pos + 4] == 'T' &&
|
||||
hs->s[hs->pos + 5] == 'A' &&
|
||||
hs->s[hs->pos + 6] == '['
|
||||
) {
|
||||
hs->pos += 7;
|
||||
return h5_state_cdata(hs);
|
||||
} else if (remaining >= 2 &&
|
||||
hs->s[hs->pos + 0] == '-' &&
|
||||
hs->s[hs->pos + 1] == '-') {
|
||||
hs->pos += 2;
|
||||
return h5_state_comment(hs);
|
||||
}
|
||||
|
||||
return h5_state_bogus_comment(hs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 12.2.4.48
|
||||
* 12.2.4.49
|
||||
* 12.2.4.50
|
||||
* 12.2.4.51
|
||||
* state machine spec is confusing since it can only look
|
||||
* at one character at a time but simply it's comments end by:
|
||||
* 1) EOF
|
||||
* 2) ending in -->
|
||||
* 3) ending in -!>
|
||||
*/
|
||||
static int h5_state_comment(h5_state_t* hs)
|
||||
{
|
||||
char ch;
|
||||
const char* idx;
|
||||
size_t pos;
|
||||
size_t offset;
|
||||
const char* end = hs->s + hs->len;
|
||||
|
||||
TRACE();
|
||||
pos = hs->pos;
|
||||
while (1) {
|
||||
|
||||
idx = (const char*) memchr(hs->s + pos, CHAR_DASH, hs->len - pos);
|
||||
|
||||
/* did not find anything or has less than 3 chars left */
|
||||
if (idx == NULL || idx > hs->s + hs->len - 3) {
|
||||
hs->state = h5_state_eof;
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = hs->len - hs->pos;
|
||||
hs->token_type = TAG_COMMENT;
|
||||
return 1;
|
||||
}
|
||||
offset = 1;
|
||||
|
||||
/* skip all nulls */
|
||||
while (idx + offset < end && *(idx + offset) == 0) {
|
||||
offset += 1;
|
||||
}
|
||||
if (idx + offset == end) {
|
||||
hs->state = h5_state_eof;
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = hs->len - hs->pos;
|
||||
hs->token_type = TAG_COMMENT;
|
||||
return 1;
|
||||
}
|
||||
|
||||
ch = *(idx + offset);
|
||||
if (ch != CHAR_DASH && ch != CHAR_BANG) {
|
||||
pos = (size_t)(idx - hs->s) + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* need to test */
|
||||
#if 0
|
||||
/* skip all nulls */
|
||||
while (idx + offset < end && *(idx + offset) == 0) {
|
||||
offset += 1;
|
||||
}
|
||||
if (idx + offset == end) {
|
||||
hs->state = h5_state_eof;
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = hs->len - hs->pos;
|
||||
hs->token_type = TAG_COMMENT;
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
offset += 1;
|
||||
if (idx + offset == end) {
|
||||
hs->state = h5_state_eof;
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = hs->len - hs->pos;
|
||||
hs->token_type = TAG_COMMENT;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
ch = *(idx + offset);
|
||||
if (ch != CHAR_GT) {
|
||||
pos = (size_t)(idx - hs->s) + 1;
|
||||
continue;
|
||||
}
|
||||
offset += 1;
|
||||
|
||||
/* ends in --> or -!> */
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = (size_t)(idx - hs->s) - hs->pos;
|
||||
hs->pos = (size_t)(idx + offset - hs->s);
|
||||
hs->state = h5_state_data;
|
||||
hs->token_type = TAG_COMMENT;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static int h5_state_cdata(h5_state_t* hs)
|
||||
{
|
||||
const char* idx;
|
||||
size_t pos;
|
||||
|
||||
TRACE();
|
||||
pos = hs->pos;
|
||||
while (1) {
|
||||
idx = (const char*) memchr(hs->s + pos, CHAR_RIGHTB, hs->len - pos);
|
||||
|
||||
/* did not find anything or has less than 3 chars left */
|
||||
if (idx == NULL || idx > hs->s + hs->len - 3) {
|
||||
hs->state = h5_state_eof;
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = hs->len - hs->pos;
|
||||
hs->token_type = DATA_TEXT;
|
||||
return 1;
|
||||
} else if ( *(idx+1) == CHAR_RIGHTB && *(idx+2) == CHAR_GT) {
|
||||
hs->state = h5_state_data;
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_len = (size_t)(idx - hs->s) - hs->pos;
|
||||
hs->pos = (size_t)(idx - hs->s) + 3;
|
||||
hs->token_type = DATA_TEXT;
|
||||
return 1;
|
||||
} else {
|
||||
pos = (size_t)(idx - hs->s) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 8.2.4.52
|
||||
* http://www.w3.org/html/wg/drafts/html/master/syntax.html#doctype-state
|
||||
*/
|
||||
static int h5_state_doctype(h5_state_t* hs)
|
||||
{
|
||||
const char* idx;
|
||||
|
||||
TRACE();
|
||||
hs->token_start = hs->s + hs->pos;
|
||||
hs->token_type = DOCTYPE;
|
||||
|
||||
idx = (const char*) memchr(hs->s + hs->pos, CHAR_GT, hs->len - hs->pos);
|
||||
if (idx == NULL) {
|
||||
hs->state = h5_state_eof;
|
||||
hs->token_len = hs->len - hs->pos;
|
||||
} else {
|
||||
hs->state = h5_state_data;
|
||||
hs->token_len = (size_t)(idx - hs->s) - hs->pos;
|
||||
hs->pos = (size_t)(idx - hs->s) + 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#ifndef LIBINJECTION_HTML5
|
||||
#define LIBINJECTION_HTML5
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* pull in size_t */
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
enum html5_type {
|
||||
DATA_TEXT
|
||||
, TAG_NAME_OPEN
|
||||
, TAG_NAME_CLOSE
|
||||
, TAG_NAME_SELFCLOSE
|
||||
, TAG_DATA
|
||||
, TAG_CLOSE
|
||||
, ATTR_NAME
|
||||
, ATTR_VALUE
|
||||
, TAG_COMMENT
|
||||
, DOCTYPE
|
||||
};
|
||||
|
||||
enum html5_flags {
|
||||
DATA_STATE
|
||||
, VALUE_NO_QUOTE
|
||||
, VALUE_SINGLE_QUOTE
|
||||
, VALUE_DOUBLE_QUOTE
|
||||
, VALUE_BACK_QUOTE
|
||||
};
|
||||
|
||||
struct h5_state;
|
||||
typedef int (*ptr_html5_state)(struct h5_state*);
|
||||
|
||||
typedef struct h5_state {
|
||||
const char* s;
|
||||
size_t len;
|
||||
size_t pos;
|
||||
int is_close;
|
||||
ptr_html5_state state;
|
||||
const char* token_start;
|
||||
size_t token_len;
|
||||
enum html5_type token_type;
|
||||
} h5_state_t;
|
||||
|
||||
|
||||
void libinjection_h5_init(h5_state_t* hs, const char* s, size_t len, enum html5_flags);
|
||||
int libinjection_h5_next(h5_state_t* hs);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
2336
internal/waf/injectionutils/libinjection/src/libinjection_sqli.c
Normal file
2336
internal/waf/injectionutils/libinjection/src/libinjection_sqli.c
Normal file
File diff suppressed because it is too large
Load Diff
294
internal/waf/injectionutils/libinjection/src/libinjection_sqli.h
Normal file
294
internal/waf/injectionutils/libinjection/src/libinjection_sqli.h
Normal file
@@ -0,0 +1,294 @@
|
||||
/**
|
||||
* Copyright 2012-2016 Nick Galbreath
|
||||
* nickg@client9.com
|
||||
* BSD License -- see `COPYING.txt` for details
|
||||
*
|
||||
* https://libinjection.client9.com/
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LIBINJECTION_SQLI_H
|
||||
#define LIBINJECTION_SQLI_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Pull in size_t
|
||||
*/
|
||||
#include <string.h>
|
||||
|
||||
enum sqli_flags {
|
||||
FLAG_NONE = 0
|
||||
, FLAG_QUOTE_NONE = 1 /* 1 << 0 */
|
||||
, FLAG_QUOTE_SINGLE = 2 /* 1 << 1 */
|
||||
, FLAG_QUOTE_DOUBLE = 4 /* 1 << 2 */
|
||||
|
||||
, FLAG_SQL_ANSI = 8 /* 1 << 3 */
|
||||
, FLAG_SQL_MYSQL = 16 /* 1 << 4 */
|
||||
};
|
||||
|
||||
enum lookup_type {
|
||||
LOOKUP_WORD = 1
|
||||
, LOOKUP_TYPE = 2
|
||||
, LOOKUP_OPERATOR = 3
|
||||
, LOOKUP_FINGERPRINT = 4
|
||||
};
|
||||
|
||||
struct libinjection_sqli_token {
|
||||
#ifdef SWIG
|
||||
%immutable;
|
||||
#endif
|
||||
/*
|
||||
* position and length of token
|
||||
* in original string
|
||||
*/
|
||||
size_t pos;
|
||||
size_t len;
|
||||
|
||||
/* count:
|
||||
* in type 'v', used for number of opening '@'
|
||||
* but maybe used in other contexts
|
||||
*/
|
||||
int count;
|
||||
|
||||
char type;
|
||||
char str_open;
|
||||
char str_close;
|
||||
char val[32];
|
||||
};
|
||||
|
||||
typedef struct libinjection_sqli_token stoken_t;
|
||||
|
||||
/**
|
||||
* Pointer to function, takes c-string input,
|
||||
* returns '\0' for no match, else a char
|
||||
*/
|
||||
struct libinjection_sqli_state;
|
||||
typedef char (*ptr_lookup_fn)(struct libinjection_sqli_state*, int lookuptype, const char* word, size_t len);
|
||||
|
||||
struct libinjection_sqli_state {
|
||||
#ifdef SWIG
|
||||
%immutable;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* input, does not need to be null terminated.
|
||||
* it is also not modified.
|
||||
*/
|
||||
const char *s;
|
||||
|
||||
/*
|
||||
* input length
|
||||
*/
|
||||
size_t slen;
|
||||
|
||||
/*
|
||||
* How to lookup a word or fingerprint
|
||||
*/
|
||||
ptr_lookup_fn lookup;
|
||||
void* userdata;
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
int flags;
|
||||
|
||||
/*
|
||||
* pos is the index in the string during tokenization
|
||||
*/
|
||||
size_t pos;
|
||||
|
||||
#ifndef SWIG
|
||||
/* for SWIG.. don't use this.. use functional API instead */
|
||||
|
||||
/* MAX TOKENS + 1 since we use one extra token
|
||||
* to determine the type of the previous token
|
||||
*/
|
||||
struct libinjection_sqli_token tokenvec[8];
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Pointer to token position in tokenvec, above
|
||||
*/
|
||||
struct libinjection_sqli_token *current;
|
||||
|
||||
/*
|
||||
* fingerprint pattern c-string
|
||||
* +1 for ending null
|
||||
* Minimum of 8 bytes to add gcc's -fstack-protector to work
|
||||
*/
|
||||
char fingerprint[8];
|
||||
|
||||
/*
|
||||
* Line number of code that said decided if the input was SQLi or
|
||||
* not. Most of the time it's line that said "it's not a matching
|
||||
* fingerprint" but there is other logic that sometimes approves
|
||||
* an input. This is only useful for debugging.
|
||||
*
|
||||
*/
|
||||
int reason;
|
||||
|
||||
/* Number of ddw (dash-dash-white) comments
|
||||
* These comments are in the form of
|
||||
* '--[whitespace]' or '--[EOF]'
|
||||
*
|
||||
* All databases treat this as a comment.
|
||||
*/
|
||||
int stats_comment_ddw;
|
||||
|
||||
/* Number of ddx (dash-dash-[notwhite]) comments
|
||||
*
|
||||
* ANSI SQL treats these are comments, MySQL treats this as
|
||||
* two unary operators '-' '-'
|
||||
*
|
||||
* If you are parsing result returns FALSE and
|
||||
* stats_comment_dd > 0, you should reparse with
|
||||
* COMMENT_MYSQL
|
||||
*
|
||||
*/
|
||||
int stats_comment_ddx;
|
||||
|
||||
/*
|
||||
* c-style comments found /x .. x/
|
||||
*/
|
||||
int stats_comment_c;
|
||||
|
||||
/* '#' operators or MySQL EOL comments found
|
||||
*
|
||||
*/
|
||||
int stats_comment_hash;
|
||||
|
||||
/*
|
||||
* number of tokens folded away
|
||||
*/
|
||||
int stats_folds;
|
||||
|
||||
/*
|
||||
* total tokens processed
|
||||
*/
|
||||
int stats_tokens;
|
||||
|
||||
};
|
||||
|
||||
typedef struct libinjection_sqli_state sfilter;
|
||||
|
||||
struct libinjection_sqli_token* libinjection_sqli_get_token(
|
||||
struct libinjection_sqli_state* sql_state, int i);
|
||||
|
||||
/*
|
||||
* Version info.
|
||||
*
|
||||
* This is moved into a function to allow SWIG and other auto-generated
|
||||
* binding to not be modified during minor release changes. We change
|
||||
* change the version number in the c source file, and not regenerated
|
||||
* the binding
|
||||
*
|
||||
* See python's normalized version
|
||||
* http://www.python.org/dev/peps/pep-0386/#normalizedversion
|
||||
*/
|
||||
const char* libinjection_version(void);
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void libinjection_sqli_init(struct libinjection_sqli_state *sf,
|
||||
const char* s, size_t len,
|
||||
int flags);
|
||||
|
||||
/**
|
||||
* Main API: tests for SQLi in three possible contexts, no quotes,
|
||||
* single quote and double quote
|
||||
*
|
||||
* \param sql_state core data structure
|
||||
*
|
||||
* \return 1 (true) if SQLi, 0 (false) if benign
|
||||
*/
|
||||
int libinjection_is_sqli(struct libinjection_sqli_state* sql_state);
|
||||
|
||||
/* FOR HACKERS ONLY
|
||||
* provides deep hooks into the decision making process
|
||||
*/
|
||||
void libinjection_sqli_callback(struct libinjection_sqli_state *sf,
|
||||
ptr_lookup_fn fn,
|
||||
void* userdata);
|
||||
|
||||
|
||||
/*
|
||||
* Resets state, but keeps initial string and callbacks
|
||||
*/
|
||||
void libinjection_sqli_reset(struct libinjection_sqli_state *sf,
|
||||
int flags);
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* This detects SQLi in a single context, mostly useful for custom
|
||||
* logic and debugging.
|
||||
*
|
||||
* \param sql_state Main data structure
|
||||
* \param flags flags to adjust parsing
|
||||
*
|
||||
* \returns a pointer to sfilter.fingerprint as convenience
|
||||
* do not free!
|
||||
*
|
||||
*/
|
||||
const char* libinjection_sqli_fingerprint(struct libinjection_sqli_state *sql_state,
|
||||
int flags);
|
||||
|
||||
/**
|
||||
* The default "word" to token-type or fingerprint function. This
|
||||
* uses a ASCII case-insensitive binary tree.
|
||||
*/
|
||||
char libinjection_sqli_lookup_word(struct libinjection_sqli_state *sql_state,
|
||||
int lookup_type,
|
||||
const char* str,
|
||||
size_t len);
|
||||
|
||||
/* Streaming tokenization interface.
|
||||
*
|
||||
* sql_state->current is updated with the current token.
|
||||
*
|
||||
* \returns 1, has a token, keep going, or 0 no tokens
|
||||
*
|
||||
*/
|
||||
int libinjection_sqli_tokenize(struct libinjection_sqli_state *sf);
|
||||
|
||||
/**
|
||||
* parses and folds input, up to 5 tokens
|
||||
*
|
||||
*/
|
||||
int libinjection_sqli_fold(struct libinjection_sqli_state *sf);
|
||||
|
||||
/** The built-in default function to match fingerprints
|
||||
* and do false negative/positive analysis. This calls the following
|
||||
* two functions. With this, you over-ride one part or the other.
|
||||
*
|
||||
* return libinjection_sqli_blacklist(sql_state) &&
|
||||
* libinjection_sqli_not_whitelist(sql_state);
|
||||
*
|
||||
* \param sql_state should be filled out after libinjection_sqli_fingerprint is called
|
||||
*/
|
||||
int libinjection_sqli_check_fingerprint(struct libinjection_sqli_state * sql_state);
|
||||
|
||||
/* Given a pattern determine if it's a SQLi pattern.
|
||||
*
|
||||
* \return TRUE if sqli, false otherwise
|
||||
*/
|
||||
int libinjection_sqli_blacklist(struct libinjection_sqli_state* sql_state);
|
||||
|
||||
/* Given a positive match for a pattern (i.e. pattern is SQLi), this function
|
||||
* does additional analysis to reduce false positives.
|
||||
*
|
||||
* \return TRUE if SQLi, false otherwise
|
||||
*/
|
||||
int libinjection_sqli_not_whitelist(struct libinjection_sqli_state * sql_state);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* LIBINJECTION_SQLI_H */
|
||||
File diff suppressed because it is too large
Load Diff
918
internal/waf/injectionutils/libinjection/src/libinjection_xss.c
Normal file
918
internal/waf/injectionutils/libinjection/src/libinjection_xss.c
Normal file
@@ -0,0 +1,918 @@
|
||||
|
||||
#include "libinjection.h"
|
||||
#include "libinjection_xss.h"
|
||||
#include "libinjection_html5.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
typedef enum attribute {
|
||||
TYPE_NONE
|
||||
, TYPE_BLACK /* ban always */
|
||||
, TYPE_ATTR_URL /* attribute value takes a URL-like object */
|
||||
, TYPE_STYLE
|
||||
, TYPE_ATTR_INDIRECT /* attribute *name* is given in *value* */
|
||||
} attribute_t;
|
||||
|
||||
|
||||
static attribute_t is_black_attr(const char* s, size_t len, int strictMode);
|
||||
static int is_black_tag(const char* s, size_t len, int strictMode);
|
||||
static int is_black_url(const char* s, size_t len);
|
||||
static int cstrcasecmp_with_null(const char *a, const char *b, size_t n);
|
||||
static int html_decode_char_at(const char* src, size_t len, size_t* consumed);
|
||||
static int htmlencode_startswith(const char *a/* prefix */, const char *b /* src */, size_t n);
|
||||
|
||||
|
||||
typedef struct stringtype {
|
||||
const char* name;
|
||||
attribute_t atype;
|
||||
} stringtype_t;
|
||||
|
||||
|
||||
static const int gsHexDecodeMap[256] = {
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 256, 256,
|
||||
256, 256, 256, 256, 256, 10, 11, 12, 13, 14, 15, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 10, 11, 12, 13, 14, 15, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256,
|
||||
256, 256, 256, 256
|
||||
};
|
||||
|
||||
static int html_decode_char_at(const char* src, size_t len, size_t* consumed)
|
||||
{
|
||||
int val = 0;
|
||||
size_t i;
|
||||
int ch;
|
||||
|
||||
if (len == 0 || src == NULL) {
|
||||
*consumed = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
*consumed = 1;
|
||||
if (*src != '&' || len < 2) {
|
||||
return (unsigned char)(*src);
|
||||
}
|
||||
|
||||
|
||||
if (*(src+1) != '#') {
|
||||
/* normally this would be for named entities
|
||||
* but for this case we don't actually care
|
||||
*/
|
||||
return '&';
|
||||
}
|
||||
|
||||
if (*(src+2) == 'x' || *(src+2) == 'X') {
|
||||
ch = (unsigned char) (*(src+3));
|
||||
ch = gsHexDecodeMap[ch];
|
||||
if (ch == 256) {
|
||||
/* degenerate case '&#[?]' */
|
||||
return '&';
|
||||
}
|
||||
val = ch;
|
||||
i = 4;
|
||||
while (i < len) {
|
||||
ch = (unsigned char) src[i];
|
||||
if (ch == ';') {
|
||||
*consumed = i + 1;
|
||||
return val;
|
||||
}
|
||||
ch = gsHexDecodeMap[ch];
|
||||
if (ch == 256) {
|
||||
*consumed = i;
|
||||
return val;
|
||||
}
|
||||
val = (val * 16) + ch;
|
||||
if (val > 0x1000FF) {
|
||||
return '&';
|
||||
}
|
||||
++i;
|
||||
}
|
||||
*consumed = i;
|
||||
return val;
|
||||
} else {
|
||||
i = 2;
|
||||
ch = (unsigned char) src[i];
|
||||
if (ch < '0' || ch > '9') {
|
||||
return '&';
|
||||
}
|
||||
val = ch - '0';
|
||||
i += 1;
|
||||
while (i < len) {
|
||||
ch = (unsigned char) src[i];
|
||||
if (ch == ';') {
|
||||
*consumed = i + 1;
|
||||
return val;
|
||||
}
|
||||
if (ch < '0' || ch > '9') {
|
||||
*consumed = i;
|
||||
return val;
|
||||
}
|
||||
val = (val * 10) + (ch - '0');
|
||||
if (val > 0x1000FF) {
|
||||
return '&';
|
||||
}
|
||||
++i;
|
||||
}
|
||||
*consumed = i;
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* These were mostly extracted from: https://raw.githubusercontent.com/WebKit/WebKit/main/Source/WebCore/dom/EventNames.h
|
||||
*
|
||||
* view-source:
|
||||
* data:
|
||||
* javascript:
|
||||
* events:
|
||||
*/
|
||||
static stringtype_t BLACKATTREVENT[] = {
|
||||
{ "ABORT", TYPE_BLACK }
|
||||
, { "ACTIVATE", TYPE_BLACK }
|
||||
, { "ACTIVE", TYPE_BLACK }
|
||||
, { "ADDSOURCEBUFFER", TYPE_BLACK }
|
||||
, { "ADDSTREAM", TYPE_BLACK }
|
||||
, { "ADDTRACK", TYPE_BLACK }
|
||||
, { "AFTERPRINT", TYPE_BLACK }
|
||||
, { "ANIMATIONCANCEL", TYPE_BLACK }
|
||||
, { "ANIMATIONEND", TYPE_BLACK }
|
||||
, { "ANIMATIONITERATION", TYPE_BLACK }
|
||||
, { "ANIMATIONSTART", TYPE_BLACK }
|
||||
, { "AUDIOEND", TYPE_BLACK }
|
||||
, { "AUDIOPROCESS", TYPE_BLACK }
|
||||
, { "AUDIOSTART", TYPE_BLACK }
|
||||
, { "AUTOCOMPLETEERROR", TYPE_BLACK }
|
||||
, { "AUTOCOMPLETE", TYPE_BLACK }
|
||||
, { "BEFOREACTIVATE", TYPE_BLACK }
|
||||
, { "BEFORECOPY", TYPE_BLACK }
|
||||
, { "BEFORECUT", TYPE_BLACK }
|
||||
, { "BEFOREINPUT", TYPE_BLACK }
|
||||
, { "BEFORELOAD", TYPE_BLACK }
|
||||
, { "BEFOREPASTE", TYPE_BLACK }
|
||||
, { "BEFOREPRINT", TYPE_BLACK }
|
||||
, { "BEFOREUNLOAD", TYPE_BLACK }
|
||||
, { "BEGINEVENT", TYPE_BLACK }
|
||||
, { "BLOCKED", TYPE_BLACK }
|
||||
, { "BLUR", TYPE_BLACK }
|
||||
, { "BOUNDARY", TYPE_BLACK }
|
||||
, { "BUFFEREDAMOUNTLOW", TYPE_BLACK }
|
||||
, { "CACHED", TYPE_BLACK }
|
||||
, { "CANCEL", TYPE_BLACK }
|
||||
, { "CANPLAYTHROUGH", TYPE_BLACK }
|
||||
, { "CANPLAY", TYPE_BLACK }
|
||||
, { "CHANGE", TYPE_BLACK }
|
||||
, { "CHARGINGCHANGE", TYPE_BLACK }
|
||||
, { "CHARGINGTIMECHANGE", TYPE_BLACK }
|
||||
, { "CHECKING", TYPE_BLACK }
|
||||
, { "CLICK", TYPE_BLACK }
|
||||
, { "CLOSE", TYPE_BLACK }
|
||||
, { "COMPLETE", TYPE_BLACK }
|
||||
, { "COMPOSITIONEND", TYPE_BLACK }
|
||||
, { "COMPOSITIONSTART", TYPE_BLACK }
|
||||
, { "COMPOSITIONUPDATE", TYPE_BLACK }
|
||||
, { "CONNECTING", TYPE_BLACK }
|
||||
, { "CONNECTIONSTATECHANGE", TYPE_BLACK }
|
||||
, { "CONNECT", TYPE_BLACK }
|
||||
, { "CONTEXTMENU", TYPE_BLACK }
|
||||
, { "CONTROLLERCHANGE", TYPE_BLACK }
|
||||
, { "COPY", TYPE_BLACK }
|
||||
, { "CUECHANGE", TYPE_BLACK }
|
||||
, { "CUT", TYPE_BLACK }
|
||||
, { "DATAAVAILABLE", TYPE_BLACK }
|
||||
, { "DATACHANNEL", TYPE_BLACK }
|
||||
, { "DBLCLICK", TYPE_BLACK }
|
||||
, { "DEVICECHANGE", TYPE_BLACK }
|
||||
, { "DEVICEMOTION", TYPE_BLACK }
|
||||
, { "DEVICEORIENTATION", TYPE_BLACK }
|
||||
, { "DISCHARGINGTIMECHANGE", TYPE_BLACK }
|
||||
, { "DISCONNECT", TYPE_BLACK }
|
||||
, { "DOMACTIVATE", TYPE_BLACK }
|
||||
, { "DOMCHARACTERDATAMODIFIED", TYPE_BLACK }
|
||||
, { "DOMCONTENTLOADED", TYPE_BLACK }
|
||||
, { "DOMFOCUSIN", TYPE_BLACK }
|
||||
, { "DOMFOCUSOUT", TYPE_BLACK }
|
||||
, { "DOMNODEINSERTEDINTODOCUMENT", TYPE_BLACK }
|
||||
, { "DOMNODEINSERTED", TYPE_BLACK }
|
||||
, { "DOMNODEREMOVEDFROMDOCUMENT", TYPE_BLACK }
|
||||
, { "DOMNODEREMOVED", TYPE_BLACK }
|
||||
, { "DOMSUBTREEMODIFIED", TYPE_BLACK }
|
||||
, { "DOWNLOADING", TYPE_BLACK }
|
||||
, { "DRAGEND", TYPE_BLACK }
|
||||
, { "DRAGENTER", TYPE_BLACK }
|
||||
, { "DRAGLEAVE", TYPE_BLACK }
|
||||
, { "DRAGOVER", TYPE_BLACK }
|
||||
, { "DRAGSTART", TYPE_BLACK }
|
||||
, { "DRAG", TYPE_BLACK }
|
||||
, { "DROP", TYPE_BLACK }
|
||||
, { "DURATIONCHANGE", TYPE_BLACK }
|
||||
, { "EMPTIED", TYPE_BLACK }
|
||||
, { "ENCRYPTED", TYPE_BLACK }
|
||||
, { "ENDED", TYPE_BLACK }
|
||||
, { "ENDEVENT", TYPE_BLACK }
|
||||
, { "END", TYPE_BLACK }
|
||||
, { "ENTERPICTUREINPICTURE", TYPE_BLACK }
|
||||
, { "ENTER", TYPE_BLACK }
|
||||
, { "ERROR", TYPE_BLACK }
|
||||
, { "EXIT", TYPE_BLACK }
|
||||
, { "FETCH", TYPE_BLACK }
|
||||
, { "FINISH", TYPE_BLACK }
|
||||
, { "FOCUSIN", TYPE_BLACK }
|
||||
, { "FOCUSOUT", TYPE_BLACK }
|
||||
, { "FOCUS", TYPE_BLACK }
|
||||
, { "FORMCHANGE", TYPE_BLACK }
|
||||
, { "FORMINPUT", TYPE_BLACK }
|
||||
, { "GAMEPADCONNECTED", TYPE_BLACK }
|
||||
, { "GAMEPADDISCONNECTED", TYPE_BLACK }
|
||||
, { "GESTURECHANGE", TYPE_BLACK }
|
||||
, { "GESTUREEND", TYPE_BLACK }
|
||||
, { "GESTURESCROLLEND", TYPE_BLACK }
|
||||
, { "GESTURESCROLLSTART", TYPE_BLACK }
|
||||
, { "GESTURESCROLLUPDATE", TYPE_BLACK }
|
||||
, { "GESTURESTART", TYPE_BLACK }
|
||||
, { "GESTURETAPDOWN", TYPE_BLACK }
|
||||
, { "GESTURETAP", TYPE_BLACK }
|
||||
, { "GOTPOINTERCAPTURE", TYPE_BLACK }
|
||||
, { "HASHCHANGE", TYPE_BLACK }
|
||||
, { "ICECANDIDATEERROR", TYPE_BLACK }
|
||||
, { "ICECANDIDATE", TYPE_BLACK }
|
||||
, { "ICECONNECTIONSTATECHANGE", TYPE_BLACK }
|
||||
, { "ICEGATHERINGSTATECHANGE", TYPE_BLACK }
|
||||
, { "INACTIVE", TYPE_BLACK }
|
||||
, { "INPUTSOURCESCHANGE", TYPE_BLACK }
|
||||
, { "INPUT", TYPE_BLACK }
|
||||
, { "INSTALL", TYPE_BLACK }
|
||||
, { "INVALID", TYPE_BLACK }
|
||||
, { "KEYDOWN", TYPE_BLACK }
|
||||
, { "KEYPRESS", TYPE_BLACK }
|
||||
, { "KEYSTATUSESCHANGE", TYPE_BLACK }
|
||||
, { "KEYUP", TYPE_BLACK }
|
||||
, { "LANGUAGECHANGE", TYPE_BLACK }
|
||||
, { "LEAVEPICTUREINPICTURE", TYPE_BLACK }
|
||||
, { "LEVELCHANGE", TYPE_BLACK }
|
||||
, { "LOADEDDATA", TYPE_BLACK }
|
||||
, { "LOADEDMETADATA", TYPE_BLACK }
|
||||
, { "LOADEND", TYPE_BLACK }
|
||||
, { "LOADINGDONE", TYPE_BLACK }
|
||||
, { "LOADINGERROR", TYPE_BLACK }
|
||||
, { "LOADING", TYPE_BLACK }
|
||||
, { "LOADSTART", TYPE_BLACK }
|
||||
, { "LOAD", TYPE_BLACK }
|
||||
, { "LOSTPOINTERCAPTURE", TYPE_BLACK }
|
||||
, { "MARK", TYPE_BLACK }
|
||||
, { "MERCHANTVALIDATION", TYPE_BLACK }
|
||||
, { "MESSAGEERROR", TYPE_BLACK }
|
||||
, { "MESSAGE", TYPE_BLACK }
|
||||
, { "MOUSEDOWN", TYPE_BLACK }
|
||||
, { "MOUSEENTER", TYPE_BLACK }
|
||||
, { "MOUSELEAVE", TYPE_BLACK }
|
||||
, { "MOUSEMOVE", TYPE_BLACK }
|
||||
, { "MOUSEOUT", TYPE_BLACK }
|
||||
, { "MOUSEOVER", TYPE_BLACK }
|
||||
, { "MOUSEUP", TYPE_BLACK }
|
||||
, { "MOUSEWHEEL", TYPE_BLACK }
|
||||
, { "MUTE", TYPE_BLACK }
|
||||
, { "NEGOTIATIONNEEDED", TYPE_BLACK }
|
||||
, { "NEXTTRACK", TYPE_BLACK }
|
||||
, { "NOMATCH", TYPE_BLACK }
|
||||
, { "NOUPDATE", TYPE_BLACK }
|
||||
, { "OBSOLETE", TYPE_BLACK }
|
||||
, { "OFFLINE", TYPE_BLACK }
|
||||
, { "ONLINE", TYPE_BLACK }
|
||||
, { "OPEN", TYPE_BLACK }
|
||||
, { "ORIENTATIONCHANGE", TYPE_BLACK }
|
||||
, { "OVERCONSTRAINED", TYPE_BLACK }
|
||||
, { "OVERFLOWCHANGED", TYPE_BLACK }
|
||||
, { "PAGEHIDE", TYPE_BLACK }
|
||||
, { "PAGESHOW", TYPE_BLACK }
|
||||
, { "PASTE", TYPE_BLACK }
|
||||
, { "PAUSE", TYPE_BLACK }
|
||||
, { "PAYERDETAILCHANGE", TYPE_BLACK }
|
||||
, { "PAYMENTAUTHORIZED", TYPE_BLACK }
|
||||
, { "PAYMENTMETHODCHANGE", TYPE_BLACK }
|
||||
, { "PAYMENTMETHODSELECTED", TYPE_BLACK }
|
||||
, { "PLAYING", TYPE_BLACK }
|
||||
, { "PLAY", TYPE_BLACK }
|
||||
, { "POINTERCANCEL", TYPE_BLACK }
|
||||
, { "POINTERDOWN", TYPE_BLACK }
|
||||
, { "POINTERENTER", TYPE_BLACK }
|
||||
, { "POINTERLEAVE", TYPE_BLACK }
|
||||
, { "POINTERLOCKCHANGE", TYPE_BLACK }
|
||||
, { "POINTERLOCKERROR", TYPE_BLACK }
|
||||
, { "POINTERMOVE", TYPE_BLACK }
|
||||
, { "POINTEROUT", TYPE_BLACK }
|
||||
, { "POINTEROVER", TYPE_BLACK }
|
||||
, { "POINTERUP", TYPE_BLACK }
|
||||
, { "POPSTATE", TYPE_BLACK }
|
||||
, { "PREVIOUSTRACK", TYPE_BLACK }
|
||||
, { "PROCESSORERROR", TYPE_BLACK }
|
||||
, { "PROGRESS", TYPE_BLACK }
|
||||
, { "PROPERTYCHANGE", TYPE_BLACK }
|
||||
, { "RATECHANGE", TYPE_BLACK }
|
||||
, { "READYSTATECHANGE", TYPE_BLACK }
|
||||
, { "REJECTIONHANDLED", TYPE_BLACK }
|
||||
, { "REMOVESOURCEBUFFER", TYPE_BLACK }
|
||||
, { "REMOVESTREAM", TYPE_BLACK }
|
||||
, { "REMOVETRACK", TYPE_BLACK }
|
||||
, { "REMOVE", TYPE_BLACK }
|
||||
, { "RESET", TYPE_BLACK }
|
||||
, { "RESIZE", TYPE_BLACK }
|
||||
, { "RESOURCETIMINGBUFFERFULL", TYPE_BLACK }
|
||||
, { "RESULT", TYPE_BLACK }
|
||||
, { "RESUME", TYPE_BLACK }
|
||||
, { "SCROLL", TYPE_BLACK }
|
||||
, { "SEARCH", TYPE_BLACK }
|
||||
, { "SECURITYPOLICYVIOLATION", TYPE_BLACK }
|
||||
, { "SEEKED", TYPE_BLACK }
|
||||
, { "SEEKING", TYPE_BLACK }
|
||||
, { "SELECTEND", TYPE_BLACK }
|
||||
, { "SELECTIONCHANGE", TYPE_BLACK }
|
||||
, { "SELECTSTART", TYPE_BLACK }
|
||||
, { "SELECT", TYPE_BLACK }
|
||||
, { "SHIPPINGADDRESSCHANGE", TYPE_BLACK }
|
||||
, { "SHIPPINGCONTACTSELECTED", TYPE_BLACK }
|
||||
, { "SHIPPINGMETHODSELECTED", TYPE_BLACK }
|
||||
, { "SHIPPINGOPTIONCHANGE", TYPE_BLACK }
|
||||
, { "SHOW", TYPE_BLACK }
|
||||
, { "SIGNALINGSTATECHANGE", TYPE_BLACK }
|
||||
, { "SLOTCHANGE", TYPE_BLACK }
|
||||
, { "SOUNDEND", TYPE_BLACK }
|
||||
, { "SOUNDSTART", TYPE_BLACK }
|
||||
, { "SOURCECLOSE", TYPE_BLACK }
|
||||
, { "SOURCEENDED", TYPE_BLACK }
|
||||
, { "SOURCEOPEN", TYPE_BLACK }
|
||||
, { "SPEECHEND", TYPE_BLACK }
|
||||
, { "SPEECHSTART", TYPE_BLACK }
|
||||
, { "SQUEEZEEND", TYPE_BLACK }
|
||||
, { "SQUEEZESTART", TYPE_BLACK }
|
||||
, { "SQUEEZE", TYPE_BLACK }
|
||||
, { "STALLED", TYPE_BLACK }
|
||||
, { "STARTED", TYPE_BLACK }
|
||||
, { "START", TYPE_BLACK }
|
||||
, { "STATECHANGE", TYPE_BLACK }
|
||||
, { "STOP", TYPE_BLACK }
|
||||
, { "STORAGE", TYPE_BLACK }
|
||||
, { "SUBMIT", TYPE_BLACK }
|
||||
, { "SUCCESS", TYPE_BLACK }
|
||||
, { "SUSPEND", TYPE_BLACK }
|
||||
, { "TEXTINPUT", TYPE_BLACK }
|
||||
, { "TIMEOUT", TYPE_BLACK }
|
||||
, { "TIMEUPDATE", TYPE_BLACK }
|
||||
, { "TOGGLE", TYPE_BLACK }
|
||||
, { "TOGGLE", TYPE_BLACK }
|
||||
, { "TONECHANGE", TYPE_BLACK }
|
||||
, { "TOUCHCANCEL", TYPE_BLACK }
|
||||
, { "TOUCHEND", TYPE_BLACK }
|
||||
, { "TOUCHFORCECHANGE", TYPE_BLACK }
|
||||
, { "TOUCHMOVE", TYPE_BLACK }
|
||||
, { "TOUCHSTART", TYPE_BLACK }
|
||||
, { "TRACK", TYPE_BLACK }
|
||||
, { "TRANSITIONCANCEL", TYPE_BLACK }
|
||||
, { "TRANSITIONEND", TYPE_BLACK }
|
||||
, { "TRANSITIONRUN", TYPE_BLACK }
|
||||
, { "TRANSITIONSTART", TYPE_BLACK }
|
||||
, { "UNCAPTUREDERROR", TYPE_BLACK }
|
||||
, { "UNHANDLEDREJECTION", TYPE_BLACK }
|
||||
, { "UNLOAD", TYPE_BLACK }
|
||||
, { "UNMUTE", TYPE_BLACK }
|
||||
, { "UPDATEEND", TYPE_BLACK }
|
||||
, { "UPDATEFOUND", TYPE_BLACK }
|
||||
, { "UPDATEREADY", TYPE_BLACK }
|
||||
, { "UPDATESTART", TYPE_BLACK }
|
||||
, { "UPDATE", TYPE_BLACK }
|
||||
, { "UPGRADENEEDED", TYPE_BLACK }
|
||||
, { "VALIDATEMERCHANT", TYPE_BLACK }
|
||||
, { "VERSIONCHANGE", TYPE_BLACK }
|
||||
, { "VISIBILITYCHANGE", TYPE_BLACK }
|
||||
, { "VOLUMECHANGE", TYPE_BLACK }
|
||||
, { "WAITINGFORKEY", TYPE_BLACK }
|
||||
, { "WAITING", TYPE_BLACK }
|
||||
, { "WEBGLCONTEXTCHANGED", TYPE_BLACK }
|
||||
, { "WEBGLCONTEXTCREATIONERROR", TYPE_BLACK }
|
||||
, { "WEBGLCONTEXTLOST", TYPE_BLACK }
|
||||
, { "WEBGLCONTEXTRESTORED", TYPE_BLACK }
|
||||
, { "WEBKITANIMATIONEND", TYPE_BLACK }
|
||||
, { "WEBKITANIMATIONITERATION", TYPE_BLACK }
|
||||
, { "WEBKITANIMATIONSTART", TYPE_BLACK }
|
||||
, { "WEBKITBEFORETEXTINSERTED", TYPE_BLACK }
|
||||
, { "WEBKITBEGINFULLSCREEN", TYPE_BLACK }
|
||||
, { "WEBKITCURRENTPLAYBACKTARGETISWIRELESSCHANGED", TYPE_BLACK }
|
||||
, { "WEBKITENDFULLSCREEN", TYPE_BLACK }
|
||||
, { "WEBKITFULLSCREENCHANGE", TYPE_BLACK }
|
||||
, { "WEBKITFULLSCREENERROR", TYPE_BLACK }
|
||||
, { "WEBKITKEYADDED", TYPE_BLACK }
|
||||
, { "WEBKITKEYERROR", TYPE_BLACK }
|
||||
, { "WEBKITKEYMESSAGE", TYPE_BLACK }
|
||||
, { "WEBKITMOUSEFORCECHANGED", TYPE_BLACK }
|
||||
, { "WEBKITMOUSEFORCEDOWN", TYPE_BLACK }
|
||||
, { "WEBKITMOUSEFORCEUP", TYPE_BLACK }
|
||||
, { "WEBKITMOUSEFORCEWILLBEGIN", TYPE_BLACK }
|
||||
, { "WEBKITNEEDKEY", TYPE_BLACK }
|
||||
, { "WEBKITNETWORKINFOCHANGE", TYPE_BLACK }
|
||||
, { "WEBKITPLAYBACKTARGETAVAILABILITYCHANGED", TYPE_BLACK }
|
||||
, { "WEBKITPRESENTATIONMODECHANGED", TYPE_BLACK }
|
||||
, { "WEBKITREGIONOVERSETCHANGE", TYPE_BLACK }
|
||||
, { "WEBKITREMOVESOURCEBUFFER", TYPE_BLACK }
|
||||
, { "WEBKITSOURCECLOSE", TYPE_BLACK }
|
||||
, { "WEBKITSOURCEENDED", TYPE_BLACK }
|
||||
, { "WEBKITSOURCEOPEN", TYPE_BLACK }
|
||||
, { "WEBKITSPEECHCHANGE", TYPE_BLACK }
|
||||
, { "WEBKITTRANSITIONEND", TYPE_BLACK }
|
||||
, { "WEBKITWILLREVEALBOTTOM", TYPE_BLACK }
|
||||
, { "WEBKITWILLREVEALLEFT", TYPE_BLACK }
|
||||
, { "WEBKITWILLREVEALRIGHT", TYPE_BLACK }
|
||||
, { "WEBKITWILLREVEALTOP", TYPE_BLACK }
|
||||
, { "WHEEL", TYPE_BLACK }
|
||||
, { "WRITEEND", TYPE_BLACK }
|
||||
, { "WRITESTART", TYPE_BLACK }
|
||||
, { "WRITE", TYPE_BLACK }
|
||||
, { "ZOOM", TYPE_BLACK }
|
||||
, { NULL, TYPE_NONE }
|
||||
};
|
||||
|
||||
/*
|
||||
* view-source:
|
||||
* data:
|
||||
* javascript:
|
||||
*/
|
||||
static stringtype_t STRICT_BLACKATTR[] = {
|
||||
{ "ACTION", TYPE_ATTR_URL } /* form */
|
||||
, { "ATTRIBUTENAME", TYPE_ATTR_INDIRECT } /* SVG allow indirection of attribute names */
|
||||
, { "BY", TYPE_ATTR_URL } /* SVG */
|
||||
, { "BACKGROUND", TYPE_ATTR_URL } /* IE6, O11 */
|
||||
, { "DATAFORMATAS", TYPE_BLACK } /* IE */
|
||||
, { "DATASRC", TYPE_BLACK } /* IE */
|
||||
, { "DYNSRC", TYPE_ATTR_URL } /* Obsolete img attribute */
|
||||
, { "FILTER", TYPE_STYLE } /* Opera, SVG inline style */
|
||||
, { "FORMACTION", TYPE_ATTR_URL } /* HTML 5 */
|
||||
, { "FOLDER", TYPE_ATTR_URL } /* Only on A tags, IE-only */
|
||||
, { "FROM", TYPE_ATTR_URL } /* SVG */
|
||||
, { "HANDLER", TYPE_ATTR_URL } /* SVG Tiny, Opera */
|
||||
, { "HREF", TYPE_ATTR_URL }
|
||||
, { "LOWSRC", TYPE_ATTR_URL } /* Obsolete img attribute */
|
||||
, { "POSTER", TYPE_ATTR_URL } /* Opera 10,11 */
|
||||
, { "SRC", TYPE_ATTR_URL }
|
||||
, { "STYLE", TYPE_STYLE }
|
||||
, { "TO", TYPE_ATTR_URL } /* SVG */
|
||||
, { "VALUES", TYPE_ATTR_URL } /* SVG */
|
||||
, { "XLINK:HREF", TYPE_ATTR_URL }
|
||||
, { NULL, TYPE_NONE }
|
||||
};
|
||||
|
||||
static stringtype_t BLACKATTR[] = {
|
||||
{ "ACTION", TYPE_ATTR_URL } /* form */
|
||||
, { "ATTRIBUTENAME", TYPE_ATTR_INDIRECT } /* SVG allow indirection of attribute names */
|
||||
, { "BY", TYPE_ATTR_URL } /* SVG */
|
||||
, { "BACKGROUND", TYPE_ATTR_URL } /* IE6, O11 */
|
||||
, { "DATAFORMATAS", TYPE_BLACK } /* IE */
|
||||
, { "DATASRC", TYPE_BLACK } /* IE */
|
||||
, { "DYNSRC", TYPE_ATTR_URL } /* Obsolete img attribute */
|
||||
, { "FILTER", TYPE_STYLE } /* Opera, SVG inline style */
|
||||
, { "FORMACTION", TYPE_ATTR_URL } /* HTML 5 */
|
||||
, { "FOLDER", TYPE_ATTR_URL } /* Only on A tags, IE-only */
|
||||
, { "FROM", TYPE_ATTR_URL } /* SVG */
|
||||
, { "HANDLER", TYPE_ATTR_URL } /* SVG Tiny, Opera */
|
||||
, { "HREF", TYPE_ATTR_URL }
|
||||
, { "LOWSRC", TYPE_ATTR_URL } /* Obsolete img attribute */
|
||||
, { "POSTER", TYPE_ATTR_URL } /* Opera 10,11 */
|
||||
, { "SRC", TYPE_ATTR_URL }
|
||||
, { "TO", TYPE_ATTR_URL } /* SVG */
|
||||
, { "VALUES", TYPE_ATTR_URL } /* SVG */
|
||||
, { "XLINK:HREF", TYPE_ATTR_URL }
|
||||
, { NULL, TYPE_NONE }
|
||||
};
|
||||
|
||||
|
||||
|
||||
/* xmlns */
|
||||
/* `xml-stylesheet` > <eval>, <if expr=> */
|
||||
|
||||
/*
|
||||
static const char* BLACKATTR[] = {
|
||||
"ATTRIBUTENAME",
|
||||
"BACKGROUND",
|
||||
"DATAFORMATAS",
|
||||
"HREF",
|
||||
"SCROLL",
|
||||
"SRC",
|
||||
"STYLE",
|
||||
"SRCDOC",
|
||||
NULL
|
||||
};
|
||||
*/
|
||||
|
||||
// GoEdge: change BLACKTAG to STRICT_BLACKTAG
|
||||
static const char* STRICT_BLACKTAG[] = {
|
||||
"APPLET"
|
||||
, "AUDIO"
|
||||
, "BASE"
|
||||
, "COMMENT" /* IE http://html5sec.org/#38 */
|
||||
, "EMBED"
|
||||
, "FORM"
|
||||
, "FRAME"
|
||||
, "FRAMESET"
|
||||
, "HANDLER" /* Opera SVG, effectively a script tag */
|
||||
, "IFRAME"
|
||||
, "IMPORT"
|
||||
, "ISINDEX"
|
||||
, "LINK"
|
||||
, "LISTENER"
|
||||
/* , "MARQUEE" */
|
||||
, "META"
|
||||
, "NOSCRIPT"
|
||||
, "OBJECT"
|
||||
, "SCRIPT"
|
||||
, "STYLE"
|
||||
, "VIDEO"
|
||||
, "VMLFRAME"
|
||||
, "XML"
|
||||
, "XSS"
|
||||
, NULL
|
||||
};
|
||||
|
||||
static const char* BLACKTAG[] = {
|
||||
"APPLET"
|
||||
/* , "AUDIO" */
|
||||
, "BASE"
|
||||
, "COMMENT" /* IE http://html5sec.org/#38 */
|
||||
, "EMBED"
|
||||
/* , "FORM" */
|
||||
, "FRAME"
|
||||
, "FRAMESET"
|
||||
, "HANDLER" /* Opera SVG, effectively a script tag */
|
||||
, "IFRAME"
|
||||
, "IMPORT"
|
||||
, "ISINDEX"
|
||||
, "LINK"
|
||||
, "LISTENER"
|
||||
/* , "MARQUEE" */
|
||||
, "META"
|
||||
, "NOSCRIPT"
|
||||
, "OBJECT"
|
||||
, "SCRIPT"
|
||||
, "STYLE"
|
||||
/* , "VIDEO" */
|
||||
, "VMLFRAME"
|
||||
, "XSS"
|
||||
, NULL
|
||||
};
|
||||
|
||||
|
||||
static int cstrcasecmp_with_null(const char *a, const char *b, size_t n)
|
||||
{
|
||||
char ca;
|
||||
char cb;
|
||||
/* printf("Comparing to %s %.*s\n", a, (int)n, b); */
|
||||
while (n-- > 0) {
|
||||
cb = *b++;
|
||||
if (cb == '\0') continue;
|
||||
|
||||
ca = *a++;
|
||||
|
||||
if (cb >= 'a' && cb <= 'z') {
|
||||
cb -= 0x20;
|
||||
}
|
||||
/* printf("Comparing %c vs %c with %d left\n", ca, cb, (int)n); */
|
||||
if (ca != cb) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (*a == 0) {
|
||||
/* printf(" MATCH \n"); */
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Does an HTML encoded binary string (const char*, length) start with
|
||||
* a all uppercase c-string (null terminated), case insensitive!
|
||||
*
|
||||
* also ignore any embedded nulls in the HTML string!
|
||||
*
|
||||
* return 1 if match / starts with
|
||||
* return 0 if not
|
||||
*/
|
||||
static int htmlencode_startswith(const char *a, const char *b, size_t n)
|
||||
{
|
||||
size_t consumed;
|
||||
int cb;
|
||||
int first = 1;
|
||||
/* printf("Comparing %s with %.*s\n", a,(int)n,b); */
|
||||
while (n > 0) {
|
||||
if (*a == 0) {
|
||||
/* printf("Match EOL!\n"); */
|
||||
return 1;
|
||||
}
|
||||
cb = html_decode_char_at(b, n, &consumed);
|
||||
b += consumed;
|
||||
n -= consumed;
|
||||
|
||||
if (first && cb <= 32) {
|
||||
/* ignore all leading whitespace and control characters */
|
||||
continue;
|
||||
}
|
||||
first = 0;
|
||||
|
||||
if (cb == 0) {
|
||||
/* always ignore null characters in user input */
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cb == 10) {
|
||||
/* always ignore vertical tab characters in user input */
|
||||
/* who allows this?? */
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cb >= 'a' && cb <= 'z') {
|
||||
/* upcase */
|
||||
cb -= 0x20;
|
||||
}
|
||||
|
||||
if (*a != (char) cb) {
|
||||
/* printf(" %c != %c\n", *a, cb); */
|
||||
/* mismatch */
|
||||
return 0;
|
||||
}
|
||||
a++;
|
||||
}
|
||||
|
||||
return (*a == 0) ? 1 : 0;
|
||||
}
|
||||
|
||||
static int is_black_tag(const char* s, size_t len, int strictMode)
|
||||
{
|
||||
const char** black;
|
||||
|
||||
if (len < 3) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strictMode == 1) {
|
||||
black = STRICT_BLACKTAG;
|
||||
} else {
|
||||
black = BLACKTAG;
|
||||
}
|
||||
while (*black != NULL) {
|
||||
if (cstrcasecmp_with_null(*black, s, len) == 0) {
|
||||
/* printf("Got black tag %s\n", *black); */
|
||||
return 1;
|
||||
}
|
||||
black += 1;
|
||||
}
|
||||
|
||||
/* anything SVG related */
|
||||
if ((s[0] == 's' || s[0] == 'S') &&
|
||||
(s[1] == 'v' || s[1] == 'V') &&
|
||||
(s[2] == 'g' || s[2] == 'G')) {
|
||||
/* printf("Got SVG tag \n"); */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Anything XSL(t) related */
|
||||
if ((s[0] == 'x' || s[0] == 'X') &&
|
||||
(s[1] == 's' || s[1] == 'S') &&
|
||||
(s[2] == 'l' || s[2] == 'L')) {
|
||||
/* printf("Got XSL tag\n"); */
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static attribute_t is_black_attr(const char* s, size_t len, int strictMode)
|
||||
{
|
||||
stringtype_t* black;
|
||||
|
||||
if (len < 2) {
|
||||
return TYPE_NONE;
|
||||
}
|
||||
|
||||
if (len >= 5) {
|
||||
|
||||
/* JavaScript on.* event handlers */
|
||||
if ((s[0] == 'o' || s[0] == 'O') && (s[1] == 'n' || s[1] == 'N')) {
|
||||
black = BLACKATTREVENT;
|
||||
const char *s_without_on = &s[2]; // start comparing from the third char
|
||||
while (black->name != NULL) {
|
||||
if (cstrcasecmp_with_null(black->name, s_without_on, strlen(black->name)) == 0) {
|
||||
/* printf("Got banned attribute name %s\n", black->name); */
|
||||
return black->atype;
|
||||
}
|
||||
black += 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* XMLNS can be used to create arbitrary tags */
|
||||
// goedge: commented for photo uploading
|
||||
//if (cstrcasecmp_with_null("XMLNS", s, 5) == 0 || cstrcasecmp_with_null("XLINK", s, 5) == 0) {
|
||||
/* printf("Got XMLNS and XLINK tags\n"); */
|
||||
// return TYPE_BLACK;
|
||||
//}
|
||||
}
|
||||
|
||||
if (strictMode == 1) {
|
||||
black = STRICT_BLACKATTR;
|
||||
} else {
|
||||
black = BLACKATTR;
|
||||
}
|
||||
while (black->name != NULL) {
|
||||
if (cstrcasecmp_with_null(black->name, s, len) == 0) {
|
||||
/* printf("Got banned attribute name %s\n", black->name); */
|
||||
return black->atype;
|
||||
}
|
||||
black += 1;
|
||||
}
|
||||
|
||||
return TYPE_NONE;
|
||||
}
|
||||
|
||||
static int is_black_url(const char* s, size_t len)
|
||||
{
|
||||
|
||||
static const char* data_url = "DATA";
|
||||
static const char* viewsource_url = "VIEW-SOURCE";
|
||||
|
||||
/* obsolete but interesting signal */
|
||||
static const char* vbscript_url = "VBSCRIPT";
|
||||
|
||||
/* covers JAVA, JAVASCRIPT, + colon */
|
||||
static const char* javascript_url = "JAVA";
|
||||
|
||||
/* skip whitespace */
|
||||
while (len > 0 && (*s <= 32 || *s >= 127)) {
|
||||
/*
|
||||
* HEY: this is a signed character.
|
||||
* We are intentionally skipping high-bit characters too
|
||||
* since they are not ASCII, and Opera sometimes uses UTF-8 whitespace.
|
||||
*
|
||||
* Also in EUC-JP some of the high bytes are just ignored.
|
||||
*/
|
||||
++s;
|
||||
--len;
|
||||
}
|
||||
|
||||
if (htmlencode_startswith(data_url, s, len)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (htmlencode_startswith(viewsource_url, s, len)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (htmlencode_startswith(javascript_url, s, len)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (htmlencode_startswith(vbscript_url, s, len)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int libinjection_is_xss(const char* s, size_t len, int flags, int strictMode)
|
||||
{
|
||||
h5_state_t h5;
|
||||
attribute_t attr = TYPE_NONE;
|
||||
|
||||
libinjection_h5_init(&h5, s, len, (enum html5_flags) flags);
|
||||
while (libinjection_h5_next(&h5)) {
|
||||
if (h5.token_type != ATTR_VALUE) {
|
||||
attr = TYPE_NONE;
|
||||
}
|
||||
|
||||
if (h5.token_type == DOCTYPE) {
|
||||
return 1;
|
||||
} else if (h5.token_type == TAG_NAME_OPEN) {
|
||||
if (is_black_tag(h5.token_start, h5.token_len, strictMode)) {
|
||||
return 1;
|
||||
}
|
||||
} else if (h5.token_type == ATTR_NAME) {
|
||||
attr = is_black_attr(h5.token_start, h5.token_len, strictMode);
|
||||
} else if (h5.token_type == ATTR_VALUE) {
|
||||
/*
|
||||
* IE6,7,8 parsing works a bit differently so
|
||||
* a whole <script> or other black tag might be hiding
|
||||
* inside an attribute value under HTML 5 parsing
|
||||
* See http://html5sec.org/#102
|
||||
* to avoid doing a full reparse of the value, just
|
||||
* look for "<". This probably need adjusting to
|
||||
* handle escaped characters
|
||||
*/
|
||||
/*
|
||||
if (memchr(h5.token_start, '<', h5.token_len) != NULL) {
|
||||
return 1;
|
||||
}
|
||||
*/
|
||||
|
||||
switch (attr) {
|
||||
case TYPE_NONE:
|
||||
break;
|
||||
case TYPE_BLACK:
|
||||
return 1;
|
||||
case TYPE_ATTR_URL:
|
||||
if (is_black_url(h5.token_start, h5.token_len)) {
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
case TYPE_STYLE:
|
||||
return 1;
|
||||
case TYPE_ATTR_INDIRECT:
|
||||
/* an attribute name is specified in a _value_ */
|
||||
if (is_black_attr(h5.token_start, h5.token_len, strictMode)) {
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
/*
|
||||
default:
|
||||
assert(0);
|
||||
*/
|
||||
}
|
||||
attr = TYPE_NONE;
|
||||
} else if (h5.token_type == TAG_COMMENT) {
|
||||
/* IE uses a "`" as a tag ending char */
|
||||
// goedge: commented for photo uploading
|
||||
/**if (memchr(h5.token_start, '`', h5.token_len) != NULL) {
|
||||
return 1;
|
||||
}**/
|
||||
|
||||
/* IE conditional comment */
|
||||
if (h5.token_len > 3) {
|
||||
if (h5.token_start[0] == '[' &&
|
||||
(h5.token_start[1] == 'i' || h5.token_start[1] == 'I') &&
|
||||
(h5.token_start[2] == 'f' || h5.token_start[2] == 'F')) {
|
||||
return 1;
|
||||
}
|
||||
if ((h5.token_start[0] == 'x' || h5.token_start[0] == 'X') &&
|
||||
(h5.token_start[1] == 'm' || h5.token_start[1] == 'M') &&
|
||||
(h5.token_start[2] == 'l' || h5.token_start[2] == 'L')) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (h5.token_len > 5) {
|
||||
/* IE <?import pseudo-tag */
|
||||
if (cstrcasecmp_with_null("IMPORT", h5.token_start, 6) == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* XML Entity definition */
|
||||
if (cstrcasecmp_with_null("ENTITY", h5.token_start, 6) == 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* wrapper
|
||||
*
|
||||
*
|
||||
* const char* s: input string, may contain nulls, does not need to be null-terminated.
|
||||
* size_t len: input string length.
|
||||
*
|
||||
*
|
||||
*/
|
||||
int libinjection_xss(const char* s, size_t slen, int strictMode)
|
||||
{
|
||||
if (libinjection_is_xss(s, slen, DATA_STATE, strictMode)) {
|
||||
return 1;
|
||||
}
|
||||
if (libinjection_is_xss(s, slen, VALUE_NO_QUOTE, strictMode)) {
|
||||
return 1;
|
||||
}
|
||||
if (libinjection_is_xss(s, slen, VALUE_SINGLE_QUOTE, strictMode)) {
|
||||
return 1;
|
||||
}
|
||||
if (libinjection_is_xss(s, slen, VALUE_DOUBLE_QUOTE, strictMode)) {
|
||||
return 1;
|
||||
}
|
||||
if (libinjection_is_xss(s, slen, VALUE_BACK_QUOTE, strictMode)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef LIBINJECTION_XSS
|
||||
#define LIBINJECTION_XSS
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* HEY THIS ISN'T DONE
|
||||
*/
|
||||
|
||||
/* pull in size_t */
|
||||
|
||||
#include <string.h>
|
||||
|
||||
int libinjection_is_xss(const char* s, size_t len, int flags, int strictMode);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
313
internal/waf/injectionutils/libinjection/src/reader.c
Normal file
313
internal/waf/injectionutils/libinjection/src/reader.c
Normal file
@@ -0,0 +1,313 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "libinjection.h"
|
||||
#include "libinjection_sqli.h"
|
||||
#include "libinjection_xss.h"
|
||||
|
||||
#ifndef TRUE
|
||||
#define TRUE 1
|
||||
#endif
|
||||
#ifndef FALSE
|
||||
#define FALSE 0
|
||||
#endif
|
||||
|
||||
static int g_test_ok = 0;
|
||||
static int g_test_fail = 0;
|
||||
|
||||
typedef enum {
|
||||
MODE_SQLI,
|
||||
MODE_XSS
|
||||
} detect_mode_t;
|
||||
|
||||
static void usage(const char* program_name);
|
||||
size_t modp_rtrim(char* str, size_t len);
|
||||
void modp_toprint(char* str, size_t len);
|
||||
void test_positive(FILE * fd, const char *fname, detect_mode_t mode,
|
||||
int flag_invert, int flag_true, int flag_quiet);
|
||||
|
||||
int urlcharmap(char ch);
|
||||
size_t modp_url_decode(char* dest, const char* s, size_t len);
|
||||
|
||||
int urlcharmap(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;
|
||||
default:
|
||||
return 256;
|
||||
}
|
||||
}
|
||||
|
||||
size_t modp_url_decode(char* dest, const char* s, size_t len)
|
||||
{
|
||||
const char* deststart = dest;
|
||||
|
||||
size_t i = 0;
|
||||
int d = 0;
|
||||
while (i < len) {
|
||||
switch (s[i]) {
|
||||
case '+':
|
||||
*dest++ = ' ';
|
||||
i += 1;
|
||||
break;
|
||||
case '%':
|
||||
if (i+2 < len) {
|
||||
d = (urlcharmap(s[i+1]) << 4) | urlcharmap(s[i+2]);
|
||||
if ( d < 256) {
|
||||
*dest = (char) d;
|
||||
dest++;
|
||||
i += 3; /* loop will increment one time */
|
||||
} else {
|
||||
*dest++ = '%';
|
||||
i += 1;
|
||||
}
|
||||
} else {
|
||||
*dest++ = '%';
|
||||
i += 1;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
*dest++ = s[i];
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
*dest = '\0';
|
||||
return (size_t)(dest - deststart); /* compute "strlen" of dest */
|
||||
}
|
||||
|
||||
void modp_toprint(char* str, size_t len)
|
||||
{
|
||||
size_t i;
|
||||
for (i = 0; i < len; ++i) {
|
||||
if (str[i] < 32 || str[i] > 126) {
|
||||
str[i] = '?';
|
||||
}
|
||||
}
|
||||
}
|
||||
size_t modp_rtrim(char* str, size_t len)
|
||||
{
|
||||
while (len) {
|
||||
char c = str[len -1];
|
||||
if (c == ' ' || c == '\n' || c == '\t' || c == '\r') {
|
||||
str[len -1] = '\0';
|
||||
len -= 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
void test_positive(FILE * fd, const char *fname, detect_mode_t mode,
|
||||
int flag_invert, int flag_true, int flag_quiet)
|
||||
{
|
||||
char linebuf[8192];
|
||||
int issqli = 0;
|
||||
int linenum = 0;
|
||||
size_t len;
|
||||
sfilter sf;
|
||||
|
||||
while (fgets(linebuf, sizeof(linebuf), fd)) {
|
||||
linenum += 1;
|
||||
len = modp_rtrim(linebuf, strlen(linebuf));
|
||||
if (len == 0) {
|
||||
continue;
|
||||
}
|
||||
if (linebuf[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
len = modp_url_decode(linebuf, linebuf, len);
|
||||
switch (mode) {
|
||||
case MODE_SQLI: {
|
||||
libinjection_sqli_init(&sf, linebuf, len, 0);
|
||||
issqli = libinjection_is_sqli(&sf);
|
||||
break;
|
||||
}
|
||||
case MODE_XSS: {
|
||||
issqli = libinjection_xss(linebuf, len);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
|
||||
if (issqli) {
|
||||
g_test_ok += 1;
|
||||
} else {
|
||||
g_test_fail += 1;
|
||||
}
|
||||
|
||||
if (!flag_quiet) {
|
||||
if ((issqli && flag_true && ! flag_invert) ||
|
||||
(!issqli && flag_true && flag_invert) ||
|
||||
!flag_true) {
|
||||
|
||||
modp_toprint(linebuf, len);
|
||||
|
||||
switch (mode) {
|
||||
case MODE_SQLI: {
|
||||
/*
|
||||
* if we didn't find a SQLi and fingerprint from
|
||||
* sqlstats is is 'sns' or 'snsns' then redo using
|
||||
* plain context
|
||||
*/
|
||||
if (!issqli && (strcmp(sf.fingerprint, "sns") == 0 ||
|
||||
strcmp(sf.fingerprint, "snsns") == 0)) {
|
||||
libinjection_sqli_fingerprint(&sf, 0);
|
||||
}
|
||||
|
||||
fprintf(stdout, "%s\t%d\t%s\t%s\t%s\n",
|
||||
fname, linenum,
|
||||
(issqli ? "True" : "False"), sf.fingerprint, linebuf);
|
||||
break;
|
||||
}
|
||||
case MODE_XSS: {
|
||||
fprintf(stdout, "%s\t%d\t%s\t%s\n",
|
||||
fname, linenum,
|
||||
(issqli ? "True" : "False"), linebuf);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void usage(const char* program_name)
|
||||
{
|
||||
fprintf(stdout, "usage: %s [flags] [files...]\n", program_name);
|
||||
fprintf(stdout, "%s\n", "");
|
||||
fprintf(stdout, "%s\n", "-q --quiet : quiet mode");
|
||||
fprintf(stdout, "%s\n", "-m --max-fails : number of failed cases need to fail entire test");
|
||||
fprintf(stdout, "%s\n", "-s INTEGER : repeat each test N time "
|
||||
"(for performance testing)");
|
||||
fprintf(stdout, "%s\n", "-t : only print positive matches");
|
||||
fprintf(stdout, "%s\n", "-x --mode-xss : test input for XSS");
|
||||
fprintf(stdout, "%s\n", "-i --invert : invert test logic "
|
||||
"(input is tested for being safe)");
|
||||
|
||||
fprintf(stdout, "%s\n", "");
|
||||
fprintf(stdout, "%s\n", "-? -h -help --help : this page");
|
||||
fprintf(stdout, "%s\n", "");
|
||||
}
|
||||
|
||||
int main(int argc, const char *argv[])
|
||||
{
|
||||
/*
|
||||
* invert output, by
|
||||
*/
|
||||
int flag_invert = FALSE;
|
||||
|
||||
/*
|
||||
* don't print anything.. useful for
|
||||
* performance monitors, gprof.
|
||||
*/
|
||||
int flag_quiet = FALSE;
|
||||
|
||||
/*
|
||||
* only print positive results
|
||||
* with invert, only print negative results
|
||||
*/
|
||||
int flag_true = FALSE;
|
||||
detect_mode_t mode = MODE_SQLI;
|
||||
|
||||
int flag_slow = 1;
|
||||
int count = 0;
|
||||
int max = -1;
|
||||
|
||||
int i, j;
|
||||
int offset = 1;
|
||||
|
||||
while (offset < argc) {
|
||||
if (strcmp(argv[offset], "-?") == 0 ||
|
||||
strcmp(argv[offset], "-h") == 0 ||
|
||||
strcmp(argv[offset], "-help") == 0 ||
|
||||
strcmp(argv[offset], "--help") == 0) {
|
||||
usage(argv[0]);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (strcmp(argv[offset], "-i") == 0) {
|
||||
offset += 1;
|
||||
flag_invert = TRUE;
|
||||
} else if (strcmp(argv[offset], "-q") == 0 ||
|
||||
strcmp(argv[offset], "--quiet") == 0) {
|
||||
offset += 1;
|
||||
flag_quiet = TRUE;
|
||||
} else if (strcmp(argv[offset], "-t") == 0) {
|
||||
offset += 1;
|
||||
flag_true = TRUE;
|
||||
} else if (strcmp(argv[offset], "-s") == 0) {
|
||||
offset += 1;
|
||||
flag_slow = 100;
|
||||
} else if (strcmp(argv[offset], "-m") == 0 ||
|
||||
strcmp(argv[offset], "--max-fails") == 0) {
|
||||
offset += 1;
|
||||
max = atoi(argv[offset]);
|
||||
offset += 1;
|
||||
} else if (strcmp(argv[offset], "-x") == 0 ||
|
||||
strcmp(argv[offset], "--mode-xss") == 0) {
|
||||
mode = MODE_XSS;
|
||||
offset += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (offset == argc) {
|
||||
test_positive(stdin, "stdin", mode, flag_invert, flag_true, flag_quiet);
|
||||
} else {
|
||||
for (j = 0; j < flag_slow; ++j) {
|
||||
for (i = offset; i < argc; ++i) {
|
||||
FILE* fd = fopen(argv[i], "r");
|
||||
if (fd) {
|
||||
test_positive(fd, argv[i], mode, flag_invert, flag_true, flag_quiet);
|
||||
fclose(fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!flag_quiet) {
|
||||
fprintf(stdout, "%s", "\n");
|
||||
fprintf(stdout, "SQLI : %d\n", g_test_ok);
|
||||
fprintf(stdout, "SAFE : %d\n", g_test_fail);
|
||||
fprintf(stdout, "TOTAL : %d\n", g_test_ok + g_test_fail);
|
||||
}
|
||||
|
||||
if (max == -1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
count = g_test_ok;
|
||||
if (flag_invert) {
|
||||
count = g_test_fail;
|
||||
}
|
||||
|
||||
if (count > max) {
|
||||
printf("\nThreshold is %d, got %d, failing.\n", max, count);
|
||||
return 1;
|
||||
} else {
|
||||
printf("\nThreshold is %d, got %d, passing.\n", max, count);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
165
internal/waf/injectionutils/libinjection/src/sqli_cli.c
Normal file
165
internal/waf/injectionutils/libinjection/src/sqli_cli.c
Normal file
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* Copyright 2012, 2013 Nick Galbreath
|
||||
* nickg@client9.com
|
||||
* BSD License -- see COPYING.txt for details
|
||||
*
|
||||
* This is for testing against files in ../data/ *.txt
|
||||
* Reads from stdin or a list of files, and emits if a line
|
||||
* is a SQLi attack or not, and does basic statistics
|
||||
*
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "libinjection.h"
|
||||
#include "libinjection_sqli.h"
|
||||
|
||||
void print_string(stoken_t* t);
|
||||
void print_var(stoken_t* t);
|
||||
void print_token(stoken_t *t);
|
||||
void usage(void);
|
||||
|
||||
void print_string(stoken_t* t)
|
||||
{
|
||||
/* print opening quote */
|
||||
if (t->str_open != '\0') {
|
||||
printf("%c", t->str_open);
|
||||
}
|
||||
|
||||
/* print content */
|
||||
printf("%s", t->val);
|
||||
|
||||
/* print closing quote */
|
||||
if (t->str_close != '\0') {
|
||||
printf("%c", t->str_close);
|
||||
}
|
||||
}
|
||||
|
||||
void print_var(stoken_t* t)
|
||||
{
|
||||
if (t->count >= 1) {
|
||||
printf("%c", '@');
|
||||
}
|
||||
if (t->count == 2) {
|
||||
printf("%c", '@');
|
||||
}
|
||||
print_string(t);
|
||||
}
|
||||
|
||||
void print_token(stoken_t *t) {
|
||||
printf("%c ", t->type);
|
||||
switch (t->type) {
|
||||
case 's':
|
||||
print_string(t);
|
||||
break;
|
||||
case 'v':
|
||||
print_var(t);
|
||||
break;
|
||||
default:
|
||||
printf("%s", t->val);
|
||||
}
|
||||
printf("%s", "\n");
|
||||
}
|
||||
|
||||
void usage(void) {
|
||||
printf("\n");
|
||||
printf("libinjection sqli tester\n");
|
||||
printf("\n");
|
||||
printf(" -ca parse as ANSI SQL\n");
|
||||
printf(" -cm parse as MySQL SQL\n");
|
||||
printf(" -q0 parse as is\n");
|
||||
printf(" -q1 parse in single-quote mode\n");
|
||||
printf(" -q2 parse in doiuble-quote mode\n");
|
||||
printf("\n");
|
||||
printf(" -f --fold fold results\n");
|
||||
printf("\n");
|
||||
printf(" -d --detect detect SQLI. empty reply = not detected\n");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
int main(int argc, const char* argv[])
|
||||
{
|
||||
size_t slen;
|
||||
char* copy;
|
||||
|
||||
int flags = 0;
|
||||
int fold = 0;
|
||||
int detect = 0;
|
||||
|
||||
int i;
|
||||
int count;
|
||||
int offset = 1;
|
||||
int issqli;
|
||||
|
||||
sfilter sf;
|
||||
|
||||
if (argc < 2) {
|
||||
usage();
|
||||
return 1;
|
||||
}
|
||||
while (1) {
|
||||
if (strcmp(argv[offset], "-h") == 0 || strcmp(argv[offset], "-?") == 0 || strcmp(argv[offset], "--help") == 0) {
|
||||
usage();
|
||||
return 1;
|
||||
}
|
||||
if (strcmp(argv[offset], "-m") == 0) {
|
||||
flags |= FLAG_SQL_MYSQL;
|
||||
offset += 1;
|
||||
}
|
||||
else if (strcmp(argv[offset], "-f") == 0 || strcmp(argv[offset], "--fold") == 0) {
|
||||
fold = 1;
|
||||
offset += 1;
|
||||
} else if (strcmp(argv[offset], "-d") == 0 || strcmp(argv[offset], "--detect") == 0) {
|
||||
detect = 1;
|
||||
offset += 1;
|
||||
} else if (strcmp(argv[offset], "-ca") == 0) {
|
||||
flags |= FLAG_SQL_ANSI;
|
||||
offset += 1;
|
||||
} else if (strcmp(argv[offset], "-cm") == 0) {
|
||||
flags |= FLAG_SQL_MYSQL;
|
||||
offset += 1;
|
||||
} else if (strcmp(argv[offset], "-q0") == 0) {
|
||||
flags |= FLAG_QUOTE_NONE;
|
||||
offset += 1;
|
||||
} else if (strcmp(argv[offset], "-q1") == 0) {
|
||||
flags |= FLAG_QUOTE_SINGLE;
|
||||
offset += 1;
|
||||
} else if (strcmp(argv[offset], "-q2") == 0) {
|
||||
flags |= FLAG_QUOTE_DOUBLE;
|
||||
offset += 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* ATTENTION: argv is a C-string, null terminated. We copy this
|
||||
* to it's own location, WITHOUT null byte. This way, valgrind
|
||||
* can see if we run past the buffer.
|
||||
*/
|
||||
|
||||
slen = strlen(argv[offset]);
|
||||
copy = (char* ) malloc(slen);
|
||||
memcpy(copy, argv[offset], slen);
|
||||
libinjection_sqli_init(&sf, copy, slen, flags);
|
||||
|
||||
if (detect == 1) {
|
||||
issqli = libinjection_is_sqli(&sf);
|
||||
if (issqli) {
|
||||
printf("%s\n", sf.fingerprint);
|
||||
}
|
||||
} else if (fold == 1) {
|
||||
count = libinjection_sqli_fold(&sf);
|
||||
for (i = 0; i < count; ++i) {
|
||||
print_token(&(sf.tokenvec[i]));
|
||||
}
|
||||
} else {
|
||||
while (libinjection_sqli_tokenize(&sf)) {
|
||||
print_token(sf.current);
|
||||
}
|
||||
}
|
||||
|
||||
free(copy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
132
internal/waf/injectionutils/libinjection/src/sqlparse2c.py
Executable file
132
internal/waf/injectionutils/libinjection/src/sqlparse2c.py
Executable file
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright 2012, 2013 Nick Galbreath
|
||||
# nickg@client9.com
|
||||
# BSD License -- see COPYING.txt for details
|
||||
#
|
||||
|
||||
"""
|
||||
Converts a libinjection JSON data file to a C header (.h) file
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
def toc(obj):
|
||||
""" main routine """
|
||||
|
||||
print("""
|
||||
#ifndef LIBINJECTION_SQLI_DATA_H
|
||||
#define LIBINJECTION_SQLI_DATA_H
|
||||
|
||||
#include "libinjection.h"
|
||||
#include "libinjection_sqli.h"
|
||||
|
||||
typedef struct {
|
||||
const char *word;
|
||||
char type;
|
||||
} keyword_t;
|
||||
|
||||
static size_t parse_money(sfilter * sf);
|
||||
static size_t parse_other(sfilter * sf);
|
||||
static size_t parse_white(sfilter * sf);
|
||||
static size_t parse_operator1(sfilter *sf);
|
||||
static size_t parse_char(sfilter *sf);
|
||||
static size_t parse_hash(sfilter *sf);
|
||||
static size_t parse_dash(sfilter *sf);
|
||||
static size_t parse_slash(sfilter *sf);
|
||||
static size_t parse_backslash(sfilter * sf);
|
||||
static size_t parse_operator2(sfilter *sf);
|
||||
static size_t parse_string(sfilter *sf);
|
||||
static size_t parse_word(sfilter * sf);
|
||||
static size_t parse_var(sfilter * sf);
|
||||
static size_t parse_number(sfilter * sf);
|
||||
static size_t parse_tick(sfilter * sf);
|
||||
static size_t parse_ustring(sfilter * sf);
|
||||
static size_t parse_qstring(sfilter * sf);
|
||||
static size_t parse_nqstring(sfilter * sf);
|
||||
static size_t parse_xstring(sfilter * sf);
|
||||
static size_t parse_bstring(sfilter * sf);
|
||||
static size_t parse_estring(sfilter * sf);
|
||||
static size_t parse_bword(sfilter * sf);
|
||||
""")
|
||||
|
||||
#
|
||||
# Mapping of character to function
|
||||
#
|
||||
fnmap = {
|
||||
'CHAR_WORD' : 'parse_word',
|
||||
'CHAR_WHITE': 'parse_white',
|
||||
'CHAR_OP1' : 'parse_operator1',
|
||||
'CHAR_UNARY': 'parse_operator1',
|
||||
'CHAR_OP2' : 'parse_operator2',
|
||||
'CHAR_BANG' : 'parse_operator2',
|
||||
'CHAR_BACK' : 'parse_backslash',
|
||||
'CHAR_DASH' : 'parse_dash',
|
||||
'CHAR_STR' : 'parse_string',
|
||||
'CHAR_HASH' : 'parse_hash',
|
||||
'CHAR_NUM' : 'parse_number',
|
||||
'CHAR_SLASH': 'parse_slash',
|
||||
'CHAR_SEMICOLON' : 'parse_char',
|
||||
'CHAR_COMMA': 'parse_char',
|
||||
'CHAR_LEFTPARENS': 'parse_char',
|
||||
'CHAR_RIGHTPARENS': 'parse_char',
|
||||
'CHAR_LEFTBRACE': 'parse_char',
|
||||
'CHAR_RIGHTBRACE': 'parse_char',
|
||||
'CHAR_VAR' : 'parse_var',
|
||||
'CHAR_OTHER': 'parse_other',
|
||||
'CHAR_MONEY': 'parse_money',
|
||||
'CHAR_TICK' : 'parse_tick',
|
||||
'CHAR_UNDERSCORE': 'parse_underscore',
|
||||
'CHAR_USTRING' : 'parse_ustring',
|
||||
'CHAR_QSTRING' : 'parse_qstring',
|
||||
'CHAR_NQSTRING' : 'parse_nqstring',
|
||||
'CHAR_XSTRING' : 'parse_xstring',
|
||||
'CHAR_BSTRING' : 'parse_bstring',
|
||||
'CHAR_ESTRING' : 'parse_estring',
|
||||
'CHAR_BWORD' : 'parse_bword'
|
||||
}
|
||||
print()
|
||||
print("typedef size_t (*pt2Function)(sfilter *sf);")
|
||||
print("static const pt2Function char_parse_map[] = {")
|
||||
pos = 0
|
||||
for character in obj['charmap']:
|
||||
print(" &%s, /* %d */" % (fnmap[character], pos))
|
||||
pos += 1
|
||||
print("};")
|
||||
print()
|
||||
|
||||
# keywords
|
||||
# load them
|
||||
keywords = obj['keywords']
|
||||
|
||||
for fingerprint in list(obj['fingerprints']):
|
||||
fingerprint = '0' + fingerprint.upper()
|
||||
keywords[fingerprint] = 'F'
|
||||
|
||||
needhelp = []
|
||||
for key in keywords.keys():
|
||||
if key != key.upper():
|
||||
needhelp.append(key)
|
||||
|
||||
for key in needhelp:
|
||||
tmpv = keywords[key]
|
||||
del keywords[key]
|
||||
keywords[key.upper()] = tmpv
|
||||
|
||||
print("static const keyword_t sql_keywords[] = {")
|
||||
for k in sorted(keywords.keys()):
|
||||
if len(k) > 31:
|
||||
sys.stderr.write("ERROR: keyword greater than 32 chars\n")
|
||||
sys.exit(1)
|
||||
|
||||
print(" {\"%s\", '%s'}," % (k, keywords[k]))
|
||||
print("};")
|
||||
print("static const size_t sql_keywords_sz = %d;" % (len(keywords), ))
|
||||
|
||||
print("#endif")
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
import json
|
||||
sys.exit(toc(json.load(sys.stdin)))
|
||||
|
||||
3
internal/waf/injectionutils/libinjection_sqli.c
Normal file
3
internal/waf/injectionutils/libinjection_sqli.c
Normal file
@@ -0,0 +1,3 @@
|
||||
#define LIBINJECTION_VERSION "3.9.1"
|
||||
|
||||
#include "libinjection/src/libinjection_sqli.c"
|
||||
6
internal/waf/injectionutils/libinjection_xss.c
Normal file
6
internal/waf/injectionutils/libinjection_xss.c
Normal file
@@ -0,0 +1,6 @@
|
||||
#define LIBINJECTION_VERSION "3.9.1"
|
||||
|
||||
#include "libinjection/src/libinjection_xss.c"
|
||||
#include "libinjection/src/libinjection_html5.c"
|
||||
|
||||
#define GOEDGE_VERSION "25" // last version is for GoEdge change
|
||||
91
internal/waf/injectionutils/utils_sqli.go
Normal file
91
internal/waf/injectionutils/utils_sqli.go
Normal file
@@ -0,0 +1,91 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build gcc
|
||||
|
||||
package injectionutils
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -O2 -I./libinjection/src
|
||||
|
||||
#include <libinjection.h>
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// DetectSQLInjectionCache detect sql injection in string with cache
|
||||
func DetectSQLInjectionCache(input string, isStrict bool, cacheLife int) bool {
|
||||
var l = len(input)
|
||||
|
||||
if l == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if cacheLife <= 0 || l < 128 || l > MaxCacheDataSize {
|
||||
return DetectSQLInjection(input, isStrict)
|
||||
}
|
||||
|
||||
var result = DetectSQLInjection(input, isStrict)
|
||||
return result
|
||||
}
|
||||
|
||||
// DetectSQLInjection detect sql injection in string
|
||||
func DetectSQLInjection(input string, isStrict bool) bool {
|
||||
if len(input) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if !isStrict {
|
||||
if len(input) > 1024 {
|
||||
if !utf8.ValidString(input[:1024]) && !utf8.ValidString(input[:1023]) && !utf8.ValidString(input[:1022]) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if !utf8.ValidString(input) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if detectSQLInjectionOne(input) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 兼容 /PATH?URI
|
||||
if (input[0] == '/' || strings.HasPrefix(input, "http://") || strings.HasPrefix(input, "https://")) && len(input) < 1024 {
|
||||
var argsIndex = strings.Index(input, "?")
|
||||
if argsIndex > 0 {
|
||||
var args = input[argsIndex+1:]
|
||||
unescapeArgs, err := url.QueryUnescape(args)
|
||||
if err == nil && args != unescapeArgs {
|
||||
return detectSQLInjectionOne(args) || detectSQLInjectionOne(unescapeArgs)
|
||||
} else {
|
||||
return detectSQLInjectionOne(args)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unescapedInput, err := url.QueryUnescape(input)
|
||||
if err == nil && input != unescapedInput {
|
||||
return detectSQLInjectionOne(unescapedInput)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func detectSQLInjectionOne(input string) bool {
|
||||
if len(input) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var fingerprint [8]C.char
|
||||
var fingerprintPtr = (*C.char)(unsafe.Pointer(&fingerprint[0]))
|
||||
var cInput = C.CString(input)
|
||||
defer C.free(unsafe.Pointer(cInput))
|
||||
|
||||
return C.libinjection_sqli(cInput, C.size_t(len(input)), fingerprintPtr) == 1
|
||||
}
|
||||
21
internal/waf/injectionutils/utils_sqli_stub.go
Normal file
21
internal/waf/injectionutils/utils_sqli_stub.go
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build !gcc
|
||||
|
||||
package injectionutils
|
||||
|
||||
// DetectSQLInjectionCache detect sql injection in string with cache
|
||||
func DetectSQLInjectionCache(input string, isStrict bool, cacheLife int) bool {
|
||||
// stub
|
||||
return false
|
||||
}
|
||||
|
||||
// DetectSQLInjection detect sql injection in string
|
||||
func DetectSQLInjection(input string, isStrict bool) bool {
|
||||
// stub
|
||||
return false
|
||||
}
|
||||
|
||||
func detectSQLInjectionOne(input string) bool {
|
||||
// stub
|
||||
return false
|
||||
}
|
||||
130
internal/waf/injectionutils/utils_sqli_test.go
Normal file
130
internal/waf/injectionutils/utils_sqli_test.go
Normal file
@@ -0,0 +1,130 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build gcc
|
||||
|
||||
package injectionutils_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/waf/injectionutils"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDetectSQLInjection(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
for _, isStrict := range []bool{true, false} {
|
||||
a.IsTrue(injectionutils.DetectSQLInjection("' UNION SELECT * FROM myTable", isStrict))
|
||||
a.IsTrue(injectionutils.DetectSQLInjection("id=1 ' UNION select * from a", isStrict))
|
||||
a.IsTrue(injectionutils.DetectSQLInjection("asdf asd ; -1' and 1=1 union/* foo */select load_file('/etc/passwd')--", isStrict))
|
||||
a.IsFalse(injectionutils.DetectSQLInjection("' UNION SELECT1 * FROM myTable", isStrict))
|
||||
a.IsFalse(injectionutils.DetectSQLInjection("1234", isStrict))
|
||||
a.IsFalse(injectionutils.DetectSQLInjection("", isStrict))
|
||||
a.IsTrue(injectionutils.DetectSQLInjection("id=123 OR 1=1&b=2", isStrict))
|
||||
a.IsTrue(injectionutils.DetectSQLInjection("id=123&b=456&c=1' or 2=2", isStrict))
|
||||
a.IsFalse(injectionutils.DetectSQLInjection("?", isStrict))
|
||||
a.IsFalse(injectionutils.DetectSQLInjection("/hello?age=22", isStrict))
|
||||
a.IsTrue(injectionutils.DetectSQLInjection("/sql/injection?id=123 or 1=1", isStrict))
|
||||
a.IsTrue(injectionutils.DetectSQLInjection("/sql/injection?id=123%20or%201=1", isStrict))
|
||||
a.IsTrue(injectionutils.DetectSQLInjection("https://example.com/sql/injection?id=123%20or%201=1", isStrict))
|
||||
a.IsTrue(injectionutils.DetectSQLInjection("id=123%20or%201=1", isStrict))
|
||||
a.IsTrue(injectionutils.DetectSQLInjection("https://example.com/' or 1=1", isStrict))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDetectSQLInjection(b *testing.B) {
|
||||
runtime.GOMAXPROCS(4)
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_ = injectionutils.DetectSQLInjection("asdf asd ; -1' and 1=1 union/* foo */select load_file('/etc/passwd')--", false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkDetectSQLInjection_URL(b *testing.B) {
|
||||
runtime.GOMAXPROCS(4)
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_ = injectionutils.DetectSQLInjection("/sql/injection?id=123 or 1=1", false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkDetectSQLInjection_Normal_Small(b *testing.B) {
|
||||
runtime.GOMAXPROCS(4)
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_ = injectionutils.DetectSQLInjection("a/sql/injection?id=1234", false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkDetectSQLInjection_URL_Normal_Small(b *testing.B) {
|
||||
runtime.GOMAXPROCS(4)
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_ = injectionutils.DetectSQLInjection("/sql/injection?id="+types.String(rands.Int64()%10000), false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkDetectSQLInjection_URL_Normal_Middle(b *testing.B) {
|
||||
runtime.GOMAXPROCS(4)
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_ = injectionutils.DetectSQLInjection("/search?q=libinjection+fingerprint&newwindow=1&sca_esv=589290862&sxsrf=AMwHvKnxuLoejn2XlNniffC12E_xc35M7Q%3A1702090118361&ei=htvzzebfFZfo1e8PvLGggAk&ved=0ahUKEwjTsYmnq4GDAxUWdPOHHbwkCJAQ4ddDCBA&uact=5&oq=libinjection+fingerprint&gs_lp=Egxnd3Mtd2l6LXNlcnAiGIxpYmluamVjdGlvbmBmaW5nKXJwcmludTIEEAAYHjIGVAAYCBgeSiEaUPkRWKFZcAJ4AZABAJgBHgGgAfoEqgwDMC40uAEGyAEA-AEBwgIKEAFYTxjWMuiwA-IDBBgAVteIBgGQBgI&sclient=gws-wiz-serp#ip=1", false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkDetectSQLInjection_URL_Normal_Small_Cache(b *testing.B) {
|
||||
runtime.GOMAXPROCS(4)
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_ = injectionutils.DetectSQLInjectionCache("/sql/injection?id="+types.String(rands.Int64()%10000), false, 1800)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkDetectSQLInjection_Normal_Large(b *testing.B) {
|
||||
runtime.GOMAXPROCS(4)
|
||||
|
||||
var s = strings.Repeat("A", 512)
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_ = injectionutils.DetectSQLInjection("a/sql/injection?id="+types.String(rands.Int64()%10000)+"&s="+s+"&v=%20", false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkDetectSQLInjection_Normal_Large_Cache(b *testing.B) {
|
||||
runtime.GOMAXPROCS(4)
|
||||
|
||||
var s = strings.Repeat("A", 512)
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_ = injectionutils.DetectSQLInjectionCache("a/sql/injection?id="+types.String(rands.Int64()%10000)+"&s="+s, false, 1800)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkDetectSQLInjection_URL_Unescape(b *testing.B) {
|
||||
runtime.GOMAXPROCS(4)
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_ = injectionutils.DetectSQLInjection("/sql/injection?id=123%20or%201=1", false)
|
||||
}
|
||||
})
|
||||
}
|
||||
89
internal/waf/injectionutils/utils_xss.go
Normal file
89
internal/waf/injectionutils/utils_xss.go
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build gcc
|
||||
|
||||
package injectionutils
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -O2 -I./libinjection/src
|
||||
|
||||
#include <libinjection.h>
|
||||
#include <stdlib.h>
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"github.com/cespare/xxhash/v2"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const MaxCacheDataSize = 1 << 20
|
||||
|
||||
func DetectXSSCache(input string, isStrict bool, cacheLife int) bool {
|
||||
var l = len(input)
|
||||
|
||||
if l == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if cacheLife <= 0 || l < 512 || l > MaxCacheDataSize {
|
||||
return DetectXSS(input, isStrict)
|
||||
}
|
||||
|
||||
var hash = xxhash.Sum64String(input)
|
||||
var key = "WAF@XSS@" + strconv.FormatUint(hash, 10)
|
||||
if isStrict {
|
||||
key += "@1"
|
||||
}
|
||||
|
||||
var result = DetectXSS(input, isStrict)
|
||||
return result
|
||||
}
|
||||
|
||||
// DetectXSS detect XSS in string
|
||||
func DetectXSS(input string, isStrict bool) bool {
|
||||
if len(input) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if detectXSSOne(input, isStrict) {
|
||||
return true
|
||||
}
|
||||
|
||||
// 兼容 /PATH?URI
|
||||
if (input[0] == '/' || strings.HasPrefix(input, "http://") || strings.HasPrefix(input, "https://")) && len(input) < 1024 {
|
||||
var argsIndex = strings.Index(input, "?")
|
||||
if argsIndex > 0 {
|
||||
var args = input[argsIndex+1:]
|
||||
unescapeArgs, err := url.QueryUnescape(args)
|
||||
if err == nil && args != unescapeArgs {
|
||||
return detectXSSOne(args, isStrict) || detectXSSOne(unescapeArgs, isStrict)
|
||||
} else {
|
||||
return detectXSSOne(args, isStrict)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unescapedInput, err := url.QueryUnescape(input)
|
||||
if err == nil && input != unescapedInput {
|
||||
return detectXSSOne(unescapedInput, isStrict)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func detectXSSOne(input string, isStrict bool) bool {
|
||||
if len(input) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var cInput = C.CString(input)
|
||||
defer C.free(unsafe.Pointer(cInput))
|
||||
|
||||
var isStrictInt = 0
|
||||
if isStrict {
|
||||
isStrictInt = 1
|
||||
}
|
||||
return C.libinjection_xss(cInput, C.size_t(len(input)), C.int(isStrictInt)) == 1
|
||||
}
|
||||
22
internal/waf/injectionutils/utils_xss_stub.go
Normal file
22
internal/waf/injectionutils/utils_xss_stub.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build !gcc
|
||||
|
||||
package injectionutils
|
||||
|
||||
const MaxCacheDataSize = 1 << 20
|
||||
|
||||
func DetectXSSCache(input string, isStrict bool, cacheLife int) bool {
|
||||
// stub
|
||||
return false
|
||||
}
|
||||
|
||||
// DetectXSS detect XSS in string
|
||||
func DetectXSS(input string, isStrict bool) bool {
|
||||
// stub
|
||||
return false
|
||||
}
|
||||
|
||||
func detectXSSOne(input string, isStrict bool) bool {
|
||||
// stub
|
||||
return false
|
||||
}
|
||||
94
internal/waf/injectionutils/utils_xss_test.go
Normal file
94
internal/waf/injectionutils/utils_xss_test.go
Normal file
@@ -0,0 +1,94 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build gcc
|
||||
|
||||
package injectionutils_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/waf/injectionutils"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDetectXSS(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
a.IsFalse(injectionutils.DetectXSS("", true))
|
||||
a.IsFalse(injectionutils.DetectXSS("abc", true))
|
||||
a.IsTrue(injectionutils.DetectXSS("<script>", true))
|
||||
a.IsTrue(injectionutils.DetectXSS("<link>", true))
|
||||
a.IsFalse(injectionutils.DetectXSS("<html><span>", true))
|
||||
a.IsFalse(injectionutils.DetectXSS("<script>", true))
|
||||
a.IsTrue(injectionutils.DetectXSS("/path?onmousedown=a", true))
|
||||
a.IsTrue(injectionutils.DetectXSS("/path?onkeyup=a", true))
|
||||
a.IsTrue(injectionutils.DetectXSS("onkeyup=a", true))
|
||||
a.IsTrue(injectionutils.DetectXSS("<iframe scrolling='no'>", true))
|
||||
a.IsFalse(injectionutils.DetectXSS("<html><body><span>RequestId: 1234567890</span></body></html>", true))
|
||||
a.IsTrue(injectionutils.DetectXSS("name=s&description=%3Cscript+src%3D%22a.js%22%3Edddd%3C%2Fscript%3E", true))
|
||||
a.IsFalse(injectionutils.DetectXSS(`<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="XMP Core 6.0.0">
|
||||
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about=""
|
||||
xmlns:tiff="http://ns.adobe.com/tiff/1.0/">
|
||||
<tiff:Orientation>1</tiff:Orientation>
|
||||
</rdf:Description>
|
||||
</rdf:RDF>
|
||||
</x:xmpmeta>`, true)) // included in some photo files
|
||||
a.IsFalse(injectionutils.DetectXSS(`<xml></xml>`, false))
|
||||
}
|
||||
|
||||
func TestDetectXSS_Strict(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
a.IsFalse(injectionutils.DetectXSS(`<xml></xml>`, false))
|
||||
a.IsTrue(injectionutils.DetectXSS(`<xml></xml>`, true))
|
||||
a.IsFalse(injectionutils.DetectXSS(`<img src=\"\"/>`, false))
|
||||
a.IsFalse(injectionutils.DetectXSS(`<img src=\"test.jpg\"/>`, true))
|
||||
a.IsFalse(injectionutils.DetectXSS(`<a href="aaaa"></a>`, true))
|
||||
a.IsFalse(injectionutils.DetectXSS(`<span style="color: red"></span>`, false))
|
||||
a.IsTrue(injectionutils.DetectXSS(`<span style="color: red"></span>`, true))
|
||||
a.IsFalse(injectionutils.DetectXSS("https://example.com?style=list", false))
|
||||
a.IsTrue(injectionutils.DetectXSS("https://example.com?style=list", true))
|
||||
}
|
||||
|
||||
func BenchmarkDetectXSS_MISS(b *testing.B) {
|
||||
var result = injectionutils.DetectXSS("<html><body><span>RequestId: 1234567890</span></body></html>", false)
|
||||
if result {
|
||||
b.Fatal("'result' should not be 'true'")
|
||||
}
|
||||
|
||||
runtime.GOMAXPROCS(4)
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_ = injectionutils.DetectXSS("<html><body><span>RequestId: 1234567890</span></body></html>", false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkDetectXSS_MISS_Cache(b *testing.B) {
|
||||
var result = injectionutils.DetectXSS("<html><body><span>RequestId: 1234567890</span></body></html>", false)
|
||||
if result {
|
||||
b.Fatal("'result' should not be 'true'")
|
||||
}
|
||||
|
||||
runtime.GOMAXPROCS(4)
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_ = injectionutils.DetectXSSCache("<html><body><span>RequestId: 1234567890</span></body></html>", false, 1800)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkDetectXSS_HIT(b *testing.B) {
|
||||
var result = injectionutils.DetectXSS("<html><body><span>RequestId: 1234567890</span><script src=\"\"></script></body></html>", false)
|
||||
if !result {
|
||||
b.Fatal("'result' should not be 'false'")
|
||||
}
|
||||
|
||||
runtime.GOMAXPROCS(4)
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_ = injectionutils.DetectXSS("<html><body><span>RequestId: 1234567890</span><script src=\"\"></script></body></html>", false)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/oplogs"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/index/loginutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
|
||||
@@ -106,7 +107,7 @@ func (this *ParentAction) CreateLog(level string, messageCode langs.MessageCode,
|
||||
}
|
||||
}
|
||||
}
|
||||
err := dao.SharedLogDAO.CreateAdminLog(this.AdminContext(), level, this.Request.URL.Path, desc, this.RequestRemoteIP(), messageCode, args)
|
||||
err := dao.SharedLogDAO.CreateAdminLog(this.AdminContext(), level, this.Request.URL.Path, desc, loginutils.RemoteIP(&this.ActionObject), messageCode, args)
|
||||
if err != nil {
|
||||
utils.PrintError(err)
|
||||
}
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
package recipients
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type CreatePopupAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) RunGet(params struct{}) {
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) RunPost(params struct {
|
||||
AdminId int64
|
||||
InstanceId int64
|
||||
User string
|
||||
|
||||
TelegramToken string
|
||||
|
||||
GroupIds string
|
||||
Description string
|
||||
|
||||
TimeFromHour string
|
||||
TimeFromMinute string
|
||||
TimeFromSecond string
|
||||
|
||||
TimeToHour string
|
||||
TimeToMinute string
|
||||
TimeToSecond string
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
params.Must.
|
||||
Field("adminId", params.AdminId).
|
||||
Gt(0, "请选择系统用户").
|
||||
Field("instanceId", params.InstanceId).
|
||||
Gt(0, "请选择媒介")
|
||||
|
||||
groupIds := utils.SplitNumbers(params.GroupIds)
|
||||
|
||||
var digitReg = regexp.MustCompile(`^\d+$`)
|
||||
|
||||
var timeFrom = ""
|
||||
if digitReg.MatchString(params.TimeFromHour) && digitReg.MatchString(params.TimeFromMinute) && digitReg.MatchString(params.TimeFromSecond) {
|
||||
timeFrom = params.TimeFromHour + ":" + params.TimeFromMinute + ":" + params.TimeFromSecond
|
||||
}
|
||||
|
||||
var timeTo = ""
|
||||
if digitReg.MatchString(params.TimeToHour) && digitReg.MatchString(params.TimeToMinute) && digitReg.MatchString(params.TimeToSecond) {
|
||||
timeTo = params.TimeToHour + ":" + params.TimeToMinute + ":" + params.TimeToSecond
|
||||
}
|
||||
|
||||
resp, err := this.RPC().MessageRecipientRPC().CreateMessageRecipient(this.AdminContext(), &pb.CreateMessageRecipientRequest{
|
||||
AdminId: params.AdminId,
|
||||
MessageMediaInstanceId: params.InstanceId,
|
||||
User: params.User,
|
||||
MessageRecipientGroupIds: groupIds,
|
||||
Description: params.Description,
|
||||
TimeFrom: timeFrom,
|
||||
TimeTo: timeTo,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
defer this.CreateLogInfo(codes.MessageRecipient_LogCreateMessageRecipient, resp.MessageRecipientId)
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package recipients
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
)
|
||||
|
||||
type DeleteAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *DeleteAction) RunPost(params struct {
|
||||
RecipientId int64
|
||||
}) {
|
||||
defer this.CreateLogInfo(codes.MessageRecipient_LogDeleteMessageRecipient, params.RecipientId)
|
||||
|
||||
_, err := this.RPC().MessageRecipientRPC().DeleteMessageRecipient(this.AdminContext(), &pb.DeleteMessageRecipientRequest{MessageRecipientId: params.RecipientId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package groups
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
)
|
||||
|
||||
type CreatePopupAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) RunGet(params struct{}) {
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) RunPost(params struct {
|
||||
Name string
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
params.Must.
|
||||
Field("name", params.Name).
|
||||
Require("请输入分组名称")
|
||||
|
||||
_, err := this.RPC().MessageRecipientGroupRPC().CreateMessageRecipientGroup(this.AdminContext(), &pb.CreateMessageRecipientGroupRequest{Name: params.Name})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package groups
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
)
|
||||
|
||||
type DeleteAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *DeleteAction) RunPost(params struct {
|
||||
GroupId int64
|
||||
}) {
|
||||
_, err := this.RPC().MessageRecipientGroupRPC().DeleteMessageRecipientGroup(this.AdminContext(), &pb.DeleteMessageRecipientGroupRequest{MessageRecipientGroupId: params.GroupId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package groups
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "", "group")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct{}) {
|
||||
groupsResp, err := this.RPC().MessageRecipientGroupRPC().FindAllEnabledMessageRecipientGroups(this.AdminContext(), &pb.FindAllEnabledMessageRecipientGroupsRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
groupMaps := []maps.Map{}
|
||||
for _, group := range groupsResp.MessageRecipientGroups {
|
||||
groupMaps = append(groupMaps, maps.Map{
|
||||
"id": group.Id,
|
||||
"name": group.Name,
|
||||
"isOn": group.IsOn,
|
||||
})
|
||||
}
|
||||
this.Data["groups"] = groupMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package groups
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeAdmin)).
|
||||
Data("teaMenu", "admins").
|
||||
Data("teaSubMenu", "recipients").
|
||||
Prefix("/admins/recipients/groups").
|
||||
Get("", new(IndexAction)).
|
||||
GetPost("/createPopup", new(CreatePopupAction)).
|
||||
GetPost("/updatePopup", new(UpdatePopupAction)).
|
||||
Post("/delete", new(DeleteAction)).
|
||||
Get("/selectPopup", new(SelectPopupAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package groups
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type SelectPopupAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *SelectPopupAction) RunGet(params struct {
|
||||
GroupIds string
|
||||
}) {
|
||||
selectedGroupIds := utils.SplitNumbers(params.GroupIds)
|
||||
|
||||
groupsResp, err := this.RPC().MessageRecipientGroupRPC().FindAllEnabledMessageRecipientGroups(this.AdminContext(), &pb.FindAllEnabledMessageRecipientGroupsRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
groupMaps := []maps.Map{}
|
||||
for _, group := range groupsResp.MessageRecipientGroups {
|
||||
if lists.ContainsInt64(selectedGroupIds, group.Id) {
|
||||
continue
|
||||
}
|
||||
groupMaps = append(groupMaps, maps.Map{
|
||||
"id": group.Id,
|
||||
"name": group.Name,
|
||||
"isOn": group.IsOn,
|
||||
})
|
||||
}
|
||||
this.Data["groups"] = groupMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package groups
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type UpdatePopupAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *UpdatePopupAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *UpdatePopupAction) RunGet(params struct {
|
||||
GroupId int64
|
||||
}) {
|
||||
groupResp, err := this.RPC().MessageRecipientGroupRPC().FindEnabledMessageRecipientGroup(this.AdminContext(), &pb.FindEnabledMessageRecipientGroupRequest{MessageRecipientGroupId: params.GroupId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
group := groupResp.MessageRecipientGroup
|
||||
if group == nil {
|
||||
this.NotFound("messageRecipientGroup", params.GroupId)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["group"] = maps.Map{
|
||||
"id": group.Id,
|
||||
"name": group.Name,
|
||||
"isOn": group.IsOn,
|
||||
}
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *UpdatePopupAction) RunPost(params struct {
|
||||
GroupId int64
|
||||
Name string
|
||||
IsOn bool
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
params.Must.
|
||||
Field("name", params.Name).
|
||||
Require("请输入分组名称")
|
||||
|
||||
_, err := this.RPC().MessageRecipientGroupRPC().UpdateMessageRecipientGroup(this.AdminContext(), &pb.UpdateMessageRecipientGroupRequest{
|
||||
MessageRecipientGroupId: params.GroupId,
|
||||
Name: params.Name,
|
||||
IsOn: params.IsOn,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package recipients
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "", "recipient")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
}) {
|
||||
// TODO 增加系统用户、媒介类型等条件搜索
|
||||
countResp, err := this.RPC().MessageRecipientRPC().CountAllEnabledMessageRecipients(this.AdminContext(), &pb.CountAllEnabledMessageRecipientsRequest{
|
||||
AdminId: 0,
|
||||
MediaType: "",
|
||||
MessageRecipientGroupId: 0,
|
||||
Keyword: "",
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
count := countResp.Count
|
||||
page := this.NewPage(count)
|
||||
this.Data["page"] = page.AsHTML()
|
||||
|
||||
recipientsResp, err := this.RPC().MessageRecipientRPC().ListEnabledMessageRecipients(this.AdminContext(), &pb.ListEnabledMessageRecipientsRequest{
|
||||
AdminId: 0,
|
||||
MediaType: "",
|
||||
MessageRecipientGroupId: 0,
|
||||
Keyword: "",
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
recipientMaps := []maps.Map{}
|
||||
for _, recipient := range recipientsResp.MessageRecipients {
|
||||
if recipient.Admin == nil {
|
||||
continue
|
||||
}
|
||||
if recipient.MessageMediaInstance == nil {
|
||||
continue
|
||||
}
|
||||
recipientMaps = append(recipientMaps, maps.Map{
|
||||
"id": recipient.Id,
|
||||
"admin": maps.Map{
|
||||
"id": recipient.Admin.Id,
|
||||
"fullname": recipient.Admin.Fullname,
|
||||
"username": recipient.Admin.Username,
|
||||
},
|
||||
"groups": recipient.MessageRecipientGroups,
|
||||
"isOn": recipient.IsOn,
|
||||
"instance": maps.Map{
|
||||
"name": recipient.MessageMediaInstance.Name,
|
||||
},
|
||||
"user": recipient.User,
|
||||
"description": recipient.Description,
|
||||
})
|
||||
}
|
||||
this.Data["recipients"] = recipientMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package recipients
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeAdmin)).
|
||||
Data("teaMenu", "admins").
|
||||
Data("teaSubMenu", "recipients").
|
||||
Prefix("/admins/recipients").
|
||||
Get("", new(IndexAction)).
|
||||
GetPost("/createPopup", new(CreatePopupAction)).
|
||||
GetPost("/update", new(UpdateAction)).
|
||||
Post("/delete", new(DeleteAction)).
|
||||
Post("/mediaOptions", new(MediaOptionsAction)).
|
||||
Get("/recipient", new(RecipientAction)).
|
||||
GetPost("/test", new(TestAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
package instances
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/monitorconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type CreatePopupAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) RunGet(params struct{}) {
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *CreatePopupAction) RunPost(params struct {
|
||||
Name string
|
||||
MediaType string
|
||||
|
||||
EmailSmtp string
|
||||
EmailUsername string
|
||||
EmailPassword string
|
||||
EmailFrom string
|
||||
|
||||
WebHookURL string
|
||||
WebHookMethod string
|
||||
WebHookHeaderNames []string
|
||||
WebHookHeaderValues []string
|
||||
WebHookContentType string
|
||||
WebHookParamNames []string
|
||||
WebHookParamValues []string
|
||||
WebHookBody string
|
||||
|
||||
ScriptType string
|
||||
ScriptPath string
|
||||
ScriptLang string
|
||||
ScriptCode string
|
||||
ScriptCwd string
|
||||
ScriptEnvNames []string
|
||||
ScriptEnvValues []string
|
||||
|
||||
DingTalkWebHookURL string
|
||||
|
||||
QyWeixinCorporateId string
|
||||
QyWeixinAgentId string
|
||||
QyWeixinAppSecret string
|
||||
QyWeixinTextFormat string
|
||||
|
||||
QyWeixinRobotWebHookURL string
|
||||
QyWeixinRobotTextFormat string
|
||||
|
||||
AliyunSmsSign string
|
||||
AliyunSmsTemplateCode string
|
||||
AliyunSmsTemplateVarNames []string
|
||||
AliyunSmsTemplateVarValues []string
|
||||
AliyunSmsAccessKeyId string
|
||||
AliyunSmsAccessKeySecret string
|
||||
|
||||
TelegramToken string
|
||||
|
||||
RateMinutes int32
|
||||
RateCount int32
|
||||
|
||||
HashLife int32
|
||||
|
||||
Description string
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
params.Must.
|
||||
Field("name", params.Name).
|
||||
Require("请输入媒介名称").
|
||||
Field("mediaType", params.MediaType).
|
||||
Require("请选择媒介类型")
|
||||
|
||||
options := maps.Map{}
|
||||
|
||||
switch params.MediaType {
|
||||
case "email":
|
||||
params.Must.
|
||||
Field("emailSmtp", params.EmailSmtp).
|
||||
Require("请输入SMTP地址").
|
||||
Field("emailUsername", params.EmailUsername).
|
||||
Require("请输入邮箱账号").
|
||||
Field("emailPassword", params.EmailPassword).
|
||||
Require("请输入密码或授权码")
|
||||
|
||||
options["smtp"] = params.EmailSmtp
|
||||
options["username"] = params.EmailUsername
|
||||
options["password"] = params.EmailPassword
|
||||
options["from"] = params.EmailFrom
|
||||
case "webHook":
|
||||
params.Must.
|
||||
Field("webHookURL", params.WebHookURL).
|
||||
Require("请输入URL地址").
|
||||
Match("(?i)^(http|https)://", "URL地址必须以http或https开头").
|
||||
Field("webHookMethod", params.WebHookMethod).
|
||||
Require("请选择请求方法")
|
||||
|
||||
options["url"] = params.WebHookURL
|
||||
options["method"] = params.WebHookMethod
|
||||
options["contentType"] = params.WebHookContentType
|
||||
|
||||
headers := []maps.Map{}
|
||||
if len(params.WebHookHeaderNames) > 0 {
|
||||
for index, name := range params.WebHookHeaderNames {
|
||||
if index < len(params.WebHookHeaderValues) {
|
||||
headers = append(headers, maps.Map{
|
||||
"name": name,
|
||||
"value": params.WebHookHeaderValues[index],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
options["headers"] = headers
|
||||
|
||||
if params.WebHookContentType == "params" {
|
||||
webHookParams := []maps.Map{}
|
||||
for index, name := range params.WebHookParamNames {
|
||||
if index < len(params.WebHookParamValues) {
|
||||
webHookParams = append(webHookParams, maps.Map{
|
||||
"name": name,
|
||||
"value": params.WebHookParamValues[index],
|
||||
})
|
||||
}
|
||||
}
|
||||
options["params"] = webHookParams
|
||||
} else if params.WebHookContentType == "body" {
|
||||
options["body"] = params.WebHookBody
|
||||
}
|
||||
case "script":
|
||||
if params.ScriptType == "path" {
|
||||
params.Must.
|
||||
Field("scriptPath", params.ScriptPath).
|
||||
Require("请输入脚本路径")
|
||||
} else if params.ScriptType == "code" {
|
||||
params.Must.
|
||||
Field("scriptCode", params.ScriptCode).
|
||||
Require("请输入脚本代码")
|
||||
} else {
|
||||
params.Must.
|
||||
Field("scriptPath", params.ScriptPath).
|
||||
Require("请输入脚本路径")
|
||||
}
|
||||
|
||||
options["scriptType"] = params.ScriptType
|
||||
options["path"] = params.ScriptPath
|
||||
options["scriptLang"] = params.ScriptLang
|
||||
options["script"] = params.ScriptCode
|
||||
options["cwd"] = params.ScriptCwd
|
||||
|
||||
env := []maps.Map{}
|
||||
for index, envName := range params.ScriptEnvNames {
|
||||
if index < len(params.ScriptEnvValues) {
|
||||
env = append(env, maps.Map{
|
||||
"name": envName,
|
||||
"value": params.ScriptEnvValues[index],
|
||||
})
|
||||
}
|
||||
}
|
||||
options["env"] = env
|
||||
case "dingTalk":
|
||||
params.Must.
|
||||
Field("dingTalkWebHookURL", params.DingTalkWebHookURL).
|
||||
Require("请输入Hook地址").
|
||||
Match("^https:", "Hook地址必须以https://开头")
|
||||
|
||||
options["webHookURL"] = params.DingTalkWebHookURL
|
||||
case "qyWeixin":
|
||||
params.Must.
|
||||
Field("qyWeixinCorporateId", params.QyWeixinCorporateId).
|
||||
Require("请输入企业ID").
|
||||
Field("qyWeixinAgentId", params.QyWeixinAgentId).
|
||||
Require("请输入应用AgentId").
|
||||
Field("qyWeixinSecret", params.QyWeixinAppSecret).
|
||||
Require("请输入应用Secret")
|
||||
|
||||
options["corporateId"] = params.QyWeixinCorporateId
|
||||
options["agentId"] = params.QyWeixinAgentId
|
||||
options["appSecret"] = params.QyWeixinAppSecret
|
||||
options["textFormat"] = params.QyWeixinTextFormat
|
||||
case "qyWeixinRobot":
|
||||
params.Must.
|
||||
Field("qyWeixinRobotWebHookURL", params.QyWeixinRobotWebHookURL).
|
||||
Require("请输入WebHook地址").
|
||||
Match("^https:", "WebHook地址必须以https://开头")
|
||||
|
||||
options["webHookURL"] = params.QyWeixinRobotWebHookURL
|
||||
options["textFormat"] = params.QyWeixinRobotTextFormat
|
||||
case "aliyunSms":
|
||||
params.Must.
|
||||
Field("aliyunSmsSign", params.AliyunSmsSign).
|
||||
Require("请输入签名名称").
|
||||
Field("aliyunSmsTemplateCode", params.AliyunSmsTemplateCode).
|
||||
Require("请输入模板CODE").
|
||||
Field("aliyunSmsAccessKeyId", params.AliyunSmsAccessKeyId).
|
||||
Require("请输入AccessKey ID").
|
||||
Field("aliyunSmsAccessKeySecret", params.AliyunSmsAccessKeySecret).
|
||||
Require("请输入AccessKey Secret")
|
||||
|
||||
options["sign"] = params.AliyunSmsSign
|
||||
options["templateCode"] = params.AliyunSmsTemplateCode
|
||||
options["accessKeyId"] = params.AliyunSmsAccessKeyId
|
||||
options["accessKeySecret"] = params.AliyunSmsAccessKeySecret
|
||||
|
||||
variables := []maps.Map{}
|
||||
for index, name := range params.AliyunSmsTemplateVarNames {
|
||||
if index < len(params.AliyunSmsTemplateVarValues) {
|
||||
variables = append(variables, maps.Map{
|
||||
"name": name,
|
||||
"value": params.AliyunSmsTemplateVarValues[index],
|
||||
})
|
||||
}
|
||||
}
|
||||
options["variables"] = variables
|
||||
case "telegram":
|
||||
params.Must.
|
||||
Field("telegramToken", params.TelegramToken).
|
||||
Require("请输入机器人Token")
|
||||
options["token"] = params.TelegramToken
|
||||
default:
|
||||
this.Fail("错误的媒介类型")
|
||||
}
|
||||
|
||||
optionsJSON, err := json.Marshal(options)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
var rateConfig = &monitorconfigs.RateConfig{
|
||||
Minutes: params.RateMinutes,
|
||||
Count: params.RateCount,
|
||||
}
|
||||
rateJSON, err := json.Marshal(rateConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := this.RPC().MessageMediaInstanceRPC().CreateMessageMediaInstance(this.AdminContext(), &pb.CreateMessageMediaInstanceRequest{
|
||||
Name: params.Name,
|
||||
MediaType: params.MediaType,
|
||||
ParamsJSON: optionsJSON,
|
||||
Description: params.Description,
|
||||
RateJSON: rateJSON,
|
||||
HashLife: params.HashLife,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
defer this.CreateLogInfo(codes.MessageMediaInstance_LogCreateMessageMediaInstance, resp.MessageMediaInstanceId)
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package instances
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
)
|
||||
|
||||
type DeleteAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *DeleteAction) RunPost(params struct {
|
||||
InstanceId int64
|
||||
}) {
|
||||
defer this.CreateLogInfo(codes.MessageMediaInstance_LogDeleteMessageMediaInstance, params.InstanceId)
|
||||
|
||||
_, err := this.RPC().MessageMediaInstanceRPC().DeleteMessageMediaInstance(this.AdminContext(), &pb.DeleteMessageMediaInstanceRequest{MessageMediaInstanceId: params.InstanceId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package instances
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "", "instance")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
}) {
|
||||
// TODO 增加系统用户、媒介类型等条件搜索
|
||||
countResp, err := this.RPC().MessageMediaInstanceRPC().CountAllEnabledMessageMediaInstances(this.AdminContext(), &pb.CountAllEnabledMessageMediaInstancesRequest{
|
||||
MediaType: "",
|
||||
Keyword: "",
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
count := countResp.Count
|
||||
page := this.NewPage(count)
|
||||
this.Data["page"] = page.AsHTML()
|
||||
|
||||
instancesResp, err := this.RPC().MessageMediaInstanceRPC().ListEnabledMessageMediaInstances(this.AdminContext(), &pb.ListEnabledMessageMediaInstancesRequest{
|
||||
MediaType: "",
|
||||
Keyword: "",
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
instanceMaps := []maps.Map{}
|
||||
for _, instance := range instancesResp.MessageMediaInstances {
|
||||
if instance.MessageMedia == nil {
|
||||
continue
|
||||
}
|
||||
instanceMaps = append(instanceMaps, maps.Map{
|
||||
"id": instance.Id,
|
||||
"name": instance.Name,
|
||||
"isOn": instance.IsOn,
|
||||
"media": maps.Map{
|
||||
"name": instance.MessageMedia.Name,
|
||||
},
|
||||
"description": instance.Description,
|
||||
})
|
||||
}
|
||||
this.Data["instances"] = instanceMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package instances
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeAdmin)).
|
||||
Data("teaMenu", "admins").
|
||||
Data("teaSubMenu", "instances").
|
||||
Prefix("/admins/recipients/instances").
|
||||
Get("", new(IndexAction)).
|
||||
GetPost("/createPopup", new(CreatePopupAction)).
|
||||
GetPost("/update", new(UpdateAction)).
|
||||
Post("/delete", new(DeleteAction)).
|
||||
Get("/instance", new(InstanceAction)).
|
||||
GetPost("/test", new(TestAction)).
|
||||
Post("/options", new(OptionsAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package instances
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/monitorconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type InstanceAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *InstanceAction) Init() {
|
||||
this.Nav("", "", "instance")
|
||||
}
|
||||
|
||||
func (this *InstanceAction) RunGet(params struct {
|
||||
InstanceId int64
|
||||
}) {
|
||||
instanceResp, err := this.RPC().MessageMediaInstanceRPC().FindEnabledMessageMediaInstance(this.AdminContext(), &pb.FindEnabledMessageMediaInstanceRequest{MessageMediaInstanceId: params.InstanceId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
instance := instanceResp.MessageMediaInstance
|
||||
if instance == nil || instance.MessageMedia == nil {
|
||||
this.NotFound("messageMediaInstance", params.InstanceId)
|
||||
return
|
||||
}
|
||||
|
||||
mediaParams := maps.Map{}
|
||||
if len(instance.ParamsJSON) > 0 {
|
||||
err = json.Unmarshal(instance.ParamsJSON, &mediaParams)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 频率
|
||||
var rateConfig = &monitorconfigs.RateConfig{}
|
||||
if len(instance.RateJSON) > 0 {
|
||||
err = json.Unmarshal(instance.RateJSON, rateConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.Data["instance"] = maps.Map{
|
||||
"id": instance.Id,
|
||||
"name": instance.Name,
|
||||
"isOn": instance.IsOn,
|
||||
"media": maps.Map{
|
||||
"type": instance.MessageMedia.Type,
|
||||
"name": instance.MessageMedia.Name,
|
||||
},
|
||||
"description": instance.Description,
|
||||
"params": mediaParams,
|
||||
"rate": rateConfig,
|
||||
"hashLife": instance.HashLife,
|
||||
}
|
||||
|
||||
this.Show()
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package instances
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
// 媒介类型选项
|
||||
type OptionsAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *OptionsAction) RunPost(params struct{}) {
|
||||
resp, err := this.RPC().MessageMediaInstanceRPC().ListEnabledMessageMediaInstances(this.AdminContext(), &pb.ListEnabledMessageMediaInstancesRequest{
|
||||
Offset: 0,
|
||||
Size: 1000,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
instanceMaps := []maps.Map{}
|
||||
for _, instance := range resp.MessageMediaInstances {
|
||||
instanceMaps = append(instanceMaps, maps.Map{
|
||||
"id": instance.Id,
|
||||
"name": instance.Name,
|
||||
"description": instance.Description,
|
||||
"media": maps.Map{
|
||||
"type": instance.MessageMedia.Type,
|
||||
"name": instance.MessageMedia.Name,
|
||||
"userDescription": instance.MessageMedia.UserDescription,
|
||||
},
|
||||
})
|
||||
}
|
||||
this.Data["instances"] = instanceMaps
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package instances
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type TestAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *TestAction) Init() {
|
||||
this.Nav("", "", "test")
|
||||
}
|
||||
|
||||
func (this *TestAction) RunGet(params struct {
|
||||
InstanceId int64
|
||||
}) {
|
||||
instanceResp, err := this.RPC().MessageMediaInstanceRPC().FindEnabledMessageMediaInstance(this.AdminContext(), &pb.FindEnabledMessageMediaInstanceRequest{MessageMediaInstanceId: params.InstanceId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
instance := instanceResp.MessageMediaInstance
|
||||
if instance == nil || instance.MessageMedia == nil {
|
||||
this.NotFound("messageMediaInstance", params.InstanceId)
|
||||
return
|
||||
}
|
||||
|
||||
mediaParams := maps.Map{}
|
||||
if len(instance.ParamsJSON) > 0 {
|
||||
err = json.Unmarshal(instance.ParamsJSON, &mediaParams)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.Data["instance"] = maps.Map{
|
||||
"id": instance.Id,
|
||||
"isOn": instance.IsOn,
|
||||
"media": maps.Map{
|
||||
"type": instance.MessageMedia.Type,
|
||||
"name": instance.MessageMedia.Name,
|
||||
"userDescription": instance.MessageMedia.UserDescription,
|
||||
},
|
||||
"description": instance.Description,
|
||||
"params": mediaParams,
|
||||
}
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *TestAction) RunPost(params struct {
|
||||
InstanceId int64
|
||||
Subject string
|
||||
Body string
|
||||
User string
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
params.Must.
|
||||
Field("instanceId", params.InstanceId).
|
||||
Gt(0, "请选择正确的媒介")
|
||||
|
||||
resp, err := this.RPC().MessageTaskRPC().CreateMessageTask(this.AdminContext(), &pb.CreateMessageTaskRequest{
|
||||
RecipientId: 0,
|
||||
InstanceId: params.InstanceId,
|
||||
User: params.User,
|
||||
Subject: params.Subject,
|
||||
Body: params.Body,
|
||||
IsPrimary: true,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["taskId"] = resp.MessageTaskId
|
||||
|
||||
defer this.CreateLogInfo(codes.MessageTask_LogCreateTestingMessageTask, resp.MessageTaskId)
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -1,317 +0,0 @@
|
||||
package instances
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/monitorconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type UpdateAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *UpdateAction) Init() {
|
||||
this.Nav("", "", "update")
|
||||
}
|
||||
|
||||
func (this *UpdateAction) RunGet(params struct {
|
||||
InstanceId int64
|
||||
}) {
|
||||
instanceResp, err := this.RPC().MessageMediaInstanceRPC().FindEnabledMessageMediaInstance(this.AdminContext(), &pb.FindEnabledMessageMediaInstanceRequest{MessageMediaInstanceId: params.InstanceId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
instance := instanceResp.MessageMediaInstance
|
||||
if instance == nil || instance.MessageMedia == nil {
|
||||
this.NotFound("messageMediaInstance", params.InstanceId)
|
||||
return
|
||||
}
|
||||
|
||||
mediaParams := maps.Map{}
|
||||
if len(instance.ParamsJSON) > 0 {
|
||||
err = json.Unmarshal(instance.ParamsJSON, &mediaParams)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var rateConfig = &monitorconfigs.RateConfig{}
|
||||
if len(instance.RateJSON) > 0 {
|
||||
err = json.Unmarshal(instance.RateJSON, rateConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.Data["instance"] = maps.Map{
|
||||
"id": instance.Id,
|
||||
"name": instance.Name,
|
||||
"isOn": instance.IsOn,
|
||||
"media": maps.Map{
|
||||
"type": instance.MessageMedia.Type,
|
||||
"name": instance.MessageMedia.Name,
|
||||
},
|
||||
"description": instance.Description,
|
||||
"params": mediaParams,
|
||||
"rate": rateConfig,
|
||||
"hashLife": instance.HashLife,
|
||||
}
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *UpdateAction) RunPost(params struct {
|
||||
InstanceId int64
|
||||
Name string
|
||||
MediaType string
|
||||
|
||||
EmailSmtp string
|
||||
EmailUsername string
|
||||
EmailPassword string
|
||||
EmailFrom string
|
||||
|
||||
WebHookURL string
|
||||
WebHookMethod string
|
||||
WebHookHeaderNames []string
|
||||
WebHookHeaderValues []string
|
||||
WebHookContentType string
|
||||
WebHookParamNames []string
|
||||
WebHookParamValues []string
|
||||
WebHookBody string
|
||||
|
||||
ScriptType string
|
||||
ScriptPath string
|
||||
ScriptLang string
|
||||
ScriptCode string
|
||||
ScriptCwd string
|
||||
ScriptEnvNames []string
|
||||
ScriptEnvValues []string
|
||||
|
||||
DingTalkWebHookURL string
|
||||
|
||||
QyWeixinCorporateId string
|
||||
QyWeixinAgentId string
|
||||
QyWeixinAppSecret string
|
||||
QyWeixinTextFormat string
|
||||
|
||||
QyWeixinRobotWebHookURL string
|
||||
QyWeixinRobotTextFormat string
|
||||
|
||||
AliyunSmsSign string
|
||||
AliyunSmsTemplateCode string
|
||||
AliyunSmsTemplateVarNames []string
|
||||
AliyunSmsTemplateVarValues []string
|
||||
AliyunSmsAccessKeyId string
|
||||
AliyunSmsAccessKeySecret string
|
||||
|
||||
TelegramToken string
|
||||
|
||||
GroupIds string
|
||||
Description string
|
||||
IsOn bool
|
||||
|
||||
RateMinutes int32
|
||||
RateCount int32
|
||||
|
||||
HashLife int32
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
defer this.CreateLogInfo(codes.MessageMediaInstance_LogUpdateMessageMediaInstance, params.InstanceId)
|
||||
|
||||
params.Must.
|
||||
Field("name", params.Name).
|
||||
Require("请输入媒介名称").
|
||||
Field("mediaType", params.MediaType).
|
||||
Require("请选择媒介类型")
|
||||
|
||||
options := maps.Map{}
|
||||
|
||||
switch params.MediaType {
|
||||
case "email":
|
||||
params.Must.
|
||||
Field("emailSmtp", params.EmailSmtp).
|
||||
Require("请输入SMTP地址").
|
||||
Field("emailUsername", params.EmailUsername).
|
||||
Require("请输入邮箱账号").
|
||||
Field("emailPassword", params.EmailPassword).
|
||||
Require("请输入密码或授权码")
|
||||
|
||||
options["smtp"] = params.EmailSmtp
|
||||
options["username"] = params.EmailUsername
|
||||
options["password"] = params.EmailPassword
|
||||
options["from"] = params.EmailFrom
|
||||
case "webHook":
|
||||
params.Must.
|
||||
Field("webHookURL", params.WebHookURL).
|
||||
Require("请输入URL地址").
|
||||
Match("(?i)^(http|https)://", "URL地址必须以http或https开头").
|
||||
Field("webHookMethod", params.WebHookMethod).
|
||||
Require("请选择请求方法")
|
||||
|
||||
options["url"] = params.WebHookURL
|
||||
options["method"] = params.WebHookMethod
|
||||
options["contentType"] = params.WebHookContentType
|
||||
|
||||
headers := []maps.Map{}
|
||||
if len(params.WebHookHeaderNames) > 0 {
|
||||
for index, name := range params.WebHookHeaderNames {
|
||||
if index < len(params.WebHookHeaderValues) {
|
||||
headers = append(headers, maps.Map{
|
||||
"name": name,
|
||||
"value": params.WebHookHeaderValues[index],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
options["headers"] = headers
|
||||
|
||||
if params.WebHookContentType == "params" {
|
||||
webHookParams := []maps.Map{}
|
||||
for index, name := range params.WebHookParamNames {
|
||||
if index < len(params.WebHookParamValues) {
|
||||
webHookParams = append(webHookParams, maps.Map{
|
||||
"name": name,
|
||||
"value": params.WebHookParamValues[index],
|
||||
})
|
||||
}
|
||||
}
|
||||
options["params"] = webHookParams
|
||||
} else if params.WebHookContentType == "body" {
|
||||
options["body"] = params.WebHookBody
|
||||
}
|
||||
case "script":
|
||||
if params.ScriptType == "path" {
|
||||
params.Must.
|
||||
Field("scriptPath", params.ScriptPath).
|
||||
Require("请输入脚本路径")
|
||||
} else if params.ScriptType == "code" {
|
||||
params.Must.
|
||||
Field("scriptCode", params.ScriptCode).
|
||||
Require("请输入脚本代码")
|
||||
} else {
|
||||
params.Must.
|
||||
Field("scriptPath", params.ScriptPath).
|
||||
Require("请输入脚本路径")
|
||||
}
|
||||
|
||||
options["scriptType"] = params.ScriptType
|
||||
options["path"] = params.ScriptPath
|
||||
options["scriptLang"] = params.ScriptLang
|
||||
options["script"] = params.ScriptCode
|
||||
options["cwd"] = params.ScriptCwd
|
||||
|
||||
env := []maps.Map{}
|
||||
for index, envName := range params.ScriptEnvNames {
|
||||
if index < len(params.ScriptEnvValues) {
|
||||
env = append(env, maps.Map{
|
||||
"name": envName,
|
||||
"value": params.ScriptEnvValues[index],
|
||||
})
|
||||
}
|
||||
}
|
||||
options["env"] = env
|
||||
case "dingTalk":
|
||||
params.Must.
|
||||
Field("dingTalkWebHookURL", params.DingTalkWebHookURL).
|
||||
Require("请输入Hook地址").
|
||||
Match("^https:", "Hook地址必须以https://开头")
|
||||
|
||||
options["webHookURL"] = params.DingTalkWebHookURL
|
||||
case "qyWeixin":
|
||||
params.Must.
|
||||
Field("qyWeixinCorporateId", params.QyWeixinCorporateId).
|
||||
Require("请输入企业ID").
|
||||
Field("qyWeixinAgentId", params.QyWeixinAgentId).
|
||||
Require("请输入应用AgentId").
|
||||
Field("qyWeixinSecret", params.QyWeixinAppSecret).
|
||||
Require("请输入应用Secret")
|
||||
|
||||
options["corporateId"] = params.QyWeixinCorporateId
|
||||
options["agentId"] = params.QyWeixinAgentId
|
||||
options["appSecret"] = params.QyWeixinAppSecret
|
||||
options["textFormat"] = params.QyWeixinTextFormat
|
||||
case "qyWeixinRobot":
|
||||
params.Must.
|
||||
Field("qyWeixinRobotWebHookURL", params.QyWeixinRobotWebHookURL).
|
||||
Require("请输入WebHook地址").
|
||||
Match("^https:", "WebHook地址必须以https://开头")
|
||||
|
||||
options["webHookURL"] = params.QyWeixinRobotWebHookURL
|
||||
options["textFormat"] = params.QyWeixinRobotTextFormat
|
||||
case "aliyunSms":
|
||||
params.Must.
|
||||
Field("aliyunSmsSign", params.AliyunSmsSign).
|
||||
Require("请输入签名名称").
|
||||
Field("aliyunSmsTemplateCode", params.AliyunSmsTemplateCode).
|
||||
Require("请输入模板CODE").
|
||||
Field("aliyunSmsAccessKeyId", params.AliyunSmsAccessKeyId).
|
||||
Require("请输入AccessKey ID").
|
||||
Field("aliyunSmsAccessKeySecret", params.AliyunSmsAccessKeySecret).
|
||||
Require("请输入AccessKey Secret")
|
||||
|
||||
options["sign"] = params.AliyunSmsSign
|
||||
options["templateCode"] = params.AliyunSmsTemplateCode
|
||||
options["accessKeyId"] = params.AliyunSmsAccessKeyId
|
||||
options["accessKeySecret"] = params.AliyunSmsAccessKeySecret
|
||||
|
||||
variables := []maps.Map{}
|
||||
for index, name := range params.AliyunSmsTemplateVarNames {
|
||||
if index < len(params.AliyunSmsTemplateVarValues) {
|
||||
variables = append(variables, maps.Map{
|
||||
"name": name,
|
||||
"value": params.AliyunSmsTemplateVarValues[index],
|
||||
})
|
||||
}
|
||||
}
|
||||
options["variables"] = variables
|
||||
case "telegram":
|
||||
params.Must.
|
||||
Field("telegramToken", params.TelegramToken).
|
||||
Require("请输入机器人Token")
|
||||
options["token"] = params.TelegramToken
|
||||
default:
|
||||
this.Fail("错误的媒介类型")
|
||||
}
|
||||
|
||||
optionsJSON, err := json.Marshal(options)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
var rateConfig = &monitorconfigs.RateConfig{
|
||||
Minutes: params.RateMinutes,
|
||||
Count: params.RateCount,
|
||||
}
|
||||
rateJSON, err := json.Marshal(rateConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = this.RPC().MessageMediaInstanceRPC().UpdateMessageMediaInstance(this.AdminContext(), &pb.UpdateMessageMediaInstanceRequest{
|
||||
MessageMediaInstanceId: params.InstanceId,
|
||||
Name: params.Name,
|
||||
MediaType: params.MediaType,
|
||||
ParamsJSON: optionsJSON,
|
||||
Description: params.Description,
|
||||
RateJSON: rateJSON,
|
||||
HashLife: params.HashLife,
|
||||
IsOn: params.IsOn,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "", "log")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct{}) {
|
||||
countResp, err := this.RPC().MessageTaskLogRPC().CountMessageTaskLogs(this.AdminContext(), &pb.CountMessageTaskLogsRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
page := this.NewPage(countResp.Count)
|
||||
this.Data["page"] = page.AsHTML()
|
||||
|
||||
logsResp, err := this.RPC().MessageTaskLogRPC().ListMessageTaskLogs(this.AdminContext(), &pb.ListMessageTaskLogsRequest{
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
logMaps := []maps.Map{}
|
||||
for _, log := range logsResp.MessageTaskLogs {
|
||||
if log.MessageTask.MessageRecipient != nil {
|
||||
log.MessageTask.User = log.MessageTask.MessageRecipient.User
|
||||
}
|
||||
logMaps = append(logMaps, maps.Map{
|
||||
"task": maps.Map{
|
||||
"id": log.MessageTask.Id,
|
||||
"user": log.MessageTask.User,
|
||||
"subject": log.MessageTask.Subject,
|
||||
"body": log.MessageTask.Body,
|
||||
"instance": maps.Map{
|
||||
"id": log.MessageTask.MessageMediaInstance.Id,
|
||||
"name": log.MessageTask.MessageMediaInstance.Name,
|
||||
},
|
||||
},
|
||||
"isOk": log.IsOk,
|
||||
"error": log.Error,
|
||||
"response": log.Response,
|
||||
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", log.CreatedAt),
|
||||
})
|
||||
}
|
||||
this.Data["logs"] = logMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package logs
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeAdmin)).
|
||||
Data("teaMenu", "admins").
|
||||
Data("teaSubMenu", "recipients").
|
||||
Prefix("/admins/recipients/logs").
|
||||
Get("", new(IndexAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package recipients
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
// 媒介类型选项
|
||||
type MediaOptionsAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *MediaOptionsAction) RunPost(params struct{}) {
|
||||
resp, err := this.RPC().MessageMediaRPC().FindAllMessageMedias(this.AdminContext(), &pb.FindAllMessageMediasRequest{})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
mediaMaps := []maps.Map{}
|
||||
for _, media := range resp.MessageMedias {
|
||||
mediaMaps = append(mediaMaps, maps.Map{
|
||||
"id": media.Id,
|
||||
"type": media.Type,
|
||||
"name": media.Name,
|
||||
"description": media.Description,
|
||||
})
|
||||
}
|
||||
this.Data["medias"] = mediaMaps
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package recipients
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type RecipientAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *RecipientAction) Init() {
|
||||
this.Nav("", "", "recipient")
|
||||
}
|
||||
|
||||
func (this *RecipientAction) RunGet(params struct {
|
||||
RecipientId int64
|
||||
}) {
|
||||
recipientResp, err := this.RPC().MessageRecipientRPC().FindEnabledMessageRecipient(this.AdminContext(), &pb.FindEnabledMessageRecipientRequest{MessageRecipientId: params.RecipientId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
recipient := recipientResp.MessageRecipient
|
||||
if recipient == nil || recipient.Admin == nil || recipient.MessageMediaInstance == nil {
|
||||
this.NotFound("messageRecipient", params.RecipientId)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["recipient"] = maps.Map{
|
||||
"id": recipient.Id,
|
||||
"admin": maps.Map{
|
||||
"id": recipient.Admin.Id,
|
||||
"fullname": recipient.Admin.Fullname,
|
||||
"username": recipient.Admin.Username,
|
||||
},
|
||||
"groups": recipient.MessageRecipientGroups,
|
||||
"isOn": recipient.IsOn,
|
||||
"instance": maps.Map{
|
||||
"id": recipient.MessageMediaInstance.Id,
|
||||
"name": recipient.MessageMediaInstance.Name,
|
||||
"description": recipient.MessageMediaInstance.Description,
|
||||
},
|
||||
"user": recipient.User,
|
||||
"description": recipient.Description,
|
||||
"timeFrom": recipient.TimeFrom,
|
||||
"timeTo": recipient.TimeTo,
|
||||
}
|
||||
|
||||
this.Show()
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
)
|
||||
|
||||
type DeleteAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *DeleteAction) RunPost(params struct {
|
||||
TaskId int64
|
||||
}) {
|
||||
defer this.CreateLogInfo(codes.MessageTask_LogDeleteMessageTask, params.TaskId)
|
||||
|
||||
_, err := this.RPC().MessageTaskRPC().DeleteMessageTask(this.AdminContext(), &pb.DeleteMessageTaskRequest{MessageTaskId: params.TaskId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "", "task")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
Status int32
|
||||
}) {
|
||||
this.Data["status"] = params.Status
|
||||
if params.Status > 3 {
|
||||
params.Status = 0
|
||||
}
|
||||
|
||||
countWaitingResp, err := this.RPC().MessageTaskRPC().CountMessageTasksWithStatus(this.AdminContext(), &pb.CountMessageTasksWithStatusRequest{Status: pb.CountMessageTasksWithStatusRequest_MessageTaskStatusNone})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var countWaiting = countWaitingResp.Count
|
||||
this.Data["countWaiting"] = countWaiting
|
||||
|
||||
countFailedResp, err := this.RPC().MessageTaskRPC().CountMessageTasksWithStatus(this.AdminContext(), &pb.CountMessageTasksWithStatusRequest{Status: pb.CountMessageTasksWithStatusRequest_MessageTaskStatusFailed})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var countFailed = countFailedResp.Count
|
||||
this.Data["countFailed"] = countFailed
|
||||
|
||||
// 列表
|
||||
var total = int64(0)
|
||||
switch params.Status {
|
||||
case 0:
|
||||
total = countWaiting
|
||||
case 3:
|
||||
total = countFailed
|
||||
}
|
||||
page := this.NewPage(total)
|
||||
this.Data["page"] = page.AsHTML()
|
||||
|
||||
var taskMaps = []maps.Map{}
|
||||
tasksResp, err := this.RPC().MessageTaskRPC().ListMessageTasksWithStatus(this.AdminContext(), &pb.ListMessageTasksWithStatusRequest{
|
||||
Status: pb.ListMessageTasksWithStatusRequest_Status(params.Status),
|
||||
Offset: page.Offset,
|
||||
Size: page.Size,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
for _, task := range tasksResp.MessageTasks {
|
||||
var resultMap = maps.Map{}
|
||||
var result = task.Result
|
||||
if result != nil {
|
||||
resultMap = maps.Map{
|
||||
"isOk": result.IsOk,
|
||||
"error": result.Error,
|
||||
"response": result.Response,
|
||||
}
|
||||
}
|
||||
|
||||
//var recipients = []string{}
|
||||
var user = ""
|
||||
var instanceMap maps.Map
|
||||
if task.MessageRecipient != nil {
|
||||
user = task.MessageRecipient.User
|
||||
if task.MessageRecipient.MessageMediaInstance != nil {
|
||||
instanceMap = maps.Map{
|
||||
"id": task.MessageRecipient.MessageMediaInstance.Id,
|
||||
"name": task.MessageRecipient.MessageMediaInstance.Name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
taskMaps = append(taskMaps, maps.Map{
|
||||
"id": task.Id,
|
||||
"subject": task.Subject,
|
||||
"body": task.Body,
|
||||
"createdTime": timeutil.FormatTime("Y-m-d H:i:s", task.CreatedAt),
|
||||
"result": resultMap,
|
||||
"status": task.Status,
|
||||
"user": user,
|
||||
"instance": instanceMap,
|
||||
})
|
||||
}
|
||||
this.Data["tasks"] = taskMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configloaders"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/helpers"
|
||||
"github.com/iwind/TeaGo"
|
||||
)
|
||||
|
||||
func init() {
|
||||
TeaGo.BeforeStart(func(server *TeaGo.Server) {
|
||||
server.
|
||||
Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeAdmin)).
|
||||
Data("teaMenu", "admins").
|
||||
Data("teaSubMenu", "recipients").
|
||||
Prefix("/admins/recipients/tasks").
|
||||
Get("", new(IndexAction)).
|
||||
Post("/taskInfo", new(TaskInfoAction)).
|
||||
Post("/delete", new(DeleteAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type TaskInfoAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *TaskInfoAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *TaskInfoAction) RunPost(params struct {
|
||||
TaskId int64
|
||||
}) {
|
||||
resp, err := this.RPC().MessageTaskRPC().FindEnabledMessageTask(this.AdminContext(), &pb.FindEnabledMessageTaskRequest{MessageTaskId: params.TaskId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
if resp.MessageTask == nil {
|
||||
this.NotFound("messageTask", params.TaskId)
|
||||
return
|
||||
}
|
||||
|
||||
result := resp.MessageTask.Result
|
||||
this.Data["status"] = resp.MessageTask.Status
|
||||
this.Data["result"] = maps.Map{
|
||||
"isOk": result.IsOk,
|
||||
"response": result.Response,
|
||||
"error": result.Error,
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
package recipients
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type TestAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *TestAction) Init() {
|
||||
this.Nav("", "", "test")
|
||||
}
|
||||
|
||||
func (this *TestAction) RunGet(params struct {
|
||||
RecipientId int64
|
||||
}) {
|
||||
recipientResp, err := this.RPC().MessageRecipientRPC().FindEnabledMessageRecipient(this.AdminContext(), &pb.FindEnabledMessageRecipientRequest{MessageRecipientId: params.RecipientId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
recipient := recipientResp.MessageRecipient
|
||||
if recipient == nil || recipient.Admin == nil || recipient.MessageMediaInstance == nil {
|
||||
this.NotFound("messageRecipient", params.RecipientId)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["recipient"] = maps.Map{
|
||||
"id": recipient.Id,
|
||||
"admin": maps.Map{
|
||||
"id": recipient.Admin.Id,
|
||||
"fullname": recipient.Admin.Fullname,
|
||||
"username": recipient.Admin.Username,
|
||||
},
|
||||
"instance": maps.Map{
|
||||
"id": recipient.MessageMediaInstance.Id,
|
||||
"name": recipient.MessageMediaInstance.Name,
|
||||
"description": recipient.MessageMediaInstance.Description,
|
||||
},
|
||||
"user": recipient.User,
|
||||
}
|
||||
|
||||
instanceResp, err := this.RPC().MessageMediaInstanceRPC().FindEnabledMessageMediaInstance(this.AdminContext(), &pb.FindEnabledMessageMediaInstanceRequest{MessageMediaInstanceId: recipient.MessageMediaInstance.Id})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
instance := instanceResp.MessageMediaInstance
|
||||
if instance == nil || instance.MessageMedia == nil {
|
||||
this.NotFound("messageMediaInstance", recipient.MessageMediaInstance.Id)
|
||||
return
|
||||
}
|
||||
|
||||
mediaParams := maps.Map{}
|
||||
if len(instance.ParamsJSON) > 0 {
|
||||
err = json.Unmarshal(instance.ParamsJSON, &mediaParams)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.Data["instance"] = maps.Map{
|
||||
"id": instance.Id,
|
||||
"isOn": instance.IsOn,
|
||||
"media": maps.Map{
|
||||
"type": instance.MessageMedia.Type,
|
||||
"name": instance.MessageMedia.Name,
|
||||
"userDescription": instance.MessageMedia.UserDescription,
|
||||
},
|
||||
"description": instance.Description,
|
||||
"params": mediaParams,
|
||||
}
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *TestAction) RunPost(params struct {
|
||||
InstanceId int64
|
||||
Subject string
|
||||
Body string
|
||||
User string
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
params.Must.
|
||||
Field("instanceId", params.InstanceId).
|
||||
Gt(0, "请选择正确的媒介")
|
||||
|
||||
resp, err := this.RPC().MessageTaskRPC().CreateMessageTask(this.AdminContext(), &pb.CreateMessageTaskRequest{
|
||||
RecipientId: 0,
|
||||
InstanceId: params.InstanceId,
|
||||
User: params.User,
|
||||
Subject: params.Subject,
|
||||
Body: params.Body,
|
||||
IsPrimary: true,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["taskId"] = resp.MessageTaskId
|
||||
|
||||
defer this.CreateLogInfo(codes.MessageTask_LogCreateTestingMessageTask, resp.MessageTaskId)
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
package recipients
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type UpdateAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *UpdateAction) Init() {
|
||||
this.Nav("", "", "update")
|
||||
}
|
||||
|
||||
func (this *UpdateAction) RunGet(params struct {
|
||||
RecipientId int64
|
||||
}) {
|
||||
recipientResp, err := this.RPC().MessageRecipientRPC().FindEnabledMessageRecipient(this.AdminContext(), &pb.FindEnabledMessageRecipientRequest{MessageRecipientId: params.RecipientId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
recipient := recipientResp.MessageRecipient
|
||||
if recipient == nil || recipient.Admin == nil || recipient.MessageMediaInstance == nil {
|
||||
this.NotFound("messageRecipient", params.RecipientId)
|
||||
return
|
||||
}
|
||||
|
||||
var timeFromHour = ""
|
||||
var timeFromMinute = ""
|
||||
var timeFromSecond = ""
|
||||
|
||||
if len(recipient.TimeFrom) > 0 {
|
||||
var pieces = strings.Split(recipient.TimeFrom, ":")
|
||||
timeFromHour = pieces[0]
|
||||
timeFromMinute = pieces[1]
|
||||
timeFromSecond = pieces[2]
|
||||
}
|
||||
|
||||
var timeToHour = ""
|
||||
var timeToMinute = ""
|
||||
var timeToSecond = ""
|
||||
if len(recipient.TimeTo) > 0 {
|
||||
var pieces = strings.Split(recipient.TimeTo, ":")
|
||||
timeToHour = pieces[0]
|
||||
timeToMinute = pieces[1]
|
||||
timeToSecond = pieces[2]
|
||||
}
|
||||
|
||||
this.Data["recipient"] = maps.Map{
|
||||
"id": recipient.Id,
|
||||
"admin": maps.Map{
|
||||
"id": recipient.Admin.Id,
|
||||
"fullname": recipient.Admin.Fullname,
|
||||
"username": recipient.Admin.Username,
|
||||
},
|
||||
"groups": recipient.MessageRecipientGroups,
|
||||
"isOn": recipient.IsOn,
|
||||
"instance": maps.Map{
|
||||
"id": recipient.MessageMediaInstance.Id,
|
||||
"name": recipient.MessageMediaInstance.Name,
|
||||
},
|
||||
"user": recipient.User,
|
||||
"description": recipient.Description,
|
||||
"timeFromHour": timeFromHour,
|
||||
"timeFromMinute": timeFromMinute,
|
||||
"timeFromSecond": timeFromSecond,
|
||||
"timeToHour": timeToHour,
|
||||
"timeToMinute": timeToMinute,
|
||||
"timeToSecond": timeToSecond,
|
||||
}
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *UpdateAction) RunPost(params struct {
|
||||
RecipientId int64
|
||||
|
||||
AdminId int64
|
||||
User string
|
||||
InstanceId int64
|
||||
|
||||
GroupIds string
|
||||
Description string
|
||||
IsOn bool
|
||||
|
||||
TimeFromHour string
|
||||
TimeFromMinute string
|
||||
TimeFromSecond string
|
||||
|
||||
TimeToHour string
|
||||
TimeToMinute string
|
||||
TimeToSecond string
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
defer this.CreateLogInfo(codes.MessageRecipient_LogUpdateMessageRecipient, params.RecipientId)
|
||||
|
||||
params.Must.
|
||||
Field("adminId", params.AdminId).
|
||||
Gt(0, "请选择系统用户").
|
||||
Field("instanceId", params.InstanceId).
|
||||
Gt(0, "请选择媒介")
|
||||
|
||||
groupIds := utils.SplitNumbers(params.GroupIds)
|
||||
|
||||
var digitReg = regexp.MustCompile(`^\d+$`)
|
||||
|
||||
var timeFrom = ""
|
||||
if digitReg.MatchString(params.TimeFromHour) && digitReg.MatchString(params.TimeFromMinute) && digitReg.MatchString(params.TimeFromSecond) {
|
||||
timeFrom = params.TimeFromHour + ":" + params.TimeFromMinute + ":" + params.TimeFromSecond
|
||||
}
|
||||
|
||||
var timeTo = ""
|
||||
if digitReg.MatchString(params.TimeToHour) && digitReg.MatchString(params.TimeToMinute) && digitReg.MatchString(params.TimeToSecond) {
|
||||
timeTo = params.TimeToHour + ":" + params.TimeToMinute + ":" + params.TimeToSecond
|
||||
}
|
||||
|
||||
_, err := this.RPC().MessageRecipientRPC().UpdateMessageRecipient(this.AdminContext(), &pb.UpdateMessageRecipientRequest{
|
||||
MessageRecipientId: params.RecipientId,
|
||||
AdminId: params.AdminId,
|
||||
MessageMediaInstanceId: params.InstanceId,
|
||||
User: params.User,
|
||||
MessageRecipientGroupIds: groupIds,
|
||||
Description: params.Description,
|
||||
IsOn: params.IsOn,
|
||||
TimeFrom: timeFrom,
|
||||
TimeTo: timeTo,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -49,6 +49,7 @@ func init() {
|
||||
Post("/start", new(node.StartAction)).
|
||||
Post("/stop", new(node.StopAction)).
|
||||
Post("/up", new(node.UpAction)).
|
||||
Post("/updateIsOn", new(node.UpdateIsOnAction)).
|
||||
Get("/detail", new(node.DetailAction)).
|
||||
GetPost("/updateDNSPopup", new(node.UpdateDNSPopupAction)).
|
||||
Post("/syncDomain", new(node.SyncDomainAction)).
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package nodeutils_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInstallLocalNode(t *testing.T) {
|
||||
err := nodeutils.InstallLocalNode()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
@@ -35,6 +36,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
clusters = append(clusters, node.SecondaryNodeClusters...)
|
||||
var allDNSRouteMaps = map[int64][]maps.Map{} // domain id => routes
|
||||
var routeMaps = map[int64][]maps.Map{} // domain id => routes
|
||||
var domainIds = []int64{}
|
||||
for _, cluster := range clusters {
|
||||
dnsInfoResp, err := this.RPC().NodeRPC().FindEnabledNodeDNS(this.AdminContext(), &pb.FindEnabledNodeDNSRequest{
|
||||
NodeId: params.NodeId,
|
||||
@@ -49,6 +51,13 @@ func (this *IndexAction) RunGet(params struct {
|
||||
continue
|
||||
}
|
||||
var domainId = dnsInfo.DnsDomainId
|
||||
|
||||
// remove same domain
|
||||
if lists.ContainsInt64(domainIds, domainId) {
|
||||
continue
|
||||
}
|
||||
domainIds = append(domainIds, domainId)
|
||||
|
||||
var domainName = dnsInfo.DnsDomainName
|
||||
if len(dnsInfo.Routes) > 0 {
|
||||
for _, route := range dnsInfo.Routes {
|
||||
@@ -102,15 +111,23 @@ func (this *IndexAction) RunPost(params struct {
|
||||
}) {
|
||||
defer this.CreateLogInfo(codes.NodeDNS_LogUpdateNodeDNS, params.NodeId)
|
||||
|
||||
dnsRouteCodes := []string{}
|
||||
var rawRouteCodes = []string{}
|
||||
if len(params.DnsRoutesJSON) > 0 {
|
||||
err := json.Unmarshal(params.DnsRoutesJSON, &dnsRouteCodes)
|
||||
err := json.Unmarshal(params.DnsRoutesJSON, &rawRouteCodes)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// remove duplications
|
||||
var dnsRouteCodes = []string{}
|
||||
for _, routeCode := range rawRouteCodes {
|
||||
if !lists.ContainsString(dnsRouteCodes, routeCode) {
|
||||
dnsRouteCodes = append(dnsRouteCodes, routeCode)
|
||||
}
|
||||
}
|
||||
|
||||
_, err := this.RPC().NodeRPC().UpdateNodeDNS(this.AdminContext(), &pb.UpdateNodeDNSRequest{
|
||||
NodeId: params.NodeId,
|
||||
IpAddr: "",
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package thresholds
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
)
|
||||
|
||||
type IndexAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "node", "update")
|
||||
this.SecondMenu("threshold")
|
||||
}
|
||||
|
||||
func (this *IndexAction) RunGet(params struct {
|
||||
ClusterId int64
|
||||
NodeId int64
|
||||
}) {
|
||||
_, err := nodeutils.InitNodeInfo(this.Parent(), params.NodeId)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["nodeId"] = params.NodeId
|
||||
|
||||
// 列出所有阈值
|
||||
thresholdsResp, err := this.RPC().NodeThresholdRPC().FindAllEnabledNodeThresholds(this.AdminContext(), &pb.FindAllEnabledNodeThresholdsRequest{
|
||||
Role: "node",
|
||||
NodeClusterId: params.ClusterId,
|
||||
NodeId: params.NodeId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
thresholdMaps := []maps.Map{}
|
||||
for _, threshold := range thresholdsResp.NodeThresholds {
|
||||
thresholdMaps = append(thresholdMaps, maps.Map{
|
||||
"id": threshold.Id,
|
||||
"itemName": nodeconfigs.FindNodeValueItemName(threshold.Item),
|
||||
"paramName": nodeconfigs.FindNodeValueItemParamName(threshold.Item, threshold.Param),
|
||||
"operatorName": nodeconfigs.FindNodeValueOperatorName(threshold.Operator),
|
||||
"value": nodeconfigs.UnmarshalNodeValue(threshold.ValueJSON),
|
||||
"sumMethodName": nodeconfigs.FindNodeValueSumMethodName(threshold.SumMethod),
|
||||
"duration": threshold.Duration,
|
||||
"durationUnitName": nodeconfigs.FindNodeValueDurationUnitName(threshold.DurationUnit),
|
||||
"isOn": threshold.IsOn,
|
||||
})
|
||||
}
|
||||
this.Data["thresholds"] = thresholdMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package node
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
)
|
||||
|
||||
type UpdateIsOnAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *UpdateIsOnAction) RunPost(params struct {
|
||||
NodeId int64
|
||||
IsOn bool
|
||||
}) {
|
||||
if params.IsOn {
|
||||
defer this.CreateLogInfo(codes.Node_LogUpdateNodeOn, params.NodeId)
|
||||
} else {
|
||||
defer this.CreateLogInfo(codes.Node_LogUpdateNodeOff, params.NodeId)
|
||||
}
|
||||
|
||||
_, err := this.RPC().NodeRPC().UpdateNodeIsOn(this.AdminContext(), &pb.UpdateNodeIsOnRequest{
|
||||
NodeId: params.NodeId,
|
||||
IsOn: params.IsOn,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
@@ -14,7 +14,7 @@ type IndexAction struct {
|
||||
}
|
||||
|
||||
func (this *IndexAction) Init() {
|
||||
this.Nav("", "setting", "")
|
||||
this.Nav("", "setting", "index")
|
||||
this.SecondMenu("dns")
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
)
|
||||
|
||||
type RecordsAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *RecordsAction) Init() {
|
||||
this.Nav("", "setting", "records")
|
||||
this.SecondMenu("dns")
|
||||
}
|
||||
|
||||
func (this *RecordsAction) RunGet(params struct {
|
||||
ClusterId int64
|
||||
}) {
|
||||
// 集群信息
|
||||
clusterResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeCluster(this.AdminContext(), &pb.FindEnabledNodeClusterRequest{NodeClusterId: params.ClusterId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var cluster = clusterResp.NodeCluster
|
||||
if cluster == nil {
|
||||
this.NotFound("nodeCluster", params.ClusterId)
|
||||
return
|
||||
}
|
||||
this.Data["cluster"] = maps.Map{
|
||||
"id": cluster.Id,
|
||||
"name": cluster.Name,
|
||||
}
|
||||
|
||||
// DNS信息
|
||||
dnsResp, err := this.RPC().NodeClusterRPC().FindEnabledNodeClusterDNS(this.AdminContext(), &pb.FindEnabledNodeClusterDNSRequest{NodeClusterId: params.ClusterId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var defaultRoute = dnsResp.DefaultRoute
|
||||
var domainName = ""
|
||||
var dnsMap = maps.Map{
|
||||
"dnsName": dnsResp.Name,
|
||||
"domainId": 0,
|
||||
"domainName": "",
|
||||
"providerId": 0,
|
||||
"providerName": "",
|
||||
"providerTypeName": "",
|
||||
}
|
||||
if dnsResp.Domain != nil {
|
||||
domainName = dnsResp.Domain.Name
|
||||
dnsMap["domainId"] = dnsResp.Domain.Id
|
||||
dnsMap["domainName"] = dnsResp.Domain.Name
|
||||
}
|
||||
if dnsResp.Provider != nil {
|
||||
dnsMap["providerId"] = dnsResp.Provider.Id
|
||||
dnsMap["providerName"] = dnsResp.Provider.Name
|
||||
dnsMap["providerTypeName"] = dnsResp.Provider.TypeName
|
||||
}
|
||||
|
||||
if len(dnsResp.CnameRecords) > 0 {
|
||||
dnsMap["cnameRecords"] = dnsResp.CnameRecords
|
||||
} else {
|
||||
dnsMap["cnameRecords"] = []string{}
|
||||
}
|
||||
|
||||
this.Data["dnsInfo"] = dnsMap
|
||||
|
||||
// 未安装的节点
|
||||
notInstalledNodesResp, err := this.RPC().NodeRPC().FindAllEnabledNodesDNSWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledNodesDNSWithNodeClusterIdRequest{
|
||||
NodeClusterId: params.ClusterId,
|
||||
IsInstalled: false,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var allNodes = notInstalledNodesResp.Nodes
|
||||
|
||||
// 节点DNS解析记录
|
||||
nodesResp, err := this.RPC().NodeRPC().FindAllEnabledNodesDNSWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledNodesDNSWithNodeClusterIdRequest{
|
||||
NodeClusterId: params.ClusterId,
|
||||
IsInstalled: true,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var installedNodeIdsMap = map[int64]bool{}
|
||||
for _, node := range nodesResp.Nodes {
|
||||
installedNodeIdsMap[node.Id] = true
|
||||
}
|
||||
|
||||
allNodes = append(allNodes, nodesResp.Nodes...)
|
||||
|
||||
var nodeMaps = []maps.Map{}
|
||||
for _, node := range allNodes {
|
||||
var isInstalled = installedNodeIdsMap[node.Id]
|
||||
|
||||
if len(node.Routes) > 0 {
|
||||
for _, route := range node.Routes {
|
||||
// 检查是否已解析
|
||||
var isResolved = false
|
||||
if isInstalled && cluster.DnsDomainId > 0 && len(cluster.DnsName) > 0 && len(node.IpAddr) > 0 {
|
||||
var recordType = "A"
|
||||
if utils.IsIPv6(node.IpAddr) {
|
||||
recordType = "AAAA"
|
||||
}
|
||||
checkResp, err := this.RPC().DNSDomainRPC().ExistDNSDomainRecord(this.AdminContext(), &pb.ExistDNSDomainRecordRequest{
|
||||
DnsDomainId: cluster.DnsDomainId,
|
||||
Name: cluster.DnsName,
|
||||
Type: recordType,
|
||||
Route: route.Code,
|
||||
Value: node.IpAddr,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
isResolved = checkResp.IsOk
|
||||
}
|
||||
|
||||
nodeMaps = append(nodeMaps, maps.Map{
|
||||
"id": node.Id,
|
||||
"name": node.Name,
|
||||
"ipAddr": node.IpAddr,
|
||||
"ipAddrId": node.NodeIPAddressId,
|
||||
"route": maps.Map{
|
||||
"name": route.Name,
|
||||
"code": route.Code,
|
||||
},
|
||||
"clusterId": node.NodeClusterId,
|
||||
"isResolved": isResolved,
|
||||
"isInstalled": isInstalled,
|
||||
"isBackup": node.IsBackupForCluster || node.IsBackupForGroup,
|
||||
"isOffline": node.IsOffline,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// 默认线路
|
||||
var isResolved = false
|
||||
if isInstalled && len(defaultRoute) > 0 {
|
||||
var recordType = "A"
|
||||
if utils.IsIPv6(node.IpAddr) {
|
||||
recordType = "AAAA"
|
||||
}
|
||||
checkResp, err := this.RPC().DNSDomainRPC().ExistDNSDomainRecord(this.AdminContext(), &pb.ExistDNSDomainRecordRequest{
|
||||
DnsDomainId: cluster.DnsDomainId,
|
||||
Name: cluster.DnsName,
|
||||
Type: recordType,
|
||||
Route: defaultRoute,
|
||||
Value: node.IpAddr,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
isResolved = checkResp.IsOk
|
||||
}
|
||||
nodeMaps = append(nodeMaps, maps.Map{
|
||||
"id": node.Id,
|
||||
"name": node.Name,
|
||||
"ipAddr": node.IpAddr,
|
||||
"ipAddrId": node.NodeIPAddressId,
|
||||
"route": maps.Map{
|
||||
"name": "",
|
||||
"code": "",
|
||||
},
|
||||
"clusterId": node.NodeClusterId,
|
||||
"isResolved": isResolved,
|
||||
"isInstalled": isInstalled,
|
||||
"isBackup": node.IsBackupForCluster || node.IsBackupForGroup,
|
||||
"isOffline": node.IsOffline,
|
||||
})
|
||||
}
|
||||
}
|
||||
this.Data["nodes"] = nodeMaps
|
||||
|
||||
// 代理服务解析记录
|
||||
serversResp, err := this.RPC().ServerRPC().FindAllEnabledServersDNSWithNodeClusterId(this.AdminContext(), &pb.FindAllEnabledServersDNSWithNodeClusterIdRequest{NodeClusterId: params.ClusterId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var serverMaps = []maps.Map{}
|
||||
for _, server := range serversResp.Servers {
|
||||
// 检查是否已解析
|
||||
isResolved := false
|
||||
if cluster.DnsDomainId > 0 && len(cluster.DnsName) > 0 && len(server.DnsName) > 0 && len(domainName) > 0 {
|
||||
checkResp, err := this.RPC().DNSDomainRPC().ExistDNSDomainRecord(this.AdminContext(), &pb.ExistDNSDomainRecordRequest{
|
||||
DnsDomainId: cluster.DnsDomainId,
|
||||
Name: server.DnsName,
|
||||
Type: "CNAME",
|
||||
Value: cluster.DnsName + "." + domainName,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
isResolved = checkResp.IsOk
|
||||
}
|
||||
|
||||
serverMaps = append(serverMaps, maps.Map{
|
||||
"id": server.Id,
|
||||
"name": server.Name,
|
||||
"dnsName": server.DnsName,
|
||||
"isResolved": isResolved,
|
||||
})
|
||||
}
|
||||
this.Data["servers"] = serverMaps
|
||||
|
||||
// 检查解析记录是否有变化
|
||||
checkChangesResp, err := this.RPC().NodeClusterRPC().CheckNodeClusterDNSChanges(this.AdminContext(), &pb.CheckNodeClusterDNSChangesRequest{NodeClusterId: params.ClusterId})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
this.Data["dnsHasChanges"] = checkChangesResp.IsChanged
|
||||
|
||||
// 需要解决的问题
|
||||
issuesResp, err := this.RPC().DNSRPC().FindAllDNSIssues(this.AdminContext(), &pb.FindAllDNSIssuesRequest{
|
||||
NodeClusterId: params.ClusterId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var issueMaps = []maps.Map{}
|
||||
for _, issue := range issuesResp.Issues {
|
||||
issueMaps = append(issueMaps, maps.Map{
|
||||
"target": issue.Target,
|
||||
"targetId": issue.TargetId,
|
||||
"type": issue.Type,
|
||||
"description": issue.Description,
|
||||
"params": issue.Params,
|
||||
})
|
||||
}
|
||||
this.Data["issues"] = issueMaps
|
||||
|
||||
// 当前正在执行的任务
|
||||
resp, err := this.RPC().DNSTaskRPC().FindAllDoingDNSTasks(this.AdminContext(), &pb.FindAllDoingDNSTasksRequest{
|
||||
NodeClusterId: params.ClusterId,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
var taskMaps = []maps.Map{}
|
||||
for _, task := range resp.DnsTasks {
|
||||
var clusterMap maps.Map = nil
|
||||
var nodeMap maps.Map = nil
|
||||
var serverMap maps.Map = nil
|
||||
var domainMap maps.Map = nil
|
||||
|
||||
if task.NodeCluster != nil {
|
||||
clusterMap = maps.Map{
|
||||
"id": task.NodeCluster.Id,
|
||||
"name": task.NodeCluster.Name,
|
||||
}
|
||||
}
|
||||
if task.Node != nil {
|
||||
nodeMap = maps.Map{
|
||||
"id": task.Node.Id,
|
||||
"name": task.Node.Name,
|
||||
}
|
||||
}
|
||||
if task.Server != nil {
|
||||
serverMap = maps.Map{
|
||||
"id": task.Server.Id,
|
||||
"name": task.Server.Name,
|
||||
}
|
||||
}
|
||||
if task.DnsDomain != nil {
|
||||
domainMap = maps.Map{
|
||||
"id": task.DnsDomain.Id,
|
||||
"name": task.DnsDomain.Name,
|
||||
}
|
||||
}
|
||||
|
||||
taskMaps = append(taskMaps, maps.Map{
|
||||
"id": task.Id,
|
||||
"type": task.Type,
|
||||
"isDone": task.IsDone,
|
||||
"isOk": task.IsOk,
|
||||
"error": task.Error,
|
||||
"updatedTime": timeutil.FormatTime("Y-m-d H:i:s", task.UpdatedAt),
|
||||
"cluster": clusterMap,
|
||||
"node": nodeMap,
|
||||
"server": serverMap,
|
||||
"domain": domainMap,
|
||||
})
|
||||
}
|
||||
this.Data["tasks"] = taskMaps
|
||||
|
||||
this.Show()
|
||||
}
|
||||
@@ -43,13 +43,26 @@ func (this *IndexAction) RunGet(params struct {
|
||||
}
|
||||
this.Data["config"] = config
|
||||
|
||||
var httpAllDomainMismatchActionCode = serverconfigs.DomainMismatchActionPage
|
||||
var httpAllDomainMismatchActionContentHTML string
|
||||
var httpAllDomainMismatchActionStatusCode = "404"
|
||||
if config.HTTPAll.DomainMismatchAction != nil && config.HTTPAll.DomainMismatchAction.Options != nil {
|
||||
httpAllDomainMismatchActionContentHTML = config.HTTPAll.DomainMismatchAction.Options.GetString("contentHTML")
|
||||
var statusCode = config.HTTPAll.DomainMismatchAction.Options.GetInt("statusCode")
|
||||
if statusCode > 0 {
|
||||
httpAllDomainMismatchActionStatusCode = types.String(statusCode)
|
||||
|
||||
var httpAllDomainMismatchActionRedirectURL = ""
|
||||
|
||||
if config.HTTPAll.DomainMismatchAction != nil {
|
||||
httpAllDomainMismatchActionCode = config.HTTPAll.DomainMismatchAction.Code
|
||||
|
||||
if config.HTTPAll.DomainMismatchAction.Options != nil {
|
||||
// 即使是非 page 处理动作,也读取这些内容,以便于在切换到 page 时,可以顺利读取到先前的设置
|
||||
httpAllDomainMismatchActionContentHTML = config.HTTPAll.DomainMismatchAction.Options.GetString("contentHTML")
|
||||
var statusCode = config.HTTPAll.DomainMismatchAction.Options.GetInt("statusCode")
|
||||
if statusCode > 0 {
|
||||
httpAllDomainMismatchActionStatusCode = types.String(statusCode)
|
||||
}
|
||||
|
||||
if config.HTTPAll.DomainMismatchAction.Code == serverconfigs.DomainMismatchActionRedirect {
|
||||
httpAllDomainMismatchActionRedirectURL = config.HTTPAll.DomainMismatchAction.Options.GetString("url")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
httpAllDomainMismatchActionContentHTML = `<!DOCTYPE html>
|
||||
@@ -73,9 +86,12 @@ p { color: grey; }
|
||||
</html>`
|
||||
}
|
||||
|
||||
this.Data["httpAllDomainMismatchActionCode"] = httpAllDomainMismatchActionCode
|
||||
this.Data["httpAllDomainMismatchActionContentHTML"] = httpAllDomainMismatchActionContentHTML
|
||||
this.Data["httpAllDomainMismatchActionStatusCode"] = httpAllDomainMismatchActionStatusCode
|
||||
|
||||
this.Data["httpAllDomainMismatchActionRedirectURL"] = httpAllDomainMismatchActionRedirectURL
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
@@ -83,18 +99,25 @@ func (this *IndexAction) RunPost(params struct {
|
||||
ClusterId int64
|
||||
|
||||
HttpAllMatchDomainStrictly bool
|
||||
HttpAllDomainMismatchActionCode string
|
||||
HttpAllDomainMismatchActionContentHTML string
|
||||
HttpAllDomainMismatchActionStatusCode string
|
||||
HttpAllDomainMismatchActionRedirectURL string
|
||||
HttpAllAllowMismatchDomainsJSON []byte
|
||||
HttpAllAllowNodeIP bool
|
||||
HttpAllDefaultDomain string
|
||||
HttpAllNodeIPPageHTML string
|
||||
HttpAllNodeIPShowPage bool
|
||||
HttpAllEnableServerAddrVariable bool
|
||||
|
||||
HttpAllServerName string
|
||||
HttpAllSupportsLowVersionHTTP bool
|
||||
HttpAllMatchCertFromAllServers bool
|
||||
HttpAllForceLnRequest bool
|
||||
HttpAllDomainAuditingIsOn bool
|
||||
HttpAllDomainAuditingPrompt string
|
||||
|
||||
HttpAllServerName string
|
||||
HttpAllSupportsLowVersionHTTP bool
|
||||
HttpAllMatchCertFromAllServers bool
|
||||
HttpAllForceLnRequest bool
|
||||
HttpAllLnRequestSchedulingMethod string
|
||||
|
||||
HttpAccessLogIsOn bool
|
||||
HttpAccessLogEnableRequestHeaders bool
|
||||
@@ -109,6 +132,11 @@ func (this *IndexAction) RunPost(params struct {
|
||||
PerformanceAutoWriteTimeout bool
|
||||
PerformanceDebug bool
|
||||
|
||||
// TCP端口设置
|
||||
TcpAllPortRangeMin int
|
||||
TcpAllPortRangeMax int
|
||||
TcpAllDenyPorts []int
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
@@ -138,11 +166,28 @@ func (this *IndexAction) RunPost(params struct {
|
||||
var domainMisMatchStatusCode = types.Int(domainMisMatchStatusCodeString)
|
||||
|
||||
config.HTTPAll.MatchDomainStrictly = params.HttpAllMatchDomainStrictly
|
||||
|
||||
// validate
|
||||
if config.HTTPAll.MatchDomainStrictly {
|
||||
// validate redirect
|
||||
if params.HttpAllDomainMismatchActionCode == serverconfigs.DomainMismatchActionRedirect {
|
||||
if len(params.HttpAllDomainMismatchActionRedirectURL) == 0 {
|
||||
this.FailField("httpAllDomainMismatchActionRedirectURL", "请输入跳转目标网址URL")
|
||||
return
|
||||
}
|
||||
if !regexp.MustCompile(`(?i)(http|https)://`).MatchString(params.HttpAllDomainMismatchActionRedirectURL) {
|
||||
this.FailField("httpAllDomainMismatchActionRedirectURL", "目标网址URL必须以http://或https://开头")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config.HTTPAll.DomainMismatchAction = &serverconfigs.DomainMismatchAction{
|
||||
Code: serverconfigs.DomainMismatchActionPage,
|
||||
Code: params.HttpAllDomainMismatchActionCode,
|
||||
Options: maps.Map{
|
||||
"statusCode": domainMisMatchStatusCode,
|
||||
"contentHTML": params.HttpAllDomainMismatchActionContentHTML,
|
||||
"statusCode": domainMisMatchStatusCode, // page
|
||||
"contentHTML": params.HttpAllDomainMismatchActionContentHTML, // page
|
||||
"url": params.HttpAllDomainMismatchActionRedirectURL, // redirect
|
||||
},
|
||||
}
|
||||
|
||||
@@ -162,11 +207,16 @@ func (this *IndexAction) RunPost(params struct {
|
||||
config.HTTPAll.NodeIPShowPage = params.HttpAllNodeIPShowPage
|
||||
config.HTTPAll.NodeIPPageHTML = params.HttpAllNodeIPPageHTML
|
||||
|
||||
config.HTTPAll.DomainAuditingIsOn = params.HttpAllDomainAuditingIsOn
|
||||
config.HTTPAll.DomainAuditingPrompt = params.HttpAllDomainAuditingPrompt
|
||||
|
||||
// HTTP All
|
||||
config.HTTPAll.ServerName = params.HttpAllServerName
|
||||
config.HTTPAll.SupportsLowVersionHTTP = params.HttpAllSupportsLowVersionHTTP
|
||||
config.HTTPAll.MatchCertFromAllServers = params.HttpAllMatchCertFromAllServers
|
||||
config.HTTPAll.ForceLnRequest = params.HttpAllForceLnRequest
|
||||
config.HTTPAll.LnRequestSchedulingMethod = params.HttpAllLnRequestSchedulingMethod
|
||||
config.HTTPAll.EnableServerAddrVariable = params.HttpAllEnableServerAddrVariable
|
||||
|
||||
// 访问日志
|
||||
config.HTTPAccessLog.IsOn = params.HttpAccessLogIsOn
|
||||
@@ -179,6 +229,23 @@ func (this *IndexAction) RunPost(params struct {
|
||||
// 日志
|
||||
config.Log.RecordServerError = params.LogRecordServerError
|
||||
|
||||
// TCP
|
||||
if params.TcpAllPortRangeMin < 1024 {
|
||||
params.TcpAllPortRangeMin = 1024
|
||||
}
|
||||
if params.TcpAllPortRangeMax > 65534 {
|
||||
params.TcpAllPortRangeMax = 65534
|
||||
} else if params.TcpAllPortRangeMax < 1024 {
|
||||
params.TcpAllPortRangeMax = 1024
|
||||
}
|
||||
if params.TcpAllPortRangeMin > params.TcpAllPortRangeMax {
|
||||
params.TcpAllPortRangeMin, params.TcpAllPortRangeMax = params.TcpAllPortRangeMax, params.TcpAllPortRangeMin
|
||||
}
|
||||
|
||||
config.TCPAll.DenyPorts = params.TcpAllDenyPorts
|
||||
config.TCPAll.PortRangeMin = params.TcpAllPortRangeMin
|
||||
config.TCPAll.PortRangeMax = params.TcpAllPortRangeMax
|
||||
|
||||
// 性能
|
||||
config.Performance.AutoReadTimeout = params.PerformanceAutoReadTimeout
|
||||
config.Performance.AutoWriteTimeout = params.PerformanceAutoWriteTimeout
|
||||
|
||||
@@ -112,6 +112,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
"clock": clockConfig,
|
||||
"autoRemoteStart": cluster.AutoRemoteStart,
|
||||
"autoInstallNftables": cluster.AutoInstallNftables,
|
||||
"autoSystemTuning": cluster.AutoSystemTuning,
|
||||
"sshParams": sshParams,
|
||||
"domainName": fullDomainName,
|
||||
}
|
||||
@@ -139,6 +140,7 @@ func (this *IndexAction) RunPost(params struct {
|
||||
ClockCheckChrony bool
|
||||
AutoRemoteStart bool
|
||||
AutoInstallNftables bool
|
||||
AutoSystemTuning bool
|
||||
|
||||
Must *actions.Must
|
||||
}) {
|
||||
@@ -193,6 +195,7 @@ func (this *IndexAction) RunPost(params struct {
|
||||
ClockJSON: clockConfigJSON,
|
||||
AutoRemoteStart: params.AutoRemoteStart,
|
||||
AutoInstallNftables: params.AutoInstallNftables,
|
||||
AutoSystemTuning: params.AutoSystemTuning,
|
||||
SshParamsJSON: sshParamsJSON,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -40,6 +40,7 @@ func init() {
|
||||
// DNS
|
||||
Prefix("/clusters/cluster/settings/dns").
|
||||
GetPost("", new(dns.IndexAction)).
|
||||
Get("/records", new(dns.RecordsAction)).
|
||||
Post("/randomName", new(dns.RandomNameAction)).
|
||||
|
||||
// 系统服务设置
|
||||
|
||||
@@ -45,6 +45,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
func (this *IndexAction) RunPost(params struct {
|
||||
ClusterId int64
|
||||
IsOn bool
|
||||
Quality int
|
||||
RequireCache bool
|
||||
MinLengthJSON []byte
|
||||
MaxLengthJSON []byte
|
||||
@@ -59,6 +60,13 @@ func (this *IndexAction) RunPost(params struct {
|
||||
RequireCache: params.RequireCache,
|
||||
}
|
||||
|
||||
if params.Quality < 0 {
|
||||
params.Quality = 0
|
||||
} else if params.Quality > 100 {
|
||||
params.Quality = 100
|
||||
}
|
||||
config.Quality = params.Quality
|
||||
|
||||
if len(params.MinLengthJSON) > 0 {
|
||||
var minLength = &shared.SizeCapacity{}
|
||||
err := json.Unmarshal(params.MinLengthJSON, minLength)
|
||||
@@ -79,6 +87,11 @@ func (this *IndexAction) RunPost(params struct {
|
||||
config.MaxLength = maxLength
|
||||
}
|
||||
|
||||
err := config.Init()
|
||||
if err != nil {
|
||||
this.Fail("配置校验失败:" + err.Error())
|
||||
}
|
||||
|
||||
configJSON, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
@@ -87,7 +100,7 @@ func (this *IndexAction) RunPost(params struct {
|
||||
|
||||
_, err = this.RPC().NodeClusterRPC().UpdateNodeClusterWebPPolicy(this.AdminContext(), &pb.UpdateNodeClusterWebPPolicyRequest{
|
||||
NodeClusterId: params.ClusterId,
|
||||
WebpPolicyJSON: configJSON,
|
||||
WebPPolicyJSON: configJSON,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -180,7 +180,7 @@ func (this *ClusterHelper) createSettingMenu(cluster *pb.NodeCluster, info *pb.F
|
||||
"name": this.Lang(actionPtr, codes.NodeClusterMenu_SettingWebP),
|
||||
"url": "/clusters/cluster/settings/webp?clusterId=" + clusterId,
|
||||
"isActive": selectedItem == "webp",
|
||||
"isOn": info != nil && info.WebpIsOn,
|
||||
"isOn": info != nil && info.WebPIsOn,
|
||||
})
|
||||
|
||||
items = this.filterMenuItems1(items, info, clusterId, selectedItem, actionPtr)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package clusters
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/dns/domains/domainutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
@@ -9,6 +11,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
)
|
||||
|
||||
type CreateAction struct {
|
||||
@@ -44,6 +47,18 @@ func (this *CreateAction) RunGet(params struct{}) {
|
||||
}
|
||||
this.Data["totalNodes"] = totalNodesResp.Count
|
||||
|
||||
// 随机子域名
|
||||
var defaultDNSName = "g" + rands.HexString(6) + ".cdn"
|
||||
{
|
||||
var b = make([]byte, 3)
|
||||
_, err = rand.Read(b)
|
||||
if err == nil {
|
||||
defaultDNSName = fmt.Sprintf("g%x.cdn", b)
|
||||
}
|
||||
}
|
||||
this.Data["defaultDNSName"] = defaultDNSName
|
||||
this.Data["dnsName"] = defaultDNSName
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
@@ -64,6 +79,7 @@ func (this *CreateAction) RunPost(params struct {
|
||||
InstallDir string
|
||||
SystemdServiceIsOn bool
|
||||
AutoInstallNftables bool
|
||||
AutoSystemTuning bool
|
||||
|
||||
// DNS相关
|
||||
DnsDomainId int64
|
||||
@@ -132,6 +148,7 @@ func (this *CreateAction) RunPost(params struct {
|
||||
SystemServicesJSON: systemServicesJSON,
|
||||
GlobalServerConfigJSON: globalServerConfigJSON,
|
||||
AutoInstallNftables: params.AutoInstallNftables,
|
||||
AutoSystemTuning: params.AutoSystemTuning,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
|
||||
@@ -31,7 +31,14 @@ func (this *GrantAction) RunGet(params struct {
|
||||
|
||||
// TODO 处理节点专用的认证
|
||||
|
||||
grant := grantResp.NodeGrant
|
||||
var grant = grantResp.NodeGrant
|
||||
|
||||
var privateKey = grant.PrivateKey
|
||||
const maskLength = 64
|
||||
if len(privateKey) > maskLength+32 {
|
||||
privateKey = privateKey[:maskLength] + strings.Repeat("*", len(privateKey)-maskLength)
|
||||
}
|
||||
|
||||
this.Data["grant"] = maps.Map{
|
||||
"id": grant.Id,
|
||||
"name": grant.Name,
|
||||
@@ -39,7 +46,7 @@ func (this *GrantAction) RunGet(params struct {
|
||||
"methodName": grantutils.FindGrantMethodName(grant.Method, this.LangCode()),
|
||||
"username": grant.Username,
|
||||
"password": strings.Repeat("*", len(grant.Password)),
|
||||
"privateKey": grant.PrivateKey,
|
||||
"privateKey": privateKey,
|
||||
"passphrase": strings.Repeat("*", len(grant.Passphrase)),
|
||||
"description": grant.Description,
|
||||
"su": grant.Su,
|
||||
|
||||
@@ -27,7 +27,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
page := this.NewPage(countResp.Count)
|
||||
var page = this.NewPage(countResp.Count)
|
||||
this.Data["page"] = page.AsHTML()
|
||||
|
||||
grantsResp, err := this.RPC().NodeGrantRPC().ListEnabledNodeGrants(this.AdminContext(), &pb.ListEnabledNodeGrantsRequest{
|
||||
@@ -39,7 +39,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
grantMaps := []maps.Map{}
|
||||
var grantMaps = []maps.Map{}
|
||||
for _, grant := range grantsResp.NodeGrants {
|
||||
// 集群数
|
||||
countClustersResp, err := this.RPC().NodeClusterRPC().CountAllEnabledNodeClustersWithNodeGrantId(this.AdminContext(), &pb.CountAllEnabledNodeClustersWithNodeGrantIdRequest{NodeGrantId: grant.Id})
|
||||
@@ -47,7 +47,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
countClusters := countClustersResp.Count
|
||||
var countClusters = countClustersResp.Count
|
||||
|
||||
// 节点数
|
||||
countNodesResp, err := this.RPC().NodeRPC().CountAllEnabledNodesWithNodeGrantId(this.AdminContext(), &pb.CountAllEnabledNodesWithNodeGrantIdRequest{NodeGrantId: grant.Id})
|
||||
@@ -55,7 +55,7 @@ func (this *IndexAction) RunGet(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
countNodes := countNodesResp.Count
|
||||
var countNodes = countNodesResp.Count
|
||||
|
||||
grantMaps = append(grantMaps, maps.Map{
|
||||
"id": grant.Id,
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package grants
|
||||
|
||||
import ( "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/grants/grantutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type UpdateAction struct {
|
||||
@@ -34,15 +36,23 @@ func (this *UpdateAction) RunGet(params struct {
|
||||
|
||||
// TODO 处理节点专用的认证
|
||||
|
||||
grant := grantResp.NodeGrant
|
||||
var grant = grantResp.NodeGrant
|
||||
|
||||
// private key
|
||||
var privateKey = grant.PrivateKey
|
||||
const maskLength = 64
|
||||
if len(privateKey) > maskLength+32 {
|
||||
privateKey = privateKey[:maskLength] + strings.Repeat("*", len(privateKey)-maskLength)
|
||||
}
|
||||
|
||||
this.Data["grant"] = maps.Map{
|
||||
"id": grant.Id,
|
||||
"name": grant.Name,
|
||||
"method": grant.Method,
|
||||
"methodName": grantutils.FindGrantMethodName(grant.Method, this.LangCode()),
|
||||
"username": grant.Username,
|
||||
"password": grant.Password,
|
||||
"privateKey": grant.PrivateKey,
|
||||
"password": strings.Repeat("*", len(grant.Password)),
|
||||
"privateKey": privateKey,
|
||||
"passphrase": grant.Passphrase,
|
||||
"description": grant.Description,
|
||||
"su": grant.Su,
|
||||
@@ -85,15 +95,17 @@ func (this *UpdateAction) RunPost(params struct {
|
||||
}
|
||||
|
||||
// 验证私钥
|
||||
var err error
|
||||
if len(params.Passphrase) > 0 {
|
||||
_, err = ssh.ParsePrivateKeyWithPassphrase([]byte(params.PrivateKey), []byte(params.Passphrase))
|
||||
} else {
|
||||
_, err = ssh.ParsePrivateKey([]byte(params.PrivateKey))
|
||||
}
|
||||
if err != nil {
|
||||
this.Fail("私钥验证失败,请检查格式:" + err.Error())
|
||||
return
|
||||
if !strings.HasSuffix(params.PrivateKey, "******") /* 非掩码 */ {
|
||||
var err error
|
||||
if len(params.Passphrase) > 0 {
|
||||
_, err = ssh.ParsePrivateKeyWithPassphrase([]byte(params.PrivateKey), []byte(params.Passphrase))
|
||||
} else {
|
||||
_, err = ssh.ParsePrivateKey([]byte(params.PrivateKey))
|
||||
}
|
||||
if err != nil {
|
||||
this.Fail("私钥验证失败,请检查格式:" + err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
default:
|
||||
this.Fail("请选择正确的认证方式")
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"encoding/json"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils/sizes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
@@ -19,6 +18,7 @@ import (
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CheckDiskPartitions 检查服务器磁盘空间
|
||||
@@ -47,9 +47,17 @@ func CheckDiskPartitions(thresholdPercent float64) (path string, usage uint64, u
|
||||
if p.Fstype != rootFS {
|
||||
continue
|
||||
}
|
||||
|
||||
// skip some specified partitions on macOS
|
||||
if runtime.GOOS == "darwin" {
|
||||
if strings.Contains(p.Mountpoint, "/Developer/") {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
stat, _ := disk.Usage(p.Mountpoint)
|
||||
if stat != nil {
|
||||
if stat.Used < 2*uint64(sizes.G) {
|
||||
if stat.Used < (5<<30) || stat.Free > (100<<30) {
|
||||
continue
|
||||
}
|
||||
if stat.UsedPercent > thresholdPercent {
|
||||
|
||||
@@ -154,17 +154,6 @@ func (this *IndexAction) RunPost(params struct{}) {
|
||||
"version": "",
|
||||
}
|
||||
}
|
||||
if resp.MonitorNodeUpgradeInfo != nil {
|
||||
this.Data["monitorNodeUpgradeInfo"] = maps.Map{
|
||||
"count": resp.MonitorNodeUpgradeInfo.CountNodes,
|
||||
"version": resp.MonitorNodeUpgradeInfo.NewVersion,
|
||||
}
|
||||
} else {
|
||||
this.Data["monitorNodeUpgradeInfo"] = maps.Map{
|
||||
"count": 0,
|
||||
"version": "",
|
||||
}
|
||||
}
|
||||
if resp.ApiNodeUpgradeInfo != nil {
|
||||
this.Data["apiNodeUpgradeInfo"] = maps.Map{
|
||||
"count": resp.ApiNodeUpgradeInfo.CountNodes,
|
||||
@@ -176,39 +165,6 @@ func (this *IndexAction) RunPost(params struct{}) {
|
||||
"version": "",
|
||||
}
|
||||
}
|
||||
if resp.UserNodeUpgradeInfo != nil {
|
||||
this.Data["userNodeUpgradeInfo"] = maps.Map{
|
||||
"count": resp.UserNodeUpgradeInfo.CountNodes,
|
||||
"version": resp.UserNodeUpgradeInfo.NewVersion,
|
||||
}
|
||||
} else {
|
||||
this.Data["userNodeUpgradeInfo"] = maps.Map{
|
||||
"count": 0,
|
||||
"version": 0,
|
||||
}
|
||||
}
|
||||
if resp.AuthorityNodeUpgradeInfo != nil {
|
||||
this.Data["authorityNodeUpgradeInfo"] = maps.Map{
|
||||
"count": resp.AuthorityNodeUpgradeInfo.CountNodes,
|
||||
"version": resp.AuthorityNodeUpgradeInfo.NewVersion,
|
||||
}
|
||||
} else {
|
||||
this.Data["authorityNodeUpgradeInfo"] = maps.Map{
|
||||
"count": 0,
|
||||
"version": "",
|
||||
}
|
||||
}
|
||||
if resp.NsNodeUpgradeInfo != nil {
|
||||
this.Data["nsNodeUpgradeInfo"] = maps.Map{
|
||||
"count": resp.NsNodeUpgradeInfo.CountNodes,
|
||||
"version": resp.NsNodeUpgradeInfo.NewVersion,
|
||||
}
|
||||
} else {
|
||||
this.Data["nsNodeUpgradeInfo"] = maps.Map{
|
||||
"count": 0,
|
||||
"version": "",
|
||||
}
|
||||
}
|
||||
|
||||
// 域名排行
|
||||
{
|
||||
|
||||
@@ -52,9 +52,13 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
Type string
|
||||
|
||||
// DNSPod
|
||||
ParamId string
|
||||
ParamToken string
|
||||
ParamRegion string
|
||||
ParamDNSPodId string
|
||||
ParamDNSPodToken string
|
||||
ParamDNSPodRegion string
|
||||
|
||||
ParamDNSPodAPIType string
|
||||
ParamDNSPodAccessKeyId string
|
||||
ParamDNSPodAccessKeySecret string
|
||||
|
||||
// AliDNS
|
||||
ParamAliDNSAccessKeyId string
|
||||
@@ -89,18 +93,31 @@ func (this *CreatePopupAction) RunPost(params struct {
|
||||
Field("type", params.Type).
|
||||
Require("请选择服务商厂家")
|
||||
|
||||
apiParams := maps.Map{}
|
||||
var apiParams = maps.Map{}
|
||||
switch params.Type {
|
||||
case "dnspod":
|
||||
params.Must.
|
||||
Field("paramId", params.ParamId).
|
||||
Require("请输入密钥ID").
|
||||
Field("paramToken", params.ParamToken).
|
||||
Require("请输入密钥Token")
|
||||
apiParams["apiType"] = params.ParamDNSPodAPIType
|
||||
switch params.ParamDNSPodAPIType {
|
||||
case "tencentDNS":
|
||||
params.Must.
|
||||
Field("paramDNSPodAccessKeyId", params.ParamDNSPodAccessKeyId).
|
||||
Require("请输入SecretId").
|
||||
Field("paramDNSPodAccessKeySecret", params.ParamDNSPodAccessKeySecret).
|
||||
Require("请输入SecretKey")
|
||||
apiParams["accessKeyId"] = params.ParamDNSPodAccessKeyId
|
||||
apiParams["accessKeySecret"] = params.ParamDNSPodAccessKeySecret
|
||||
apiParams["region"] = params.ParamDNSPodRegion
|
||||
default:
|
||||
params.Must.
|
||||
Field("paramId", params.ParamDNSPodId).
|
||||
Require("请输入密钥ID").
|
||||
Field("paramToken", params.ParamDNSPodToken).
|
||||
Require("请输入密钥Token")
|
||||
|
||||
apiParams["id"] = params.ParamId
|
||||
apiParams["token"] = params.ParamToken
|
||||
apiParams["region"] = params.ParamRegion
|
||||
apiParams["id"] = params.ParamDNSPodId
|
||||
apiParams["token"] = params.ParamDNSPodToken
|
||||
apiParams["region"] = params.ParamDNSPodRegion
|
||||
}
|
||||
case "alidns":
|
||||
params.Must.
|
||||
Field("paramAliDNSAccessKeyId", params.ParamAliDNSAccessKeyId).
|
||||
|
||||
@@ -24,7 +24,10 @@ func (this *ProviderAction) RunGet(params struct {
|
||||
this.Data["pageNo"] = params.Page
|
||||
this.Data["filter"] = params.Filter
|
||||
|
||||
providerResp, err := this.RPC().DNSProviderRPC().FindEnabledDNSProvider(this.AdminContext(), &pb.FindEnabledDNSProviderRequest{DnsProviderId: params.ProviderId})
|
||||
providerResp, err := this.RPC().DNSProviderRPC().FindEnabledDNSProvider(this.AdminContext(), &pb.FindEnabledDNSProviderRequest{
|
||||
DnsProviderId: params.ProviderId,
|
||||
MaskParams: true,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
|
||||
@@ -22,7 +22,10 @@ func (this *UpdatePopupAction) Init() {
|
||||
func (this *UpdatePopupAction) RunGet(params struct {
|
||||
ProviderId int64
|
||||
}) {
|
||||
providerResp, err := this.RPC().DNSProviderRPC().FindEnabledDNSProvider(this.AdminContext(), &pb.FindEnabledDNSProviderRequest{DnsProviderId: params.ProviderId})
|
||||
providerResp, err := this.RPC().DNSProviderRPC().FindEnabledDNSProvider(this.AdminContext(), &pb.FindEnabledDNSProviderRequest{
|
||||
DnsProviderId: params.ProviderId,
|
||||
MaskParams: true,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
@@ -79,9 +82,13 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
||||
Type string
|
||||
|
||||
// DNSPod
|
||||
ParamId string
|
||||
ParamToken string
|
||||
ParamRegion string
|
||||
ParamDNSPodId string
|
||||
ParamDNSPodToken string
|
||||
ParamDNSPodRegion string
|
||||
|
||||
ParamDNSPodAPIType string
|
||||
ParamDNSPodAccessKeyId string
|
||||
ParamDNSPodAccessKeySecret string
|
||||
|
||||
// AliDNS
|
||||
ParamAliDNSAccessKeyId string
|
||||
@@ -118,18 +125,31 @@ func (this *UpdatePopupAction) RunPost(params struct {
|
||||
Field("type", params.Type).
|
||||
Require("请选择服务商厂家")
|
||||
|
||||
apiParams := maps.Map{}
|
||||
var apiParams = maps.Map{}
|
||||
switch params.Type {
|
||||
case "dnspod":
|
||||
params.Must.
|
||||
Field("paramId", params.ParamId).
|
||||
Require("请输入密钥ID").
|
||||
Field("paramToken", params.ParamToken).
|
||||
Require("请输入密钥Token")
|
||||
apiParams["apiType"] = params.ParamDNSPodAPIType
|
||||
switch params.ParamDNSPodAPIType {
|
||||
case "tencentDNS":
|
||||
params.Must.
|
||||
Field("paramDNSPodAccessKeyId", params.ParamDNSPodAccessKeyId).
|
||||
Require("请输入SecretId").
|
||||
Field("paramDNSPodAccessKeySecret", params.ParamDNSPodAccessKeySecret).
|
||||
Require("请输入SecretKey")
|
||||
apiParams["accessKeyId"] = params.ParamDNSPodAccessKeyId
|
||||
apiParams["accessKeySecret"] = params.ParamDNSPodAccessKeySecret
|
||||
apiParams["region"] = params.ParamDNSPodRegion
|
||||
default:
|
||||
params.Must.
|
||||
Field("paramId", params.ParamDNSPodId).
|
||||
Require("请输入密钥ID").
|
||||
Field("paramToken", params.ParamDNSPodToken).
|
||||
Require("请输入密钥Token")
|
||||
|
||||
apiParams["id"] = params.ParamId
|
||||
apiParams["token"] = params.ParamToken
|
||||
apiParams["region"] = params.ParamRegion
|
||||
apiParams["id"] = params.ParamDNSPodId
|
||||
apiParams["token"] = params.ParamDNSPodToken
|
||||
apiParams["region"] = params.ParamDNSPodRegion
|
||||
}
|
||||
case "alidns":
|
||||
params.Must.
|
||||
Field("paramAliDNSAccessKeyId", params.ParamAliDNSAccessKeyId).
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/langs/codes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/dao"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
@@ -121,6 +122,19 @@ func (this *IndexAction) RunGet(params struct {
|
||||
// 删除Cookie
|
||||
loginutils.UnsetCookie(this.Object())
|
||||
|
||||
// 检查单体实例是否已经被初始化
|
||||
{
|
||||
settingResp, err := this.RPC().SysSettingRPC().ReadSysSetting(this.AdminContext(), &pb.ReadSysSettingRequest{Code: systemconfigs.SettingCodeStandaloneInstanceInitialized})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if string(settingResp.ValueJSON) == "0" {
|
||||
this.RedirectURL("/initPassword")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
@@ -175,7 +189,7 @@ func (this *IndexAction) RunPost(params struct {
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
err = dao.SharedLogDAO.CreateAdminLog(rpcClient.Context(0), oplogs.LevelError, this.Request.URL.Path, langs.DefaultMessage(codes.AdminLogin_LogSystemError, err.Error()), this.RequestRemoteIP(), codes.AdminLogin_LogSystemError, []any{err.Error()})
|
||||
err = dao.SharedLogDAO.CreateAdminLog(rpcClient.Context(0), oplogs.LevelError, this.Request.URL.Path, langs.DefaultMessage(codes.AdminLogin_LogSystemError, err.Error()), loginutils.RemoteIP(&this.ActionObject), codes.AdminLogin_LogSystemError, []any{err.Error()})
|
||||
if err != nil {
|
||||
utils.PrintError(err)
|
||||
}
|
||||
@@ -185,7 +199,7 @@ func (this *IndexAction) RunPost(params struct {
|
||||
}
|
||||
|
||||
if !resp.IsOk {
|
||||
err = dao.SharedLogDAO.CreateAdminLog(rpcClient.Context(0), oplogs.LevelWarn, this.Request.URL.Path, langs.DefaultMessage(codes.AdminLogin_LogFailed, params.Username), this.RequestRemoteIP(), codes.AdminLogin_LogFailed, []any{params.Username})
|
||||
err = dao.SharedLogDAO.CreateAdminLog(rpcClient.Context(0), oplogs.LevelWarn, this.Request.URL.Path, langs.DefaultMessage(codes.AdminLogin_LogFailed, params.Username), loginutils.RemoteIP(&this.ActionObject), codes.AdminLogin_LogFailed, []any{params.Username})
|
||||
if err != nil {
|
||||
utils.PrintError(err)
|
||||
}
|
||||
@@ -225,7 +239,7 @@ func (this *IndexAction) RunPost(params struct {
|
||||
params.Auth.StoreAdmin(adminId, params.Remember)
|
||||
|
||||
// 记录日志
|
||||
err = dao.SharedLogDAO.CreateAdminLog(rpcClient.Context(adminId), oplogs.LevelInfo, this.Request.URL.Path, langs.DefaultMessage(codes.AdminLogin_LogSuccess, params.Username), this.RequestRemoteIP(), codes.AdminLogin_LogSuccess, []any{params.Username})
|
||||
err = dao.SharedLogDAO.CreateAdminLog(rpcClient.Context(adminId), oplogs.LevelInfo, this.Request.URL.Path, langs.DefaultMessage(codes.AdminLogin_LogSuccess, params.Username), loginutils.RemoteIP(&this.ActionObject), codes.AdminLogin_LogSuccess, []any{params.Username})
|
||||
if err != nil {
|
||||
utils.PrintError(err)
|
||||
}
|
||||
@@ -235,7 +249,7 @@ func (this *IndexAction) RunPost(params struct {
|
||||
|
||||
// 检查登录区域
|
||||
func (this *IndexAction) checkRegion() bool {
|
||||
var ip = this.RequestRemoteIP()
|
||||
var ip = loginutils.RemoteIP(&this.ActionObject)
|
||||
var result = iplibrary.LookupIP(ip)
|
||||
if result != nil && result.IsOk() && result.CountryId() > 0 && lists.ContainsInt64([]int64{9, 10}, result.CountryId()) {
|
||||
return false
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user