mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-23 16:00:52 +00:00
Enabling Panel SSL with the self-sign provider rejected IPv6 hosts with
"domain format invalid". Two coupled bugs caused this:
1. The frontend extracted the host from window.location.href with
href.split('//')[1].split(':')[0]. For an IPv6 URL like
https://[::1]:1234 that yields '[' \u2014 not a valid host \u2014 because
the second split splits on the first colon inside the bracketed
address. Use window.location.hostname, which natively returns the
bracket-stripped IPv6 host.
2. The backend ObtainSSL flow used net.ParseIP(domain) directly. Even if
the frontend sent the bracketed form ('[::1]'), net.ParseIP rejects
brackets, so the value flowed into IsValidDomain() and failed the
regex.
Add common.ParseIPLoose() that accepts both bare and bracketed IPv6 in
addition to bare IPv4. Use it at both call sites in ObtainSSL (renew
path and create path). A unit test guards the regression.
Files:
- agent/utils/common/common.go (new ParseIPLoose helper)
- agent/utils/common/parse_ip_test.go (12 cases, all green)
- agent/app/service/website_ca.go (call sites switched)
- frontend/src/views/setting/safe/ssl/index.vue
(host extraction fix)
Fixes #12646
Signed-off-by: Sanjay Santhanam <51058514+Sanjays2402@users.noreply.github.com>
462 lines
10 KiB
Go
462 lines
10 KiB
Go
package common
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"fmt"
|
|
"io"
|
|
"math/big"
|
|
"net"
|
|
"os/exec"
|
|
"reflect"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
"unicode"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/1Panel-dev/1Panel/agent/buserr"
|
|
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
|
|
"github.com/1Panel-dev/1Panel/agent/utils/re"
|
|
"golang.org/x/net/idna"
|
|
)
|
|
|
|
func CompareVersion(version1, version2 string) bool {
|
|
v1s := extractNumbers(version1)
|
|
v2s := extractNumbers(version2)
|
|
|
|
maxLen := max(len(v1s), len(v2s))
|
|
v1s = append(v1s, make([]string, maxLen-len(v1s))...)
|
|
v2s = append(v2s, make([]string, maxLen-len(v2s))...)
|
|
|
|
for i := 0; i < maxLen; i++ {
|
|
v1, err1 := strconv.Atoi(v1s[i])
|
|
v2, err2 := strconv.Atoi(v2s[i])
|
|
if err1 != nil {
|
|
v1 = 0
|
|
}
|
|
if err2 != nil {
|
|
v2 = 0
|
|
}
|
|
if v1 != v2 {
|
|
return v1 > v2
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func CompareAppVersion(version1, version2 string) bool {
|
|
v1s := extractNumbers(version1)
|
|
v2s := extractNumbers(version2)
|
|
|
|
maxLen := max(len(v1s), len(v2s))
|
|
v1s = append(v1s, make([]string, maxLen-len(v1s))...)
|
|
v2s = append(v2s, make([]string, maxLen-len(v2s))...)
|
|
|
|
for i := 0; i < maxLen; i++ {
|
|
v1, err1 := strconv.Atoi(v1s[i])
|
|
v2, err2 := strconv.Atoi(v2s[i])
|
|
if err1 != nil {
|
|
v1 = 0
|
|
}
|
|
if err2 != nil {
|
|
v2 = 0
|
|
}
|
|
if v1 > v2 {
|
|
return true
|
|
}
|
|
if v1 < v2 {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func ComparePanelVersion(version1, version2 string) bool {
|
|
if version1 == version2 {
|
|
return false
|
|
}
|
|
version1s := SplitStr(version1, ".", "-")
|
|
version2s := SplitStr(version2, ".", "-")
|
|
|
|
if len(version2s) > len(version1s) {
|
|
for i := 0; i < len(version2s)-len(version1s); i++ {
|
|
version1s = append(version1s, "0")
|
|
}
|
|
}
|
|
if len(version1s) > len(version2s) {
|
|
for i := 0; i < len(version1s)-len(version2s); i++ {
|
|
version2s = append(version2s, "0")
|
|
}
|
|
}
|
|
|
|
n := min(len(version1s), len(version2s))
|
|
for i := 0; i < n; i++ {
|
|
if version1s[i] == version2s[i] {
|
|
continue
|
|
} else {
|
|
v1, err1 := strconv.Atoi(version1s[i])
|
|
if err1 != nil {
|
|
return version1s[i] > version2s[i]
|
|
}
|
|
v2, err2 := strconv.Atoi(version2s[i])
|
|
if err2 != nil {
|
|
return version1s[i] > version2s[i]
|
|
}
|
|
return v1 > v2
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func extractNumbers(version string) []string {
|
|
var numbers []string
|
|
start := -1
|
|
for i, r := range version {
|
|
if isDigit(r) {
|
|
if start == -1 {
|
|
start = i
|
|
}
|
|
} else {
|
|
if start != -1 {
|
|
numbers = append(numbers, version[start:i])
|
|
start = -1
|
|
}
|
|
}
|
|
}
|
|
if start != -1 {
|
|
numbers = append(numbers, version[start:])
|
|
}
|
|
return numbers
|
|
}
|
|
|
|
func isDigit(r rune) bool {
|
|
return r >= '0' && r <= '9'
|
|
}
|
|
|
|
func GetSortedVersions(versions []string) []string {
|
|
sort.Slice(versions, func(i, j int) bool {
|
|
return CompareVersion(versions[i], versions[j])
|
|
})
|
|
return versions
|
|
}
|
|
|
|
func IsCrossVersion(version1, version2 string) bool {
|
|
version1s := strings.Split(version1, ".")
|
|
version2s := strings.Split(version2, ".")
|
|
v1num, _ := strconv.Atoi(version1s[0])
|
|
v2num, _ := strconv.Atoi(version2s[0])
|
|
return v2num > v1num
|
|
}
|
|
|
|
func GetUuid() string {
|
|
b := make([]byte, 16)
|
|
_, _ = io.ReadFull(rand.Reader, b)
|
|
b[6] = (b[6] & 0x0f) | 0x40
|
|
b[8] = (b[8] & 0x3f) | 0x80
|
|
return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
|
|
}
|
|
|
|
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
|
|
|
|
func RandStr(n int) string {
|
|
b := make([]rune, n)
|
|
max := big.NewInt(int64(len(letters)))
|
|
for i := range b {
|
|
num, err := rand.Int(rand.Reader, max)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
b[i] = letters[num.Int64()]
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
func RandStrAndNum(n int) string {
|
|
const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
|
|
b := make([]byte, n)
|
|
max := big.NewInt(int64(len(charset)))
|
|
for i := range b {
|
|
num, err := rand.Int(rand.Reader, max)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
b[i] = charset[num.Int64()]
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
func ScanPort(port int) bool {
|
|
ln, err := net.Listen("tcp", ":"+strconv.Itoa(port))
|
|
if err != nil {
|
|
return true
|
|
}
|
|
defer func() { _ = ln.Close() }()
|
|
return false
|
|
}
|
|
|
|
func ScanUDPPort(port int) bool {
|
|
ln, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: port})
|
|
if err != nil {
|
|
return true
|
|
}
|
|
defer func() { _ = ln.Close() }()
|
|
return false
|
|
}
|
|
|
|
func ScanPortWithProto(port int, proto string) bool {
|
|
if proto == "udp" {
|
|
return ScanUDPPort(port)
|
|
}
|
|
return ScanPort(port)
|
|
}
|
|
|
|
func ScanPortWithIP(ip string, port int) bool {
|
|
if len(ip) == 0 || ip == "0.0.0.0" || ip == "::" {
|
|
return ScanPort(port)
|
|
}
|
|
address := net.JoinHostPort(ip, fmt.Sprintf("%d", port))
|
|
timeout := time.Second * 2
|
|
conn, err := net.DialTimeout("tcp", address, timeout)
|
|
|
|
if err != nil {
|
|
if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
defer func() { _ = conn.Close() }()
|
|
return true
|
|
}
|
|
|
|
func IsNum(s string) bool {
|
|
_, err := strconv.ParseFloat(s, 64)
|
|
return err == nil
|
|
}
|
|
|
|
func RemoveRepeatElement(a interface{}) (ret []interface{}) {
|
|
va := reflect.ValueOf(a)
|
|
for i := 0; i < va.Len(); i++ {
|
|
if i > 0 && reflect.DeepEqual(va.Index(i-1).Interface(), va.Index(i).Interface()) {
|
|
continue
|
|
}
|
|
ret = append(ret, va.Index(i).Interface())
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func RemoveRepeatStr(list []string) (ret []string) {
|
|
mapItem := make(map[string]struct{})
|
|
for _, item := range list {
|
|
mapItem[item] = struct{}{}
|
|
}
|
|
for key := range mapItem {
|
|
ret = append(ret, key)
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func LoadSizeUnit(value float64) string {
|
|
val := int64(value)
|
|
if val%1024 != 0 {
|
|
return fmt.Sprintf("%v", val)
|
|
}
|
|
if val > 1048576 {
|
|
return fmt.Sprintf("%vM", val/1048576)
|
|
}
|
|
if val > 1024 {
|
|
return fmt.Sprintf("%vK", val/1024)
|
|
}
|
|
return fmt.Sprintf("%v", val)
|
|
}
|
|
|
|
func LoadSizeUnit2F(value float64) string {
|
|
if value > 1073741824 {
|
|
return fmt.Sprintf("%.2fG", value/1073741824)
|
|
}
|
|
if value > 1048576 {
|
|
return fmt.Sprintf("%.2fM", value/1048576)
|
|
}
|
|
if value > 1024 {
|
|
return fmt.Sprintf("%.2fK", value/1024)
|
|
}
|
|
return fmt.Sprintf("%.2f", value)
|
|
}
|
|
|
|
func LoadTimeZoneByCmd() string {
|
|
loc := time.Now().Location().String()
|
|
if _, err := time.LoadLocation(loc); err != nil {
|
|
loc = "Asia/Shanghai"
|
|
}
|
|
std, err := cmd.RunDefaultWithStdoutBashC("timedatectl | grep 'Time zone'")
|
|
if err != nil {
|
|
return loc
|
|
}
|
|
fields := strings.Fields(string(std))
|
|
if len(fields) != 5 {
|
|
return loc
|
|
}
|
|
if _, err := time.LoadLocation(fields[2]); err != nil {
|
|
return loc
|
|
}
|
|
return fields[2]
|
|
}
|
|
|
|
func IsValidDomain(domain string) bool {
|
|
return re.GetRegex(re.DomainPattern).MatchString(domain)
|
|
}
|
|
|
|
func IsValidNginxServerName(serverName string) bool {
|
|
return re.GetRegex(re.NginxServerNamePattern).MatchString(serverName)
|
|
}
|
|
|
|
func ContainsChinese(text string) bool {
|
|
for _, char := range text {
|
|
if unicode.Is(unicode.Han, char) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func PunycodeEncode(text string) (string, error) {
|
|
encoder := idna.New()
|
|
ascii, err := encoder.ToASCII(text)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return ascii, nil
|
|
}
|
|
|
|
func SplitStr(str string, spi ...string) []string {
|
|
lists := []string{str}
|
|
var results []string
|
|
for _, s := range spi {
|
|
results = []string{}
|
|
for _, list := range lists {
|
|
results = append(results, strings.Split(list, s)...)
|
|
}
|
|
lists = results
|
|
}
|
|
return results
|
|
}
|
|
|
|
func IsValidIP(ip string) bool {
|
|
return net.ParseIP(ip) != nil
|
|
}
|
|
|
|
// ParseIPLoose parses an IP address, accepting bracketed IPv6 forms
|
|
// such as "[::1]" or "[fe80::1%eth0]" in addition to the bare forms
|
|
// that net.ParseIP supports natively. It also trims surrounding
|
|
// whitespace. Returns nil for non-IP input, matching net.ParseIP.
|
|
//
|
|
// This is required because some flows pass the host portion of a URL
|
|
// (e.g. "[::1]") rather than a bare IP address. Rejecting that form
|
|
// caused 1Panel-dev/1Panel#12646 — the panel SSL self-sign workflow
|
|
// reported “domain format invalid” for IPv6 hosts.
|
|
func ParseIPLoose(s string) net.IP {
|
|
trimmed := strings.TrimSpace(s)
|
|
if trimmed == "" {
|
|
return nil
|
|
}
|
|
if len(trimmed) >= 2 && trimmed[0] == '[' && trimmed[len(trimmed)-1] == ']' {
|
|
trimmed = trimmed[1 : len(trimmed)-1]
|
|
}
|
|
return net.ParseIP(trimmed)
|
|
}
|
|
|
|
const (
|
|
b = uint64(1)
|
|
kb = 1024 * b
|
|
mb = 1024 * kb
|
|
gb = 1024 * mb
|
|
)
|
|
|
|
func FormatBytes(bytes uint64) string {
|
|
switch {
|
|
case bytes < kb:
|
|
return fmt.Sprintf("%dB", bytes)
|
|
case bytes < mb:
|
|
return fmt.Sprintf("%.2fKB", float64(bytes)/float64(kb))
|
|
case bytes < gb:
|
|
return fmt.Sprintf("%.2fMB", float64(bytes)/float64(mb))
|
|
default:
|
|
return fmt.Sprintf("%.2fGB", float64(bytes)/float64(gb))
|
|
}
|
|
}
|
|
|
|
func FormatPercent(percent float64) string {
|
|
return fmt.Sprintf("%.2f%%", percent)
|
|
}
|
|
|
|
func GetLang(c *gin.Context) string {
|
|
lang := c.GetHeader("Accept-Language")
|
|
if lang == "" {
|
|
lang = "en"
|
|
}
|
|
return lang
|
|
}
|
|
|
|
func HandleIPList(content string) ([]string, error) {
|
|
ipList := strings.Split(content, "\n")
|
|
var res []string
|
|
for _, ip := range ipList {
|
|
if ip == "" {
|
|
continue
|
|
}
|
|
if net.ParseIP(ip) != nil {
|
|
res = append(res, ip)
|
|
continue
|
|
}
|
|
if _, _, err := net.ParseCIDR(ip); err != nil {
|
|
return nil, buserr.New("ErrParseIP")
|
|
}
|
|
res = append(res, ip)
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func GetSystemVersion(versionString string) string {
|
|
match := re.GetRegex(re.VersionPattern).FindStringSubmatch(versionString)
|
|
if len(match) > 1 {
|
|
return match[1]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func isCommandAvailable(name string, args ...string) bool {
|
|
execCmd := exec.Command(name, args...)
|
|
err := execCmd.Run()
|
|
return err == nil
|
|
}
|
|
|
|
func GetDockerComposeCommand() string {
|
|
if isCommandAvailable("docker", "compose", "version") {
|
|
return "docker compose"
|
|
}
|
|
if isCommandAvailable("docker-compose", "version") {
|
|
return "docker-compose"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func LoadParams(param string) string {
|
|
stdout, err := cmd.RunDefaultWithStdoutBashCf("grep '^%s=' /usr/local/bin/1pctl | cut -d'=' -f2", param)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
info := strings.ReplaceAll(stdout, "\n", "")
|
|
if len(info) == 0 || info == `""` {
|
|
panic(fmt.Sprintf("error `%s` find in /usr/local/bin/1pctl", param))
|
|
}
|
|
return info
|
|
}
|
|
func LoadParamsWithoutPanic(param string) string {
|
|
stdout, err := cmd.RunDefaultWithStdoutBashCf("grep '^%s=' /usr/local/bin/1pctl | cut -d'=' -f2", param)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return strings.ReplaceAll(stdout, "\n", "")
|
|
}
|