Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -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="-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 ..."
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/nodes"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
_ "github.com/TeaOSLab/EdgeAdmin/internal/web"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/clusters/cluster/node/nodeutils"
|
||||
_ "github.com/TeaOSLab/EdgeCommon/pkg/langs/messages"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
@@ -180,14 +179,6 @@ func main() {
|
||||
log.Println("restarting ...")
|
||||
app.RunRestart()
|
||||
})
|
||||
app.On("install-local-node", func() {
|
||||
err := nodeutils.InstallLocalNode()
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]" + err.Error())
|
||||
return
|
||||
}
|
||||
fmt.Println("success")
|
||||
})
|
||||
app.Run(func() {
|
||||
var adminNode = nodes.NewAdminNode()
|
||||
adminNode.Run()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
FROM --platform=linux/amd64 alpine:latest
|
||||
LABEL maintainer="goedge.cdn@gmail.com"
|
||||
ENV TZ "Asia/Shanghai"
|
||||
ENV VERSION 1.3.3
|
||||
ENV VERSION 1.3.4
|
||||
ENV ROOT_DIR /usr/local/goedge
|
||||
ENV TAR_FILE edge-admin-linux-amd64-plus-v${VERSION}.zip
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "1.3.3"
|
||||
Version = "1.3.4"
|
||||
|
||||
APINodeVersion = "1.3.3"
|
||||
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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -112,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
|
||||
@@ -130,7 +136,7 @@ After=network-online.target
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=5s
|
||||
ExecStart=` + exePath + ` daemon
|
||||
ExecStart=` + startCmd + `
|
||||
ExecStop=` + exePath + ` stop
|
||||
ExecReload=` + exePath + ` reload
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -4,23 +4,12 @@ package nodeutils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
executils "github.com/TeaOSLab/EdgeAdmin/internal/utils/exec"
|
||||
"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/Tea"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"gopkg.in/yaml.v3"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// InitNodeInfo 初始化节点信息
|
||||
@@ -115,124 +104,3 @@ func InitNodeInfo(parentAction *actionutils.ParentAction, nodeId int64) (*pb.Nod
|
||||
|
||||
return nodeResp.Node, nil
|
||||
}
|
||||
|
||||
// InstallLocalNode 安装本地节点
|
||||
func InstallLocalNode() error {
|
||||
var targetDir = Tea.Root
|
||||
var nodeDir = targetDir + "/edge-node"
|
||||
var apiAddr = "http://127.0.0.1:8001" // 先固定
|
||||
|
||||
// 查找节点安装文件
|
||||
var zipFile = Tea.Root + "/edge-api/deploy/edge-node-linux-" + runtime.GOARCH + "-v" + teaconst.Version /** 默认和管理系统一致 **/ + ".zip"
|
||||
{
|
||||
stat, err := os.Stat(zipFile)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return errors.New("installer file not found in '" + zipFile + "'")
|
||||
}
|
||||
return fmt.Errorf("open installer file failed: %w", err)
|
||||
}
|
||||
if stat.IsDir() {
|
||||
return errors.New("invalid installer file '" + zipFile + "'")
|
||||
}
|
||||
}
|
||||
|
||||
// 解压节点
|
||||
var unzip = utils.NewUnzip(zipFile, targetDir)
|
||||
err := unzip.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unzip installer file failed: %w", err)
|
||||
}
|
||||
|
||||
// 创建节点
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return fmt.Errorf("create rpc client failed: %w", err)
|
||||
}
|
||||
var ctx = rpcClient.Context(0)
|
||||
nodeClustersResp, err := rpcClient.NodeClusterRPC().ListEnabledNodeClusters(ctx, &pb.ListEnabledNodeClustersRequest{
|
||||
IdDesc: true,
|
||||
Offset: 0,
|
||||
Size: 1,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(nodeClustersResp.NodeClusters) == 0 {
|
||||
return errors.New("no clusters yet, please create a cluster at least")
|
||||
}
|
||||
var clusterId = nodeClustersResp.NodeClusters[0].Id
|
||||
|
||||
// 检查节点是否已生成
|
||||
countNodesResp, err := rpcClient.NodeRPC().CountAllEnabledNodesMatch(ctx, &pb.CountAllEnabledNodesMatchRequest{
|
||||
NodeClusterId: clusterId,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if countNodesResp.Count > 0 {
|
||||
// 这里先不判断是否有本地节点,只要有节点,就不允许再次执行
|
||||
return errors.New("there are already nodes in the cluster")
|
||||
}
|
||||
|
||||
createNodeResp, err := rpcClient.NodeRPC().CreateNode(ctx, &pb.CreateNodeRequest{
|
||||
Name: "本地节点",
|
||||
NodeClusterId: clusterId,
|
||||
NodeLogin: nil,
|
||||
NodeGroupId: 0,
|
||||
DnsRoutes: nil,
|
||||
NodeRegionId: 0,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var nodeId = createNodeResp.NodeId
|
||||
nodeResp, err := rpcClient.NodeRPC().FindEnabledNode(ctx, &pb.FindEnabledNodeRequest{NodeId: nodeId})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if nodeResp.Node == nil {
|
||||
return errors.New("could not find local node with created id '" + types.String(nodeId) + "'")
|
||||
}
|
||||
var node = nodeResp.Node
|
||||
|
||||
// 生成节点配置
|
||||
var apiConfig = &configs.APIConfig{
|
||||
RPCEndpoints: []string{apiAddr},
|
||||
RPCDisableUpdate: true,
|
||||
NodeId: node.UniqueId,
|
||||
Secret: node.Secret,
|
||||
}
|
||||
apiConfigYAML, err := yaml.Marshal(apiConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encode config failed: %w", err)
|
||||
}
|
||||
err = os.WriteFile(nodeDir+"/configs/api_node.yaml", apiConfigYAML, 0666)
|
||||
if err != nil {
|
||||
return fmt.Errorf("write config file failed: %w", err)
|
||||
}
|
||||
|
||||
// 测试节点
|
||||
{
|
||||
var cmd = executils.NewTimeoutCmd(5*time.Second, nodeDir+"/bin/edge-node", "test")
|
||||
cmd.WithStdout()
|
||||
cmd.WithStderr()
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("node test failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 启动节点
|
||||
{
|
||||
var cmd = executils.NewTimeoutCmd(5*time.Second, nodeDir+"/bin/edge-node", "start")
|
||||
cmd.WithStdout()
|
||||
cmd.WithStderr()
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("node start failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -46,6 +46,9 @@ func (this *IndexAction) RunGet(params struct {
|
||||
var httpAllDomainMismatchActionCode = serverconfigs.DomainMismatchActionPage
|
||||
var httpAllDomainMismatchActionContentHTML string
|
||||
var httpAllDomainMismatchActionStatusCode = "404"
|
||||
|
||||
var httpAllDomainMismatchActionRedirectURL = ""
|
||||
|
||||
if config.HTTPAll.DomainMismatchAction != nil {
|
||||
httpAllDomainMismatchActionCode = config.HTTPAll.DomainMismatchAction.Code
|
||||
|
||||
@@ -56,6 +59,10 @@ func (this *IndexAction) RunGet(params struct {
|
||||
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>
|
||||
@@ -83,6 +90,8 @@ p { color: grey; }
|
||||
this.Data["httpAllDomainMismatchActionContentHTML"] = httpAllDomainMismatchActionContentHTML
|
||||
this.Data["httpAllDomainMismatchActionStatusCode"] = httpAllDomainMismatchActionStatusCode
|
||||
|
||||
this.Data["httpAllDomainMismatchActionRedirectURL"] = httpAllDomainMismatchActionRedirectURL
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
@@ -93,6 +102,7 @@ func (this *IndexAction) RunPost(params struct {
|
||||
HttpAllDomainMismatchActionCode string
|
||||
HttpAllDomainMismatchActionContentHTML string
|
||||
HttpAllDomainMismatchActionStatusCode string
|
||||
HttpAllDomainMismatchActionRedirectURL string
|
||||
HttpAllAllowMismatchDomainsJSON []byte
|
||||
HttpAllAllowNodeIP bool
|
||||
HttpAllDefaultDomain string
|
||||
@@ -156,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: params.HttpAllDomainMismatchActionCode,
|
||||
Options: maps.Map{
|
||||
"statusCode": domainMisMatchStatusCode,
|
||||
"contentHTML": params.HttpAllDomainMismatchActionContentHTML,
|
||||
"statusCode": domainMisMatchStatusCode, // page
|
||||
"contentHTML": params.HttpAllDomainMismatchActionContentHTML, // page
|
||||
"url": params.HttpAllDomainMismatchActionRedirectURL, // redirect
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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("请选择正确的认证方式")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ func init() {
|
||||
Prefix("").
|
||||
GetPost("/", new(IndexAction)).
|
||||
GetPost("/index/otp", new(OtpAction)).
|
||||
GetPost("/initPassword", new(InitPasswordAction)).
|
||||
EndAll()
|
||||
})
|
||||
}
|
||||
|
||||
104
internal/web/actions/default/index/initPassword.go
Normal file
104
internal/web/actions/default/index/initPassword.go
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package index
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type InitPasswordAction struct {
|
||||
actionutils.ParentAction
|
||||
}
|
||||
|
||||
func (this *InitPasswordAction) Init() {
|
||||
this.Nav("", "", "")
|
||||
}
|
||||
|
||||
func (this *InitPasswordAction) RunGet(params struct{}) {
|
||||
isNotInitialized, err := this.isNotInitialized()
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if !isNotInitialized {
|
||||
this.RedirectURL("/")
|
||||
return
|
||||
}
|
||||
|
||||
this.Data["username"] = "admin"
|
||||
this.Data["password"] = ""
|
||||
|
||||
this.Show()
|
||||
}
|
||||
|
||||
func (this *InitPasswordAction) RunPost(params struct {
|
||||
Username string
|
||||
Password string
|
||||
|
||||
Must *actions.Must
|
||||
CSRF *actionutils.CSRF
|
||||
}) {
|
||||
isNotInitialized, err := this.isNotInitialized()
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if !isNotInitialized {
|
||||
this.ResponseWriter.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
params.Must.
|
||||
Field("username", params.Username).
|
||||
Require("请输入登录用户名").
|
||||
Match(`^[a-zA-Z0-9_]+$`, "用户名中只能包含英文、数字或下划线").
|
||||
Field("password", params.Password).
|
||||
Require("请输入密码")
|
||||
|
||||
// 查找ID
|
||||
adminResp, err := this.RPC().AdminRPC().FindAdminWithUsername(this.AdminContext(), &pb.FindAdminWithUsernameRequest{Username: "admin" /** 固定的 **/})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
if adminResp.Admin == nil {
|
||||
this.Fail("数据错误,请将数据库中的edgeAdmins表中的用户名修改为admin后再试")
|
||||
return
|
||||
}
|
||||
var adminId = adminResp.Admin.Id
|
||||
|
||||
// 修改密码
|
||||
_, err = this.RPC().AdminRPC().UpdateAdminLogin(this.AdminContext(), &pb.UpdateAdminLoginRequest{
|
||||
AdminId: adminId,
|
||||
Username: params.Username,
|
||||
Password: params.Password, // raw
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
// 修改为初始化完成
|
||||
_, err = this.RPC().SysSettingRPC().UpdateSysSetting(this.AdminContext(), &pb.UpdateSysSettingRequest{
|
||||
Code: systemconfigs.SettingCodeStandaloneInstanceInitialized,
|
||||
ValueJSON: []byte("1"),
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
this.Success()
|
||||
}
|
||||
|
||||
func (this *InitPasswordAction) isNotInitialized() (bool, error) {
|
||||
settingResp, err := this.RPC().SysSettingRPC().ReadSysSetting(this.AdminContext(), &pb.ReadSysSettingRequest{Code: systemconfigs.SettingCodeStandaloneInstanceInitialized})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return string(settingResp.ValueJSON) == "0", nil
|
||||
}
|
||||
@@ -99,11 +99,24 @@ func (this *TaskAction) readTask(taskId int64) (ok bool) {
|
||||
})
|
||||
}
|
||||
|
||||
// 集群信息
|
||||
var clusterMap = maps.Map{
|
||||
"id": 0,
|
||||
"name": "",
|
||||
}
|
||||
if key.NodeCluster != nil {
|
||||
clusterMap = maps.Map{
|
||||
"id": key.NodeCluster.Id,
|
||||
"name": key.NodeCluster.Name,
|
||||
}
|
||||
}
|
||||
|
||||
keyMaps = append(keyMaps, maps.Map{
|
||||
"key": key.Key,
|
||||
"isDone": key.IsDone,
|
||||
"isDoing": key.IsDoing,
|
||||
"errors": errorMaps,
|
||||
"cluster": clusterMap,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
||||
"github.com/iwind/TeaGo/actions"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"strings"
|
||||
@@ -454,15 +453,16 @@ func (this *CreateAction) RunPost(params struct {
|
||||
|
||||
// 开启访问日志和Websocket
|
||||
if params.ServerType == serverconfigs.ServerTypeHTTPProxy {
|
||||
webConfig, err := dao.SharedHTTPWebDAO.FindWebConfigWithServerId(this.AdminContext(), serverId)
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
} else {
|
||||
// 访问日志
|
||||
if params.AccessLogIsOn {
|
||||
_, err = this.RPC().HTTPWebRPC().UpdateHTTPWebAccessLog(this.AdminContext(), &pb.UpdateHTTPWebAccessLogRequest{
|
||||
HttpWebId: webConfig.Id,
|
||||
AccessLogJSON: []byte(`{
|
||||
webConfig, findErr := dao.SharedHTTPWebDAO.FindWebConfigWithServerId(this.AdminContext(), serverId)
|
||||
if findErr != nil {
|
||||
this.ErrorPage(findErr)
|
||||
return
|
||||
}
|
||||
// 访问日志
|
||||
if params.AccessLogIsOn {
|
||||
_, err = this.RPC().HTTPWebRPC().UpdateHTTPWebAccessLog(this.AdminContext(), &pb.UpdateHTTPWebAccessLogRequest{
|
||||
HttpWebId: webConfig.Id,
|
||||
AccessLogJSON: []byte(`{
|
||||
"isPrior": false,
|
||||
"isOn": true,
|
||||
"fields": [1, 2, 6, 7],
|
||||
@@ -477,90 +477,94 @@ func (this *CreateAction) RunPost(params struct {
|
||||
|
||||
"firewallOnly": false
|
||||
}`),
|
||||
})
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// websocket
|
||||
if params.WebsocketIsOn {
|
||||
createWebSocketResp, err := this.RPC().HTTPWebsocketRPC().CreateHTTPWebsocket(this.AdminContext(), &pb.CreateHTTPWebsocketRequest{
|
||||
HandshakeTimeoutJSON: []byte(`{
|
||||
// websocket
|
||||
if params.WebsocketIsOn {
|
||||
createWebSocketResp, err := this.RPC().HTTPWebsocketRPC().CreateHTTPWebsocket(this.AdminContext(), &pb.CreateHTTPWebsocketRequest{
|
||||
HandshakeTimeoutJSON: []byte(`{
|
||||
"count": 30,
|
||||
"unit": "second"
|
||||
}`),
|
||||
AllowAllOrigins: true,
|
||||
AllowedOrigins: nil,
|
||||
RequestSameOrigin: true,
|
||||
RequestOrigin: "",
|
||||
})
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
} else {
|
||||
websocketId := createWebSocketResp.WebsocketId
|
||||
_, err = this.RPC().HTTPWebRPC().UpdateHTTPWebWebsocket(this.AdminContext(), &pb.UpdateHTTPWebWebsocketRequest{
|
||||
HttpWebId: webConfig.Id,
|
||||
WebsocketJSON: []byte(` {
|
||||
AllowAllOrigins: true,
|
||||
AllowedOrigins: nil,
|
||||
RequestSameOrigin: true,
|
||||
RequestOrigin: "",
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
|
||||
websocketId := createWebSocketResp.WebsocketId
|
||||
_, err = this.RPC().HTTPWebRPC().UpdateHTTPWebWebsocket(this.AdminContext(), &pb.UpdateHTTPWebWebsocketRequest{
|
||||
HttpWebId: webConfig.Id,
|
||||
WebsocketJSON: []byte(`{
|
||||
"isPrior": false,
|
||||
"isOn": true,
|
||||
"websocketId": ` + types.String(websocketId) + `
|
||||
}`),
|
||||
})
|
||||
if err != nil {
|
||||
logs.Error(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// cache
|
||||
if params.CacheIsOn {
|
||||
var cacheConfig = &serverconfigs.HTTPCacheConfig{
|
||||
IsPrior: false,
|
||||
IsOn: true,
|
||||
AddStatusHeader: true,
|
||||
PurgeIsOn: false,
|
||||
PurgeKey: "",
|
||||
CacheRefs: []*serverconfigs.HTTPCacheRef{},
|
||||
}
|
||||
cacheConfigJSON, err := json.Marshal(cacheConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
_, err = this.RPC().HTTPWebRPC().UpdateHTTPWebCache(this.AdminContext(), &pb.UpdateHTTPWebCacheRequest{
|
||||
HttpWebId: webConfig.Id,
|
||||
CacheJSON: cacheConfigJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
// cache
|
||||
if params.CacheIsOn {
|
||||
var cacheConfig = &serverconfigs.HTTPCacheConfig{
|
||||
IsPrior: false,
|
||||
IsOn: true,
|
||||
AddStatusHeader: true,
|
||||
PurgeIsOn: false,
|
||||
PurgeKey: "",
|
||||
CacheRefs: []*serverconfigs.HTTPCacheRef{},
|
||||
}
|
||||
|
||||
// waf
|
||||
if params.WafIsOn {
|
||||
var firewallRef = &firewallconfigs.HTTPFirewallRef{
|
||||
IsPrior: false,
|
||||
IsOn: true,
|
||||
FirewallPolicyId: 0,
|
||||
}
|
||||
firewallRefJSON, err := json.Marshal(firewallRef)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
_, err = this.RPC().HTTPWebRPC().UpdateHTTPWebFirewall(this.AdminContext(), &pb.UpdateHTTPWebFirewallRequest{
|
||||
HttpWebId: webConfig.Id,
|
||||
FirewallJSON: firewallRefJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
cacheConfigJSON, err := json.Marshal(cacheConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
_, err = this.RPC().HTTPWebRPC().UpdateHTTPWebCache(this.AdminContext(), &pb.UpdateHTTPWebCacheRequest{
|
||||
HttpWebId: webConfig.Id,
|
||||
CacheJSON: cacheConfigJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// remoteAddr
|
||||
// waf
|
||||
if params.WafIsOn {
|
||||
var firewallRef = &firewallconfigs.HTTPFirewallRef{
|
||||
IsPrior: false,
|
||||
IsOn: true,
|
||||
FirewallPolicyId: 0,
|
||||
}
|
||||
firewallRefJSON, err := json.Marshal(firewallRef)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
_, err = this.RPC().HTTPWebRPC().UpdateHTTPWebFirewall(this.AdminContext(), &pb.UpdateHTTPWebFirewallRequest{
|
||||
HttpWebId: webConfig.Id,
|
||||
FirewallJSON: firewallRefJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// remoteAddr
|
||||
{
|
||||
var remoteAddrConfig = &serverconfigs.HTTPRemoteAddrConfig{
|
||||
IsOn: true,
|
||||
Value: "${rawRemoteAddr}",
|
||||
@@ -583,26 +587,26 @@ func (this *CreateAction) RunPost(params struct {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 统计
|
||||
if params.StatIsOn {
|
||||
var statConfig = &serverconfigs.HTTPStatRef{
|
||||
IsPrior: false,
|
||||
IsOn: true,
|
||||
}
|
||||
statJSON, err := json.Marshal(statConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
_, err = this.RPC().HTTPWebRPC().UpdateHTTPWebStat(this.AdminContext(), &pb.UpdateHTTPWebStatRequest{
|
||||
HttpWebId: webConfig.Id,
|
||||
StatJSON: statJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
// 统计
|
||||
if params.StatIsOn {
|
||||
var statConfig = &serverconfigs.HTTPStatRef{
|
||||
IsPrior: false,
|
||||
IsOn: true,
|
||||
}
|
||||
statJSON, err := json.Marshal(statConfig)
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
_, err = this.RPC().HTTPWebRPC().UpdateHTTPWebStat(this.AdminContext(), &pb.UpdateHTTPWebStatRequest{
|
||||
HttpWebId: webConfig.Id,
|
||||
StatJSON: statJSON,
|
||||
})
|
||||
if err != nil {
|
||||
this.ErrorPage(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,6 @@ func (this *IndexAction) RunGet(params struct {
|
||||
if teaconst.IsPlus {
|
||||
this.RedirectURL("/servers/server/boards?serverId=" + strconv.FormatInt(params.ServerId, 10))
|
||||
} else {
|
||||
this.RedirectURL("/servers/server/stat?serverId=" + strconv.FormatInt(params.ServerId, 10))
|
||||
this.RedirectURL("/servers/server/settings?serverId=" + strconv.FormatInt(params.ServerId, 10))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,40 +26,44 @@ func (this *DetectDBAction) RunPost(params struct{}) {
|
||||
var localPassword = ""
|
||||
|
||||
// 本地的3306端口是否可以连接
|
||||
conn, err := net.DialTimeout("tcp", "127.0.0.1:3306", 3*time.Second)
|
||||
if err == nil {
|
||||
_ = conn.Close()
|
||||
localHost = "127.0.0.1"
|
||||
localPort = "3306"
|
||||
for _, tryingHost := range []string{"127.0.0.1", "localhost", "172.20.0.2"} {
|
||||
conn, dialErr := net.DialTimeout("tcp", tryingHost+":3306", 3*time.Second)
|
||||
if dialErr == nil {
|
||||
_ = conn.Close()
|
||||
localHost = tryingHost
|
||||
localPort = "3306"
|
||||
|
||||
var username = "root"
|
||||
var passwords = []string{"", "123456", "654321", "Aa_123456", "111111"}
|
||||
var username = "root"
|
||||
var passwords = []string{"", "123456", "654321", "Aa_123456", "111111"}
|
||||
|
||||
// 使用 foolish-mysql 安装的MySQL
|
||||
localGeneratedPasswordData, err := os.ReadFile("/usr/local/mysql/generated-password.txt")
|
||||
if err == nil {
|
||||
var localGeneratedPassword = strings.TrimSpace(string(localGeneratedPasswordData))
|
||||
if len(localGeneratedPassword) > 0 {
|
||||
passwords = append(passwords, localGeneratedPassword)
|
||||
}
|
||||
}
|
||||
|
||||
for _, pass := range passwords {
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: username + ":" + pass + "@tcp(" + configutils.QuoteIP(localHost) + ":" + localPort + ")/edges",
|
||||
Prefix: "",
|
||||
})
|
||||
// 使用 foolish-mysql 安装的MySQL
|
||||
localGeneratedPasswordData, err := os.ReadFile("/usr/local/mysql/generated-password.txt")
|
||||
if err == nil {
|
||||
err = db.Raw().Ping()
|
||||
_ = db.Close()
|
||||
|
||||
if err == nil || strings.Contains(err.Error(), "Error 1049") {
|
||||
localUsername = username
|
||||
localPassword = pass
|
||||
break
|
||||
var localGeneratedPassword = strings.TrimSpace(string(localGeneratedPasswordData))
|
||||
if len(localGeneratedPassword) > 0 {
|
||||
passwords = append(passwords, localGeneratedPassword)
|
||||
}
|
||||
}
|
||||
|
||||
for _, pass := range passwords {
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: username + ":" + pass + "@tcp(" + configutils.QuoteIP(localHost) + ":" + localPort + ")/edges",
|
||||
Prefix: "",
|
||||
})
|
||||
if err == nil {
|
||||
err = db.Raw().Ping()
|
||||
_ = db.Close()
|
||||
|
||||
if err == nil || strings.Contains(err.Error(), "Error 1049") {
|
||||
localUsername = username
|
||||
localPassword = pass
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
@@ -569,6 +570,12 @@ func (this *MySQLInstaller) installService(baseDir string) error {
|
||||
|
||||
this.log("registering systemd service ...")
|
||||
|
||||
var startCmd = "${BASE_DIR}/support-files/mysql.server start"
|
||||
bashPath, _ := exec.LookPath("bash")
|
||||
if len(bashPath) > 0 {
|
||||
startCmd = bashPath + " -c \"" + startCmd + "\""
|
||||
}
|
||||
|
||||
var desc = `### BEGIN INIT INFO
|
||||
# Provides: mysql
|
||||
# Required-Start: $local_fs $network $remote_fs
|
||||
@@ -589,7 +596,8 @@ After=network-online.target
|
||||
Type=simple
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
ExecStart=${BASE_DIR}/support-files/mysql.server start
|
||||
RemainAfterExit=yes
|
||||
ExecStart=` + startCmd + `
|
||||
ExecStop=${BASE_DIR}/support-files/mysql.server stop
|
||||
ExecRestart=${BASE_DIR}/support-files/mysql.server restart
|
||||
ExecStatus=${BASE_DIR}/support-files/mysql.server status
|
||||
|
||||
@@ -109,6 +109,19 @@ func NewUserMustAuth(module string) *userMustAuth {
|
||||
func (this *userMustAuth) BeforeAction(actionPtr actions.ActionWrapper, paramName string) (goNext bool) {
|
||||
var action = actionPtr.Object()
|
||||
|
||||
// 检查请求是否合法
|
||||
if isEvilRequest(action.Request) {
|
||||
action.ResponseWriter.WriteHeader(http.StatusForbidden)
|
||||
return false
|
||||
}
|
||||
|
||||
// 检测注入
|
||||
if !safeFilterRequest(action.Request) {
|
||||
action.ResponseWriter.WriteHeader(http.StatusForbidden)
|
||||
_, _ = action.ResponseWriter.Write([]byte("Denied By WAF"))
|
||||
return false
|
||||
}
|
||||
|
||||
// 恢复模式
|
||||
if teaconst.IsRecoverMode {
|
||||
action.RedirectURL("/recover")
|
||||
|
||||
@@ -21,6 +21,19 @@ func (this *UserShouldAuth) BeforeAction(actionPtr actions.ActionWrapper, paramN
|
||||
|
||||
this.action = actionPtr.Object()
|
||||
|
||||
// 检查请求是否合法
|
||||
if isEvilRequest(this.action.Request) {
|
||||
this.action.ResponseWriter.WriteHeader(http.StatusForbidden)
|
||||
return false
|
||||
}
|
||||
|
||||
// 检测注入
|
||||
if !safeFilterRequest(this.action.Request) {
|
||||
this.action.ResponseWriter.WriteHeader(http.StatusForbidden)
|
||||
_, _ = this.action.ResponseWriter.Write([]byte("Denied By WAF"))
|
||||
return false
|
||||
}
|
||||
|
||||
// 安全相关
|
||||
var action = this.action
|
||||
securityConfig, _ := configloaders.LoadSecurityConfig()
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/events"
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
@@ -155,3 +157,9 @@ func checkRequestSecurity(securityConfig *systemconfigs.SecurityConfig, req *htt
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// 检查是否为禁止的请求
|
||||
func isEvilRequest(req *http.Request) bool {
|
||||
var headersJSON, _ = json.Marshal(req.Header)
|
||||
return bytes.Contains(headersJSON, []byte("fofa."))
|
||||
}
|
||||
|
||||
14
internal/web/helpers/utils_gcc.go
Normal file
14
internal/web/helpers/utils_gcc.go
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build gcc
|
||||
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/waf/injectionutils"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// filter request
|
||||
func safeFilterRequest(req *http.Request) bool {
|
||||
return !injectionutils.DetectXSS(req.RequestURI, false) && !injectionutils.DetectSQLInjection(req.RequestURI, false)
|
||||
}
|
||||
13
internal/web/helpers/utils_none_gcc.go
Normal file
13
internal/web/helpers/utils_none_gcc.go
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build !gcc
|
||||
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// filter request
|
||||
func safeFilterRequest(req *http.Request) bool {
|
||||
return true
|
||||
}
|
||||
@@ -353,7 +353,7 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
@change="change"
|
||||
ref="comboBox">
|
||||
</combo-box>
|
||||
</div>`}),Vue.component("ns-routes-selector",{props:["v-routes","name"],mounted:function(){let t=this;Tea.action("/ns/routes/options").post().success(function(e){t.routes=e.data.routes})},data:function(){let e=this.vRoutes,t=(null==e&&(e=[]),this.name);return{routeCode:"default",inputName:t="string"==typeof t&&0!=t.length?t:"routeCodes",routes:[],isAdding:!1,routeType:"default",selectedRoutes:e}},watch:{routeType:function(t){this.routeCode="";let i=this;this.routes.forEach(function(e){e.type==t&&0==i.routeCode.length&&(i.routeCode=e.code)})}},methods:{add:function(){this.isAdding=!0,this.routeType="default",this.routeCode="default",this.$emit("add")},cancel:function(){this.isAdding=!1,this.$emit("cancel")},confirm:function(){if(0!=this.routeCode.length){let t=this;this.routes.forEach(function(e){e.code==t.routeCode&&t.selectedRoutes.push(e)}),this.$emit("change",this.selectedRoutes),this.cancel()}},remove:function(e){this.selectedRoutes.$remove(e),this.$emit("change",this.selectedRoutes)}},template:`<div>
|
||||
</div>`}),Vue.component("ns-routes-selector",{props:["v-routes","name"],mounted:function(){let s=this;Tea.action("/ns/routes/options").post().success(function(e){s.routes=e.data.routes;let t={};if(null!=e.data.provinces&&0<e.data.provinces.length)for(const n of e.data.provinces){var i=n.countryCode;void 0===t[i]&&(t[i]=[]),t[i].push({name:n.name,code:n.code})}s.provinces=t})},data:function(){let e=this.vRoutes,t=(null==e&&(e=[]),this.name);return{routeCode:"default",inputName:t="string"==typeof t&&0!=t.length?t:"routeCodes",routes:[],provinces:{},provinceRouteCode:"",isAdding:!1,routeType:"default",selectedRoutes:e}},watch:{routeType:function(t){this.routeCode="";let i=this;this.routes.forEach(function(e){e.type==t&&0==i.routeCode.length&&(i.routeCode=e.code)})}},methods:{add:function(){this.isAdding=!0,this.routeType="default",this.routeCode="default",this.provinceRouteCode="",this.$emit("add")},cancel:function(){this.isAdding=!1,this.$emit("cancel")},confirm:function(){if(0!=this.routeCode.length){let e=null;for(const t of this.routes)if(t.code==this.routeCode){e=t;break}if(null!=e){if(0<this.provinceRouteCode.length&&null!=this.provinces[this.routeCode])for(const i of this.provinces[this.routeCode])if(i.code==this.provinceRouteCode){e={name:e.name+"-"+i.name,code:i.code};break}this.selectedRoutes.push(e)}this.$emit("change",this.selectedRoutes),this.cancel()}},remove:function(e){this.selectedRoutes.$remove(e),this.$emit("change",this.selectedRoutes)}},template:`<div>
|
||||
<div v-show="selectedRoutes.length > 0">
|
||||
<div class="ui label basic text small" v-for="(route, index) in selectedRoutes" style="margin-bottom: 0.3em">
|
||||
<input type="hidden" :name="inputName" :value="route.code"/>
|
||||
@@ -362,31 +362,44 @@ Vue.component("traffic-map-box",{props:["v-stats","v-is-attack"],mounted:functio
|
||||
<div class="ui divider"></div>
|
||||
</div>
|
||||
<div v-if="isAdding" style="margin-bottom: 1em">
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<select class="ui dropdown" v-model="routeType">
|
||||
<option value="default">[默认线路]</option>
|
||||
<option value="user">自定义线路</option>
|
||||
<option value="isp">运营商</option>
|
||||
<option value="china">中国省市</option>
|
||||
<option value="world">全球国家地区</option>
|
||||
<option value="agent">搜索引擎</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="ui field">
|
||||
<select class="ui dropdown" v-model="routeCode" style="width: 10em">
|
||||
<option v-for="route in routes" :value="route.code" v-if="route.type == routeType">{{route.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="ui field">
|
||||
<button type="button" class="ui button tiny" @click.prevent="confirm">确定</button>
|
||||
<a href="" title="取消" @click.prevent="cancel"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">选择类型 *</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" v-model="routeType">
|
||||
<option value="default">[默认线路]</option>
|
||||
<option value="user">自定义线路</option>
|
||||
<option value="isp">运营商</option>
|
||||
<option value="china">中国省市</option>
|
||||
<option value="world">全球国家地区</option>
|
||||
<option value="agent">搜索引擎</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>选择线路 *</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" v-model="routeCode">
|
||||
<option v-for="route in routes" :value="route.code" v-if="route.type == routeType">{{route.name}}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="routeCode.length > 0 && provinces[routeCode] != null">
|
||||
<td>选择省/州</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" v-model="provinceRouteCode">
|
||||
<option value="">[全域]</option>
|
||||
<option v-for="province in provinces[routeCode]" :value="province.code">{{province.name}}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div>
|
||||
<button type="button" class="ui button tiny" @click.prevent="confirm">确定</button>
|
||||
<a href="" title="取消" @click.prevent="cancel">取消</a>
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui button tiny" type="button" @click.prevent="add">+</button>
|
||||
<button class="ui button tiny" type="button" @click.prevent="add" v-if="!isAdding">+</button>
|
||||
</div>`}),Vue.component("ns-recursion-config-box",{props:["v-recursion-config"],data:function(){let e=this.vRecursionConfig;return null==(e=null==e?{isOn:!1,hosts:[],allowDomains:[],denyDomains:[],useLocalHosts:!1}:e).hosts&&(e.hosts=[]),null==e.allowDomains&&(e.allowDomains=[]),null==e.denyDomains&&(e.denyDomains=[]),{config:e,hostIsAdding:!1,host:"",updatingHost:null}},methods:{changeHosts:function(e){this.config.hosts=e},changeAllowDomains:function(e){this.config.allowDomains=e},changeDenyDomains:function(e){this.config.denyDomains=e},removeHost:function(e){this.config.hosts.$remove(e)},addHost:function(){var t;this.updatingHost=null,this.host="",this.hostIsAdding=!this.hostIsAdding,this.hostIsAdding&&(t=this,setTimeout(function(){let e=t.$refs.hostRef;null!=e&&e.focus()},200))},updateHost:function(e){var t;this.updatingHost=e,this.host=e.host,this.hostIsAdding=!this.hostIsAdding,this.hostIsAdding&&(t=this,setTimeout(function(){let e=t.$refs.hostRef;null!=e&&e.focus()},200))},confirmHost:function(){0==this.host.length?teaweb.warn("请输入DNS地址"):(this.hostIsAdding=!1,null==this.updatingHost?this.config.hosts.push({host:this.host}):this.updatingHost.host=this.host)},cancelHost:function(){this.hostIsAdding=!1}},template:`<div>
|
||||
<input type="hidden" name="recursionJSON" :value="JSON.stringify(config)"/>
|
||||
<table class="ui table definition selectable">
|
||||
@@ -6555,7 +6568,7 @@ example2.com
|
||||
</div>
|
||||
</div>`}),Vue.component("digit-input",{props:["value","maxlength","size","min","max","required","placeholder"],mounted:function(){let e=this;setTimeout(function(){e.check()})},data:function(){let e=this.maxlength,t=(null==e&&(e=20),this.size);return null==t&&(t=6),{realValue:this.value,realMaxLength:e,realSize:t,isValid:!0}},watch:{realValue:function(e){this.notifyChange()}},methods:{notifyChange:function(){let e=parseInt(this.realValue.toString(),10);isNaN(e)&&(e=0),this.check(),this.$emit("input",e)},check:function(){var e;null!=this.realValue&&(e=this.realValue.toString(),/^\d+$/.test(e)?(e=parseInt(e,10),isNaN(e)?this.isValid=!1:this.required?this.isValid=(null==this.min||this.min<=e)&&(null==this.max||this.max>=e):this.isValid=0==e||(null==this.min||this.min<=e)&&(null==this.max||this.max>=e)):this.isValid=!1)}},template:'<input type="text" v-model="realValue" :maxlength="realMaxLength" :size="realSize" :class="{error: !this.isValid}" :placeholder="placeholder" autocomplete="off"/>'}),Vue.component("keyword",{props:["v-word"],data:function(){let e=this.vWord;e=null==e?"":(e=(e=(e=(e=(e=(e=(e=(e=(e=e.replace(/\)/g,"\\)")).replace(/\(/g,"\\(")).replace(/\+/g,"\\+")).replace(/\^/g,"\\^")).replace(/\$/g,"\\$")).replace(/\?/g,"\\?")).replace(/\*/g,"\\*")).replace(/\[/g,"\\[")).replace(/{/g,"\\{")).replace(/\./g,"\\.");let t=this.$slots.default[0].text;if(0<e.length){let i=this,n=[],s=0;t=t.replaceAll(new RegExp("("+e+")","ig"),function(e){s++;var e='<span style="border: 1px #ccc dashed; color: #ef4d58">'+i.encodeHTML(e)+"</span>",t="$TMP__KEY__"+s.toString()+"$";return n.push([t,e]),t}),t=this.encodeHTML(t),n.forEach(function(e){t=t.replace(e[0],e[1])})}else t=this.encodeHTML(t);return{word:e,text:t}},methods:{encodeHTML:function(e){return e=(e=(e=(e=e.replace(/&/g,"&")).replace(/</g,"<")).replace(/>/g,">")).replace(/"/g,""")}},template:'<span><span style="display: none"><slot></slot></span><span v-html="text"></span></span>'}),Vue.component("bits-var",{props:["v-bits"],data:function(){let e=this.vBits;return"number"!=typeof e&&(e=0),{format:teaweb.splitFormat(teaweb.formatBits(e))}},template:`<var class="normal">
|
||||
<span>{{format[0]}}</span>{{format[1]}}
|
||||
</var>`}),Vue.component("chart-columns-grid",{props:[],mounted:function(){this.columns=this.calculateColumns();let e=this;window.addEventListener("resize",function(){e.columns=e.calculateColumns()})},updated:function(){var e=this.$el.getElementsByClassName("column").length;e!=this.totalElements&&(this.totalElements=e,this.calculateColumns())},data:function(){return{columns:"four",totalElements:0}},methods:{calculateColumns:function(){var e=window.innerWidth;let i=Math.floor(e/500);0==i&&(i=1);var n=this.$el.getElementsByClassName("column");if(0==n.length)return"one";e=n.length;i>e&&(i=e);for(let t=0;t<n.length;t++){let e=n[t];e.className=e.className.replace("with-border",""),t%i!=i-1&&t!=n.length-1||(e.className+=" with-border")}switch(i){case 1:return"one";case 2:return"two";case 3:return"three";case 4:return"four";case 5:return"five";case 6:return"six";case 7:return"seven";case 8:return"eight";case 9:return"nine";default:return"ten"}}},template:`<div :class="'ui ' + columns + ' columns grid chart-grid'">
|
||||
</var>`}),Vue.component("mask-warning",{template:'<span class="red">为了安全起见,此项数据保存后将不允许在界面查看完整明文,为避免忘记,请自行记录原始数据。</span>'}),Vue.component("chart-columns-grid",{props:[],mounted:function(){this.columns=this.calculateColumns();let e=this;window.addEventListener("resize",function(){e.columns=e.calculateColumns()})},updated:function(){var e=this.$el.getElementsByClassName("column").length;e!=this.totalElements&&(this.totalElements=e,this.calculateColumns())},data:function(){return{columns:"four",totalElements:0}},methods:{calculateColumns:function(){var e=window.innerWidth;let i=Math.floor(e/500);0==i&&(i=1);var n=this.$el.getElementsByClassName("column");if(0==n.length)return"one";e=n.length;i>e&&(i=e);for(let t=0;t<n.length;t++){let e=n[t];e.className=e.className.replace("with-border",""),t%i!=i-1&&t!=n.length-1||(e.className+=" with-border")}switch(i){case 1:return"one";case 2:return"two";case 3:return"three";case 4:return"four";case 5:return"five";case 6:return"six";case 7:return"seven";case 8:return"eight";case 9:return"nine";default:return"ten"}}},template:`<div :class="'ui ' + columns + ' columns grid chart-grid'">
|
||||
<slot></slot>
|
||||
</div>`}),Vue.component("bytes-var",{props:["v-bytes"],data:function(){let e=this.vBytes;return"number"!=typeof e&&(e=0),{format:teaweb.splitFormat(teaweb.formatBytes(e))}},template:`<var class="normal">
|
||||
<span>{{format[0]}}</span>{{format[1]}}
|
||||
@@ -6685,7 +6698,7 @@ example2.com
|
||||
<div>
|
||||
<div v-for="(address, index) in ipAddresses" class="ui label tiny basic">
|
||||
<span v-if="isIPv6(address.ip)" class="grey">[IPv6]</span> {{address.ip}}
|
||||
<span class="small grey" v-if="address.name.length > 0">({{address.name}}<span v-if="!address.canAccess">,不可访问</span>)</span>
|
||||
<span class="small grey" v-if="address.name.length > 0">(备注:{{address.name}}<span v-if="!address.canAccess">,不可访问</span>)</span>
|
||||
<span class="small grey" v-if="address.name.length == 0 && !address.canAccess">(不可访问)</span>
|
||||
<span class="small red" v-if="!address.isOn" title="未启用">[off]</span>
|
||||
<span class="small red" v-if="!address.isUp" title="已下线">[down]</span>
|
||||
|
||||
@@ -1230,6 +1230,22 @@ Vue.component("ns-routes-selector", {
|
||||
.post()
|
||||
.success(function (resp) {
|
||||
that.routes = resp.data.routes
|
||||
|
||||
// provinces
|
||||
let provinces = {}
|
||||
if (resp.data.provinces != null && resp.data.provinces.length > 0) {
|
||||
for (const province of resp.data.provinces) {
|
||||
let countryCode = province.countryCode
|
||||
if (typeof provinces[countryCode] == "undefined") {
|
||||
provinces[countryCode] = []
|
||||
}
|
||||
provinces[countryCode].push({
|
||||
name: province.name,
|
||||
code: province.code
|
||||
})
|
||||
}
|
||||
}
|
||||
that.provinces = provinces
|
||||
})
|
||||
},
|
||||
data: function () {
|
||||
@@ -1247,6 +1263,10 @@ Vue.component("ns-routes-selector", {
|
||||
routeCode: "default",
|
||||
inputName: inputName,
|
||||
routes: [],
|
||||
|
||||
provinces: {}, // country code => [ province1, province2, ... ]
|
||||
provinceRouteCode: "",
|
||||
|
||||
isAdding: false,
|
||||
routeType: "default",
|
||||
selectedRoutes: selectedRoutes,
|
||||
@@ -1268,6 +1288,7 @@ Vue.component("ns-routes-selector", {
|
||||
this.isAdding = true
|
||||
this.routeType = "default"
|
||||
this.routeCode = "default"
|
||||
this.provinceRouteCode = ""
|
||||
this.$emit("add")
|
||||
},
|
||||
cancel: function () {
|
||||
@@ -1280,11 +1301,33 @@ Vue.component("ns-routes-selector", {
|
||||
}
|
||||
|
||||
let that = this
|
||||
this.routes.forEach(function (v) {
|
||||
if (v.code == that.routeCode) {
|
||||
that.selectedRoutes.push(v)
|
||||
|
||||
// route
|
||||
let selectedRoute = null
|
||||
for (const route of this.routes) {
|
||||
if (route.code == this.routeCode) {
|
||||
selectedRoute = route
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (selectedRoute != null) {
|
||||
// province route
|
||||
if (this.provinceRouteCode.length > 0 && this.provinces[this.routeCode] != null) {
|
||||
for (const province of this.provinces[this.routeCode]) {
|
||||
if (province.code == this.provinceRouteCode) {
|
||||
selectedRoute = {
|
||||
name: selectedRoute.name + "-" + province.name,
|
||||
code: province.code
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
that.selectedRoutes.push(selectedRoute)
|
||||
}
|
||||
|
||||
this.$emit("change", this.selectedRoutes)
|
||||
this.cancel()
|
||||
},
|
||||
@@ -1303,31 +1346,44 @@ Vue.component("ns-routes-selector", {
|
||||
<div class="ui divider"></div>
|
||||
</div>
|
||||
<div v-if="isAdding" style="margin-bottom: 1em">
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<select class="ui dropdown" v-model="routeType">
|
||||
<option value="default">[默认线路]</option>
|
||||
<option value="user">自定义线路</option>
|
||||
<option value="isp">运营商</option>
|
||||
<option value="china">中国省市</option>
|
||||
<option value="world">全球国家地区</option>
|
||||
<option value="agent">搜索引擎</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="ui field">
|
||||
<select class="ui dropdown" v-model="routeCode" style="width: 10em">
|
||||
<option v-for="route in routes" :value="route.code" v-if="route.type == routeType">{{route.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="ui field">
|
||||
<button type="button" class="ui button tiny" @click.prevent="confirm">确定</button>
|
||||
<a href="" title="取消" @click.prevent="cancel"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">选择类型 *</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" v-model="routeType">
|
||||
<option value="default">[默认线路]</option>
|
||||
<option value="user">自定义线路</option>
|
||||
<option value="isp">运营商</option>
|
||||
<option value="china">中国省市</option>
|
||||
<option value="world">全球国家地区</option>
|
||||
<option value="agent">搜索引擎</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>选择线路 *</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" v-model="routeCode">
|
||||
<option v-for="route in routes" :value="route.code" v-if="route.type == routeType">{{route.name}}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="routeCode.length > 0 && provinces[routeCode] != null">
|
||||
<td>选择省/州</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" v-model="provinceRouteCode">
|
||||
<option value="">[全域]</option>
|
||||
<option v-for="province in provinces[routeCode]" :value="province.code">{{province.name}}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div>
|
||||
<button type="button" class="ui button tiny" @click.prevent="confirm">确定</button>
|
||||
<a href="" title="取消" @click.prevent="cancel">取消</a>
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui button tiny" type="button" @click.prevent="add">+</button>
|
||||
<button class="ui button tiny" type="button" @click.prevent="add" v-if="!isAdding">+</button>
|
||||
</div>`
|
||||
})
|
||||
|
||||
@@ -19525,6 +19581,10 @@ Vue.component("bits-var", {
|
||||
</var>`
|
||||
})
|
||||
|
||||
Vue.component("mask-warning", {
|
||||
template: `<span class="red">为了安全起见,此项数据保存后将不允许在界面查看完整明文,为避免忘记,请自行记录原始数据。</span>`
|
||||
})
|
||||
|
||||
Vue.component("chart-columns-grid", {
|
||||
props: [],
|
||||
mounted: function () {
|
||||
@@ -20525,7 +20585,7 @@ Vue.component("node-ip-addresses-box", {
|
||||
<div>
|
||||
<div v-for="(address, index) in ipAddresses" class="ui label tiny basic">
|
||||
<span v-if="isIPv6(address.ip)" class="grey">[IPv6]</span> {{address.ip}}
|
||||
<span class="small grey" v-if="address.name.length > 0">({{address.name}}<span v-if="!address.canAccess">,不可访问</span>)</span>
|
||||
<span class="small grey" v-if="address.name.length > 0">(备注:{{address.name}}<span v-if="!address.canAccess">,不可访问</span>)</span>
|
||||
<span class="small grey" v-if="address.name.length == 0 && !address.canAccess">(不可访问)</span>
|
||||
<span class="small red" v-if="!address.isOn" title="未启用">[off]</span>
|
||||
<span class="small red" v-if="!address.isUp" title="已下线">[down]</span>
|
||||
|
||||
3
web/public/js/components/common/mask-warning.js
Normal file
3
web/public/js/components/common/mask-warning.js
Normal file
@@ -0,0 +1,3 @@
|
||||
Vue.component("mask-warning", {
|
||||
template: `<span class="red">为了安全起见,此项数据保存后将不允许在界面查看完整明文,为避免忘记,请自行记录原始数据。</span>`
|
||||
})
|
||||
@@ -58,7 +58,7 @@ Vue.component("node-ip-addresses-box", {
|
||||
<div>
|
||||
<div v-for="(address, index) in ipAddresses" class="ui label tiny basic">
|
||||
<span v-if="isIPv6(address.ip)" class="grey">[IPv6]</span> {{address.ip}}
|
||||
<span class="small grey" v-if="address.name.length > 0">({{address.name}}<span v-if="!address.canAccess">,不可访问</span>)</span>
|
||||
<span class="small grey" v-if="address.name.length > 0">(备注:{{address.name}}<span v-if="!address.canAccess">,不可访问</span>)</span>
|
||||
<span class="small grey" v-if="address.name.length == 0 && !address.canAccess">(不可访问)</span>
|
||||
<span class="small red" v-if="!address.isOn" title="未启用">[off]</span>
|
||||
<span class="small red" v-if="!address.isUp" title="已下线">[down]</span>
|
||||
|
||||
@@ -7,6 +7,22 @@ Vue.component("ns-routes-selector", {
|
||||
.post()
|
||||
.success(function (resp) {
|
||||
that.routes = resp.data.routes
|
||||
|
||||
// provinces
|
||||
let provinces = {}
|
||||
if (resp.data.provinces != null && resp.data.provinces.length > 0) {
|
||||
for (const province of resp.data.provinces) {
|
||||
let countryCode = province.countryCode
|
||||
if (typeof provinces[countryCode] == "undefined") {
|
||||
provinces[countryCode] = []
|
||||
}
|
||||
provinces[countryCode].push({
|
||||
name: province.name,
|
||||
code: province.code
|
||||
})
|
||||
}
|
||||
}
|
||||
that.provinces = provinces
|
||||
})
|
||||
},
|
||||
data: function () {
|
||||
@@ -24,6 +40,10 @@ Vue.component("ns-routes-selector", {
|
||||
routeCode: "default",
|
||||
inputName: inputName,
|
||||
routes: [],
|
||||
|
||||
provinces: {}, // country code => [ province1, province2, ... ]
|
||||
provinceRouteCode: "",
|
||||
|
||||
isAdding: false,
|
||||
routeType: "default",
|
||||
selectedRoutes: selectedRoutes,
|
||||
@@ -45,6 +65,7 @@ Vue.component("ns-routes-selector", {
|
||||
this.isAdding = true
|
||||
this.routeType = "default"
|
||||
this.routeCode = "default"
|
||||
this.provinceRouteCode = ""
|
||||
this.$emit("add")
|
||||
},
|
||||
cancel: function () {
|
||||
@@ -57,11 +78,33 @@ Vue.component("ns-routes-selector", {
|
||||
}
|
||||
|
||||
let that = this
|
||||
this.routes.forEach(function (v) {
|
||||
if (v.code == that.routeCode) {
|
||||
that.selectedRoutes.push(v)
|
||||
|
||||
// route
|
||||
let selectedRoute = null
|
||||
for (const route of this.routes) {
|
||||
if (route.code == this.routeCode) {
|
||||
selectedRoute = route
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (selectedRoute != null) {
|
||||
// province route
|
||||
if (this.provinceRouteCode.length > 0 && this.provinces[this.routeCode] != null) {
|
||||
for (const province of this.provinces[this.routeCode]) {
|
||||
if (province.code == this.provinceRouteCode) {
|
||||
selectedRoute = {
|
||||
name: selectedRoute.name + "-" + province.name,
|
||||
code: province.code
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
that.selectedRoutes.push(selectedRoute)
|
||||
}
|
||||
|
||||
this.$emit("change", this.selectedRoutes)
|
||||
this.cancel()
|
||||
},
|
||||
@@ -80,30 +123,43 @@ Vue.component("ns-routes-selector", {
|
||||
<div class="ui divider"></div>
|
||||
</div>
|
||||
<div v-if="isAdding" style="margin-bottom: 1em">
|
||||
<div class="ui fields inline">
|
||||
<div class="ui field">
|
||||
<select class="ui dropdown" v-model="routeType">
|
||||
<option value="default">[默认线路]</option>
|
||||
<option value="user">自定义线路</option>
|
||||
<option value="isp">运营商</option>
|
||||
<option value="china">中国省市</option>
|
||||
<option value="world">全球国家地区</option>
|
||||
<option value="agent">搜索引擎</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="ui field">
|
||||
<select class="ui dropdown" v-model="routeCode" style="width: 10em">
|
||||
<option v-for="route in routes" :value="route.code" v-if="route.type == routeType">{{route.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="ui field">
|
||||
<button type="button" class="ui button tiny" @click.prevent="confirm">确定</button>
|
||||
<a href="" title="取消" @click.prevent="cancel"><i class="icon remove small"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
<table class="ui table">
|
||||
<tr>
|
||||
<td class="title">选择类型 *</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" v-model="routeType">
|
||||
<option value="default">[默认线路]</option>
|
||||
<option value="user">自定义线路</option>
|
||||
<option value="isp">运营商</option>
|
||||
<option value="china">中国省市</option>
|
||||
<option value="world">全球国家地区</option>
|
||||
<option value="agent">搜索引擎</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>选择线路 *</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" v-model="routeCode">
|
||||
<option v-for="route in routes" :value="route.code" v-if="route.type == routeType">{{route.name}}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="routeCode.length > 0 && provinces[routeCode] != null">
|
||||
<td>选择省/州</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" v-model="provinceRouteCode">
|
||||
<option value="">[全域]</option>
|
||||
<option v-for="province in provinces[routeCode]" :value="province.code">{{province.name}}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div>
|
||||
<button type="button" class="ui button tiny" @click.prevent="confirm">确定</button>
|
||||
<a href="" title="取消" @click.prevent="cancel">取消</a>
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui button tiny" type="button" @click.prevent="add">+</button>
|
||||
<button class="ui button tiny" type="button" @click.prevent="add" v-if="!isAdding">+</button>
|
||||
</div>`
|
||||
})
|
||||
@@ -283,9 +283,11 @@ p.margin {
|
||||
width: 10em;
|
||||
}
|
||||
/** 主菜单 **/
|
||||
.main-menu {
|
||||
width: 8em !important;
|
||||
}
|
||||
.main-menu .ui.menu {
|
||||
width: 9.5em !important;
|
||||
border-radius: 0 !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
.main-menu .ui.menu .item.separator {
|
||||
border-top: 1px rgba(0, 0, 0, 0.2) solid;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -132,10 +132,10 @@ div.margin, p.margin {
|
||||
|
||||
/** 主菜单 **/
|
||||
.main-menu {
|
||||
.ui.menu {
|
||||
width: 9.5em !important;
|
||||
border-radius: 0 !important;
|
||||
width: 8em !important;
|
||||
|
||||
.ui.menu {
|
||||
width: 100% !important;
|
||||
|
||||
// menu
|
||||
.item.separator {
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<div v-for="(address, index) in node.ipAddresses" class="ui label small basic">
|
||||
<span v-if="address.ip.indexOf(':') > -1" class="grey">[IPv6]</span> {{address.ip}}
|
||||
<span class="small red" v-if="address.originIP != null && address.originIP.length > 0 && address.originIP != address.ip">(原:{{address.originIP}})</span>
|
||||
<span class="small grey" v-if="address.name.length > 0">({{address.name}}<span v-if="!address.canAccess">,不可访问</span>)</span>
|
||||
<span class="small grey" v-if="address.name.length > 0">(备注:{{address.name}}<span v-if="!address.canAccess">,不可访问</span>)</span>
|
||||
<span class="small grey" v-if="address.name.length == 0 && !address.canAccess">(不可访问)</span>
|
||||
<span class="small red" v-if="!address.isOn">[off]</span>
|
||||
<span class="small red" v-if="!address.isUp">[down]</span>
|
||||
|
||||
@@ -27,8 +27,11 @@
|
||||
<td class="color-border">处理未绑定域名方式</td>
|
||||
<td>
|
||||
<radio name="httpAllDomainMismatchActionCode" :v-value="'page'" v-model="httpAllDomainMismatchActionCode">显示提示页面</radio>
|
||||
<radio name="httpAllDomainMismatchActionCode" :v-value="'close'" v-model="httpAllDomainMismatchActionCode">关闭连接</radio>
|
||||
<radio name="httpAllDomainMismatchActionCode" :v-value="'redirect'" v-model="httpAllDomainMismatchActionCode">跳转到网址</radio>
|
||||
<radio name="httpAllDomainMismatchActionCode" :v-value="'close'" v-model="httpAllDomainMismatchActionCode">关闭连接</radio>
|
||||
|
||||
<p class="comment" v-if="httpAllDomainMismatchActionCode == 'page'">显示提示内容。</p>
|
||||
<p class="comment" v-if="httpAllDomainMismatchActionCode == 'redirect'">跳转到一个特定URL。</p>
|
||||
<p class="comment" v-if="httpAllDomainMismatchActionCode == 'close'">直接关闭网络连接,不提示任何内容。</p>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -48,6 +51,14 @@
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr v-show="config.httpAll.matchDomainStrictly && httpAllDomainMismatchActionCode == 'redirect'">
|
||||
<td class="color-border">跳转目标网址URL *</td>
|
||||
<td>
|
||||
<input type="text" placeholder="https://..." name="httpAllDomainMismatchActionRedirectURL" v-model="httpAllDomainMismatchActionRedirectURL"/>
|
||||
<p class="comment">必须以http://或者https://开头,支持使用变量。</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr v-show="config.httpAll.matchDomainStrictly">
|
||||
<td>允许例外的域名</td>
|
||||
<td>
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<tr>
|
||||
<td>SSH密码</td>
|
||||
<td><input type="password" name="password" maxlength="100"/>
|
||||
<p class="comment">SSH登录用户密码。</p> </td>
|
||||
<p class="comment">SSH登录用户密码。<mask-warning></mask-warning></p> </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
<td>RSA私钥 *</td>
|
||||
<td>
|
||||
<file-textarea name="privateKey" spellcheck="false" placeholder="填入RSA私钥内容或者拖动私钥文件到当前框中"></file-textarea>
|
||||
<p class="comment">用来生成登录SSH公钥的私钥。</p>
|
||||
<p class="comment">用来生成登录SSH公钥的私钥。<mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
4
web/views/@default/clusters/grants/grant.css
Normal file
4
web/views/@default/clusters/grants/grant.css
Normal file
@@ -0,0 +1,4 @@
|
||||
.CodeMirror-wrap pre {
|
||||
word-break: break-all !important;
|
||||
}
|
||||
/*# sourceMappingURL=grant.css.map */
|
||||
1
web/views/@default/clusters/grants/grant.css.map
Normal file
1
web/views/@default/clusters/grants/grant.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["grant.less"],"names":[],"mappings":"AAAA,gBAAiB;EAChB,qBAAA","file":"grant.css"}
|
||||
3
web/views/@default/clusters/grants/grant.less
Normal file
3
web/views/@default/clusters/grants/grant.less
Normal file
@@ -0,0 +1,3 @@
|
||||
.CodeMirror-wrap pre {
|
||||
word-break: break-all !important;
|
||||
}
|
||||
@@ -34,7 +34,7 @@
|
||||
<tr>
|
||||
<td>SSH密码</td>
|
||||
<td><input type="password" name="password" maxlength="100" v-model="grant.password"/>
|
||||
<p class="comment">SSH登录用户密码。</p> </td>
|
||||
<p class="comment">SSH登录用户密码。<mask-warning></mask-warning></p> </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
<td>RSA私钥 *</td>
|
||||
<td>
|
||||
<file-textarea name="privateKey" v-model="grant.privateKey" spellcheck="false" placeholder="填入RSA私钥内容或者拖动私钥文件到当前框中"></file-textarea>
|
||||
<p class="comment">用来生成登录SSH公钥的私钥</p>
|
||||
<p class="comment">用来生成登录SSH公钥的私钥。<mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<td>SecretKey *</td>
|
||||
<td>
|
||||
<input type="text" name="paramDNSPodAccessKeySecret" maxlength="100"/>
|
||||
<p class="comment">在DNSPod控制台“账号中心--API密钥”中获取。</p>
|
||||
<p class="comment">在DNSPod控制台“账号中心--API密钥”中获取。<mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="paramDNSPodAPIType == 'dnsPodToken'">
|
||||
@@ -61,7 +61,7 @@
|
||||
<td>密钥Token *</td>
|
||||
<td>
|
||||
<input type="text" name="paramDNSPodToken" maxlength="100" spellcheck="false"/>
|
||||
<p class="comment">在DNSPod控制台“账号中心--API密钥--DNSPod Token”中获取。</p>
|
||||
<p class="comment">在DNSPod控制台“账号中心--API密钥--DNSPod Token”中获取。<mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="paramDNSPodAPIType == 'dnsPodToken'">
|
||||
@@ -88,7 +88,7 @@
|
||||
<td>AccessKeySecret *</td>
|
||||
<td>
|
||||
<input type="text" name="paramAliDNSAccessKeySecret" maxlength="100" spellcheck="false"/>
|
||||
<p class="comment">登录阿里云控制台 -- 在"访问控制"中创建和获取。</p>
|
||||
<p class="comment">登录阿里云控制台 -- 在"访问控制"中创建和获取。<mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -113,7 +113,7 @@
|
||||
<td>AccessKeySecret *</td>
|
||||
<td>
|
||||
<input type="text" name="paramHuaweiAccessKeySecret" maxlength="100" spellcheck="false"/>
|
||||
<p class="comment">登录华为云控制台 -- 在"我的凭证 -- 访问密钥"中创建和获取。</p>
|
||||
<p class="comment">登录华为云控制台 -- 在"我的凭证 -- 访问密钥"中创建和获取。<mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -131,7 +131,7 @@
|
||||
<td>API密钥 *</td>
|
||||
<td>
|
||||
<input type="text" name="paramCloudFlareAPIKey" maxlength="100" spellcheck="false"/>
|
||||
<p class="comment">在个人资料中的"API令牌"--"API密钥"--"Global API Key"中获取。</p>
|
||||
<p class="comment">在个人资料中的"API令牌"--"API密钥"--"Global API Key"中获取。<mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -156,6 +156,7 @@
|
||||
<td>Secret *</td>
|
||||
<td>
|
||||
<input type="text" name="paramGoDaddySecret" maxlength="100" spellcheck="false"/>
|
||||
<p class="comment"><mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -180,7 +181,7 @@
|
||||
<td>认证密码 *<em>(auth-password)</em></td>
|
||||
<td>
|
||||
<input type="password" name="paramClouDNSAuthPassword" maxlength="100" spellcheck="false"/>
|
||||
<p class="comment">用户或者子用户的认证密码。</p>
|
||||
<p class="comment">用户或者子用户的认证密码。<mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -198,7 +199,7 @@
|
||||
<td>API Secret *</td>
|
||||
<td>
|
||||
<input type="text" name="paramDNSComSecret" maxlength="100" spellcheck="false"/>
|
||||
<p class="comment">在DNS.COM控制台账号中心--API设置中创建和查看。</p>
|
||||
<p class="comment">在DNS.COM控制台账号中心--API设置中创建和查看。<mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -216,7 +217,7 @@
|
||||
<td>API密钥 *</td>
|
||||
<td>
|
||||
<input type="text" name="paramDNSLaSecret" maxlength="100" spellcheck="false"/>
|
||||
<p class="comment">在DNS.LA控制台--账户信息中查看。</p>
|
||||
<p class="comment">在DNS.LA控制台--账户信息中查看。<mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -234,6 +235,7 @@
|
||||
<td>Secret Access Key *</td>
|
||||
<td>
|
||||
<input type="text" name="paramVolcEngineAccessKeySecret" maxlength="100" spellcheck="false"/>
|
||||
<p class="comment"><mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -250,6 +252,7 @@
|
||||
<td>Secret Access Key *</td>
|
||||
<td>
|
||||
<input type="text" name="paramAmazonRoute53AccessKeySecret" maxlength="100" spellcheck="false"/>
|
||||
<p class="comment"><mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -286,7 +289,7 @@
|
||||
<tr>
|
||||
<td>客户端密码值 <br/><em>(Client Secret Value)</em> *</td>
|
||||
<td><input type="text" name="paramAzureDNSClientSecret" maxlength="100" spellcheck="false"/>
|
||||
<p class="comment">可以在应用注册(App registrations)中对应应用的“证书和密码(Certificates & secrets)”--“客户端密码(Client secrets)”中创建和查看。</p>
|
||||
<p class="comment">可以在应用注册(App registrations)中对应应用的“证书和密码(Certificates & secrets)”--“客户端密码(Client secrets)”中创建和查看。<mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -338,6 +341,7 @@
|
||||
<td>AccessKey密钥 *</td>
|
||||
<td>
|
||||
<input type="text" name="paramEdgeDNSAPIAccessKeySecret" maxlength="64"/>
|
||||
<p class="comment"><mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<td>SecretKey *</td>
|
||||
<td>
|
||||
<input type="text" name="paramDNSPodAccessKeySecret" maxlength="100" v-model="provider.params.accessKeySecret"/>
|
||||
<p class="comment">在DNSPod控制台“账号中心--API密钥”中获取。</p>
|
||||
<p class="comment">在DNSPod控制台“账号中心--API密钥”中获取。<mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-show="provider.params.apiType == null || provider.params.apiType.length == 0 || provider.params.apiType == 'dnsPodToken'">
|
||||
@@ -60,7 +60,7 @@
|
||||
<td>密钥Token *</td>
|
||||
<td>
|
||||
<input type="text" name="paramDNSPodToken" maxlength="100" v-model="provider.params.token" spellcheck="false"/>
|
||||
<p class="comment">在DNSPod控制台“账号中心--API密钥--DNSPod Token”中获取。</p>
|
||||
<p class="comment">在DNSPod控制台“账号中心--API密钥--DNSPod Token”中获取。<mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="provider.params.apiType == null || provider.params.apiType.length == 0 || provider.params.apiType == 'dnsPodToken'">
|
||||
@@ -87,7 +87,7 @@
|
||||
<td>AccessKeySecret *</td>
|
||||
<td>
|
||||
<input type="text" name="paramAliDNSAccessKeySecret" maxlength="100" v-model="provider.params.accessKeySecret" spellcheck="false"/>
|
||||
<p class="comment">登录阿里云控制台 -- 在"访问控制"中创建和获取。</p>
|
||||
<p class="comment">登录阿里云控制台 -- 在"访问控制"中创建和获取。<mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -112,7 +112,7 @@
|
||||
<td>AccessKeySecret *</td>
|
||||
<td>
|
||||
<input type="text" name="paramHuaweiAccessKeySecret" maxlength="100" v-model="provider.params.accessKeySecret" spellcheck="false"/>
|
||||
<p class="comment">登录华为云控制台 -- 在"我的凭证 -- 访问密钥"中创建和获取。</p>
|
||||
<p class="comment">登录华为云控制台 -- 在"我的凭证 -- 访问密钥"中创建和获取。<mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -131,7 +131,7 @@
|
||||
<td>API密钥 *</td>
|
||||
<td>
|
||||
<input type="text" name="paramCloudFlareAPIKey" maxlength="100" v-model="provider.params.apiKey" spellcheck="false"/>
|
||||
<p class="comment">在个人资料中的"API令牌"--"API密钥"--"Global API Key"中获取。</p>
|
||||
<p class="comment">在个人资料中的"API令牌"--"API密钥"--"Global API Key"中获取。<mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -156,6 +156,7 @@
|
||||
<td>Secret *</td>
|
||||
<td>
|
||||
<input type="text" name="paramGoDaddySecret" maxlength="100" v-model="provider.params.secret" spellcheck="false"/>
|
||||
<p class="comment"><mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -180,7 +181,7 @@
|
||||
<td>认证密码 *<em>(auth-password)</em></td>
|
||||
<td>
|
||||
<input type="password" name="paramClouDNSAuthPassword" maxlength="100" v-model="provider.params.authPassword" spellcheck="false"/>
|
||||
<p class="comment">用户或者子用户的认证密码。</p>
|
||||
<p class="comment">用户或者子用户的认证密码。<mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -198,7 +199,7 @@
|
||||
<td>API Secret *</td>
|
||||
<td>
|
||||
<input type="text" name="paramDNSComSecret" maxlength="100" v-model="provider.params.secret" spellcheck="false"/>
|
||||
<p class="comment">在DNS.COM控制台账号中心--API设置中创建和查看。</p>
|
||||
<p class="comment">在DNS.COM控制台账号中心--API设置中创建和查看。<mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -216,7 +217,7 @@
|
||||
<td>API密钥 *</td>
|
||||
<td>
|
||||
<input type="text" name="paramDNSLaSecret" maxlength="100" v-model="provider.params.secret" spellcheck="false"/>
|
||||
<p class="comment">在DNS.LA控制台--账户信息中查看。</p>
|
||||
<p class="comment">在DNS.LA控制台--账户信息中查看。<mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -234,6 +235,7 @@
|
||||
<td>Secret Access Key *</td>
|
||||
<td>
|
||||
<input type="text" name="paramVolcEngineAccessKeySecret" maxlength="100" v-model="provider.params.accessKeySecret" spellcheck="false"/>
|
||||
<p class="comment"><mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -250,6 +252,7 @@
|
||||
<td>Secret Access Key *</td>
|
||||
<td>
|
||||
<input type="text" name="paramAmazonRoute53AccessKeySecret" maxlength="100" v-model="provider.params.accessKeySecret" spellcheck="false"/>
|
||||
<p class="comment"><mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -287,7 +290,7 @@
|
||||
<td>客户端密码值 <br/><em>(Client Secret Value)</em> *</td>
|
||||
<td>
|
||||
<input type="text" name="paramAzureDNSClientSecret" maxlength="100" v-model="provider.params.clientSecret" spellcheck="false"/>
|
||||
<p class="comment">可以在应用注册(App registrations)中对应应用的“证书和密码(Certificates & secrets)”--“客户端密码(Client secrets)”中创建和查看。</p>
|
||||
<p class="comment">可以在应用注册(App registrations)中对应应用的“证书和密码(Certificates & secrets)”--“客户端密码(Client secrets)”中创建和查看。<mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -340,6 +343,7 @@
|
||||
<td>AccessKey密钥 *</td>
|
||||
<td>
|
||||
<input type="text" name="paramEdgeDNSAPIAccessKeySecret" maxlength="64" v-model="provider.params.accessKeySecret"/>
|
||||
<p class="comment"><mask-warning></mask-warning></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
|
||||
38
web/views/@default/index/initPassword.css
Normal file
38
web/views/@default/index/initPassword.css
Normal file
@@ -0,0 +1,38 @@
|
||||
.form-box {
|
||||
position: fixed;
|
||||
top: 2em;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
form {
|
||||
position: fixed;
|
||||
width: 21em;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-left: -10em;
|
||||
margin-top: -16em;
|
||||
}
|
||||
form .header {
|
||||
text-align: center;
|
||||
font-size: 1em !important;
|
||||
}
|
||||
form p {
|
||||
font-size: 0.8em;
|
||||
margin-top: 0.3em;
|
||||
margin-bottom: 0;
|
||||
font-weight: normal;
|
||||
padding: 0;
|
||||
}
|
||||
form .comment {
|
||||
margin-top: 0.5em;
|
||||
padding: 0.5em;
|
||||
color: gray;
|
||||
}
|
||||
@media screen and (max-width: 512px) {
|
||||
form {
|
||||
width: 80%;
|
||||
margin-left: -40%;
|
||||
}
|
||||
}
|
||||
/*# sourceMappingURL=initPassword.css.map */
|
||||
1
web/views/@default/index/initPassword.css.map
Normal file
1
web/views/@default/index/initPassword.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["initPassword.less"],"names":[],"mappings":"AAAA;EACI,eAAA;EACA,QAAA;EACA,SAAA;EACA,OAAA;EACA,QAAA;;AAGJ;EACI,eAAA;EACA,WAAA;EACA,QAAA;EACA,SAAA;EACA,kBAAA;EACA,iBAAA;;AANJ,IAQC;EACC,kBAAA;EACA,yBAAA;;AAVF,IAaC;EACC,gBAAA;EACA,iBAAA;EACA,gBAAA;EACA,mBAAA;EACA,UAAA;;AAlBF,IAqBC;EACC,iBAAA;EACA,cAAA;EACA,WAAA;;AAIF,mBAAqC;EACjC;IACI,UAAA;IACA,iBAAA","file":"initPassword.css"}
|
||||
45
web/views/@default/index/initPassword.html
Normal file
45
web/views/@default/index/initPassword.html
Normal file
@@ -0,0 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
<title>初始化系统</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0">
|
||||
{$TEA.VUE}
|
||||
{$TEA.SEMANTIC}
|
||||
<script type="text/javascript" src="/js/md5.min.js"></script>
|
||||
<script type="text/javascript" src="/js/utils.min.js"></script>
|
||||
<script type="text/javascript" src="/js/sweetalert2/dist/sweetalert2.all.min.js"></script>
|
||||
<script type="text/javascript" src="/js/components.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<div class="form-box">
|
||||
<form method="post" class="ui form" data-tea-action="$" data-tea-success="success" autocomplete="off">
|
||||
<csrf-token></csrf-token>
|
||||
<div class="ui segment stacked">
|
||||
<div class="ui header">
|
||||
设置管理员初始密码
|
||||
<p class="comment">由于你是第一次登录,所以需要设置管理员用户名和密码。</p>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<div class="ui left icon input">
|
||||
<i class="ui user icon small"></i>
|
||||
<input type="text" name="username" v-model="username" placeholder="请输入用户名" maxlength="200" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui field">
|
||||
<div class="ui left icon input">
|
||||
<i class="ui lock icon small"></i>
|
||||
<input type="text" name="password" placeholder="请输入密码" maxlength="200" v-model="password"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="ui button primary fluid" type="submit">保存用户名密码</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
7
web/views/@default/index/initPassword.js
Normal file
7
web/views/@default/index/initPassword.js
Normal file
@@ -0,0 +1,7 @@
|
||||
Tea.context(function () {
|
||||
this.success = function () {
|
||||
teaweb.success("用户名和密码保存成功,现在去登录", function () {
|
||||
window.location = "/"
|
||||
})
|
||||
}
|
||||
})
|
||||
42
web/views/@default/index/initPassword.less
Normal file
42
web/views/@default/index/initPassword.less
Normal file
@@ -0,0 +1,42 @@
|
||||
.form-box {
|
||||
position: fixed;
|
||||
top: 2em;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
form {
|
||||
position: fixed;
|
||||
width: 21em;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-left: -10em;
|
||||
margin-top: -16em;
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
font-size: 1em !important;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 0.8em;
|
||||
margin-top: 0.3em;
|
||||
margin-bottom: 0;
|
||||
font-weight: normal;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.comment {
|
||||
margin-top: 0.5em;
|
||||
padding: 0.5em;
|
||||
color: gray;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 512px) {
|
||||
form {
|
||||
width: 80%;
|
||||
margin-left: -40%;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</tr>
|
||||
<tbody v-show="moreOptionsVisible">
|
||||
<tr>
|
||||
<td class="title">名称</td>
|
||||
<td class="title">备注</td>
|
||||
<td>
|
||||
<input type="text" name="name" maxlength="50"/>
|
||||
</td>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">名称</td>
|
||||
<td class="title">备注</td>
|
||||
<td>
|
||||
<input type="text" name="name" maxlength="50" v-model="address.name"/>
|
||||
</td>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">服务商 *</td>
|
||||
<td class="title">证书服务商 *</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" name="providerCode" v-model="providerCode" @change="changeProvider" tabindex="2">
|
||||
<option value="">[选择服务商]</option>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>名称</th>
|
||||
<th>服务商</th>
|
||||
<th>证书服务商</th>
|
||||
<th class="four wide">EAB Kid</th>
|
||||
<th class="four wide">EBA HMAC Key</th>
|
||||
<th class="two wide">操作</th>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="title">服务商 *</td>
|
||||
<td class="title">证书服务商 *</td>
|
||||
<td>
|
||||
<span v-if="account.provider != null">{{account.provider.name}}</span>
|
||||
<span v-else class="disabled">服务商已失效</span>
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
<div v-show="step == 'user'">
|
||||
<table class="ui table definition selectable">
|
||||
<tr>
|
||||
<td class="title">选择服务商 *</td>
|
||||
<td class="title">选择证书服务商 *</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" v-model="providerCode" @change="changeProvider">
|
||||
<option value="">[选择服务商]</option>
|
||||
@@ -120,6 +120,9 @@
|
||||
<option v-for="provider in dnsProviders" :value="provider.id">{{provider.name}}({{provider.typeName}})</option>
|
||||
</select>
|
||||
</div>
|
||||
<div v-else>
|
||||
<span>暂时没有DNS服务商,<a href="/dns/providers">[去添加]</a>。</span>
|
||||
</div>
|
||||
<p class="comment">用于自动创建域名解析记录。</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>所属服务商 *</td>
|
||||
<td>所属证书服务商 *</td>
|
||||
<td>
|
||||
<select class="ui dropdown auto-width" name="providerCode" v-model="providerCode" @change="changeProvider">
|
||||
<option value="">[选择服务商]</option>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="user.provider != null">
|
||||
<td>所属服务商</td>
|
||||
<td>所属证书服务商</td>
|
||||
<td>{{user.provider.name}}</td>
|
||||
</tr>
|
||||
<tr v-if="user.account != null">
|
||||
|
||||
@@ -71,19 +71,24 @@
|
||||
<span v-if="task.keyType == 'key'">URL</span>
|
||||
<span v-if="task.keyType == 'prefix'">前缀</span>
|
||||
</th>
|
||||
<th class="two wide">所属集群</th>
|
||||
<th class="width5">状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody v-for="key in task.keys">
|
||||
<tr>
|
||||
<td>{{key.key}}
|
||||
|
||||
<td>
|
||||
{{key.key}}
|
||||
<div v-if="key.errors.length > 0" style="margin-top: 0.5em">
|
||||
<a :href="'/clusters/cluster/node?nodeId=' + err.nodeId" v-for="err in key.errors" class="ui label basic tiny red">
|
||||
节点{{err.nodeId}}:{{err.error}}
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="key.cluster != null && key.cluster.id > 0">{{key.cluster.name}}</span>
|
||||
<span v-else class="disabled">-</span>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="key.isDone && key.errors.length == 0" class="green">完成</span>
|
||||
<span v-if="key.isDone && key.errors.length > 0" class="red">失败</span>
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
<td>自定义客户端IP报头</td>
|
||||
<td>
|
||||
<input type="text" name="clientIPHeaderNames" v-model="config.clientIPHeaderNames"/>
|
||||
<p class="comment">可以通过此报头获取客户端IP,类似于<code-label>Client-IP</code-label>,用于使用反向代理访问管理系统的情形;如果有多个报头可以使用空格隔开。</p>
|
||||
<p class="comment">可以通过此报头获取客户端IP,类似于<code-label>X-Forwarded-For X-Real-IP True-Client-IP Client-IP</code-label> <a href=""><span class="small" @click.prevent="addDefaultClientIPHeaderNames('X-Forwarded-For X-Real-IP True-Client-IP Client-IP')">[填入]</span></a>,用于使用反向代理访问管理系统的情形;如果有多个报头可以使用空格隔开。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -89,14 +89,14 @@
|
||||
<td>检查客户端指纹</td>
|
||||
<td>
|
||||
<checkbox name="checkClientFingerprint" v-model="config.checkClientFingerprint"></checkbox>
|
||||
<p class="comment">选中后,表示每次管理员访问时都检查客户端相关信息是否跟登录时一致。</p>
|
||||
<p class="comment">选中后,表示每次管理员访问时都检查客户端相关信息是否跟登录时一致;如果客户端IP或User-Agent信息变化比较频繁,请不要启用此选项;如果当前系统下游有反向代理设置,请设置当前表单中的“自定义客户端IP报头”选项。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>检查客户端区域</td>
|
||||
<td>
|
||||
<checkbox name="checkClientRegion" v-model="config.checkClientRegion"></checkbox>
|
||||
<p class="comment">选中后,表示每次管理员访问时都检查客户端所在地理区域是否和登录时一致。</p>
|
||||
<p class="comment">选中后,表示每次管理员访问时都检查客户端所在地理区域是否和登录时一致;如果客户端所处地理区域变化比较频繁,请不要启用此选项;如果当前系统下游有反向代理设置,请设置当前表单中的“自定义客户端IP报头”选项。</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
Tea.context(function () {
|
||||
this.success = NotifyReloadSuccess("保存成功")
|
||||
|
||||
this.addDefaultClientIPHeaderNames = function (headerNames) {
|
||||
if (this.config.clientIPHeaderNames == null || this.config.clientIPHeaderNames.length == 0) {
|
||||
this.config.clientIPHeaderNames = headerNames
|
||||
} else {
|
||||
this.config.clientIPHeaderNames += " " + headerNames
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,4 +1,4 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
|
||||
Reference in New Issue
Block a user