A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
1// SiYuan - Refactor your thinking
2// Copyright (c) 2020-present, b3log.org
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU Affero General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <https://www.gnu.org/licenses/>.
16
17package util
18
19import (
20 "net"
21 "net/http"
22 "net/url"
23 "os"
24 "strconv"
25 "strings"
26 "time"
27
28 "github.com/88250/gulu"
29 "github.com/88250/lute/ast"
30 "github.com/gin-gonic/gin"
31 "github.com/imroc/req/v3"
32 "github.com/siyuan-note/httpclient"
33 "github.com/siyuan-note/logging"
34)
35
36// GetPrivateIPv4s 获取本地所有的私有 IPv4 地址(排除虚拟网卡)
37func GetPrivateIPv4s() (ret []string) {
38 ret = []string{}
39
40 interfaces, err := net.Interfaces()
41 if err != nil {
42 return
43 }
44
45 // 常见的虚拟网卡名称关键字黑名单
46 virtualKeywords := []string{"docker", "veth", "br-", "vmnet", "vbox", "utun", "tun", "tap", "bridge", "cloud", "hyper-"}
47
48 for _, itf := range interfaces {
49 // 1. 基础状态过滤:必须是启动状态且不能是回环网卡
50 if itf.Flags&net.FlagUp == 0 || itf.Flags&net.FlagLoopback != 0 {
51 continue
52 }
53
54 // 2. 硬件地址过滤:物理网卡通常必须有 MAC 地址
55 if len(itf.HardwareAddr) == 0 {
56 continue
57 }
58
59 // 3. 名称过滤:排除已知虚拟网卡前缀
60 name := strings.ToLower(itf.Name)
61 isVirtual := false
62 for _, kw := range virtualKeywords {
63 if strings.Contains(name, kw) {
64 isVirtual = true
65 break
66 }
67 }
68 if isVirtual {
69 continue
70 }
71
72 // 4. 提取并校验 IP
73 addrs, err := itf.Addrs()
74 if err != nil {
75 continue
76 }
77
78 for _, addr := range addrs {
79 ipNet, ok := addr.(*net.IPNet)
80 if !ok {
81 continue
82 }
83
84 ip := ipNet.IP
85 // 仅保留 IPv4 且必须是私有局域网地址 (10.x, 172.16.x, 192.168.x)
86 if ip.To4() != nil && ip.IsPrivate() {
87 ret = append(ret, ip.String())
88 }
89 }
90 }
91 return
92}
93
94func IsLocalHostname(hostname string) bool {
95 if "localhost" == hostname || strings.HasSuffix(hostname, ".localhost") {
96 return true
97 }
98 if ip := net.ParseIP(hostname); nil != ip {
99 return ip.IsLoopback()
100 }
101 return false
102}
103
104func IsLocalHost(host string) bool {
105 if hostname, _, err := net.SplitHostPort(strings.TrimSpace(host)); err != nil {
106 return false
107 } else {
108 return IsLocalHostname(hostname)
109 }
110}
111
112func IsLocalOrigin(origin string) bool {
113 if u, err := url.Parse(origin); err == nil {
114 return IsLocalHostname(u.Hostname())
115 }
116 return false
117}
118
119func IsOnline(checkURL string, skipTlsVerify bool, timeout int) bool {
120 if "" == checkURL {
121 return false
122 }
123
124 u, err := url.Parse(checkURL)
125 if err != nil {
126 logging.LogWarnf("invalid check URL [%s]", checkURL)
127 return false
128 }
129 if u.Scheme == "file" {
130 filePath := strings.TrimPrefix(checkURL, "file://")
131 _, err := os.Stat(filePath)
132 return err == nil
133 }
134
135 if isOnline(checkURL, skipTlsVerify, timeout) {
136 return true
137 }
138
139 logging.LogWarnf("network is offline [checkURL=%s]", checkURL)
140 return false
141}
142
143func IsPortOpen(port string) bool {
144 timeout := time.Second
145 conn, err := net.DialTimeout("tcp", net.JoinHostPort("127.0.0.1", port), timeout)
146 if err != nil {
147 return false
148 }
149 if nil != conn {
150 conn.Close()
151 return true
152 }
153 return false
154}
155
156func isOnline(checkURL string, skipTlsVerify bool, timeout int) (ret bool) {
157 c := req.C().
158 SetTimeout(time.Duration(timeout) * time.Millisecond).
159 SetProxy(httpclient.ProxyFromEnvironment).
160 SetUserAgent(UserAgent)
161 if skipTlsVerify {
162 c.EnableInsecureSkipVerify()
163 }
164
165 for i := 0; i < 2; i++ {
166 resp, err := c.R().Get(checkURL)
167 if resp.GetHeader("Location") != "" {
168 return true
169 }
170
171 switch err.(type) {
172 case *url.Error:
173 if err.(*url.Error).URL != checkURL {
174 // DNS 重定向
175 logging.LogWarnf("network is online [DNS redirect, checkURL=%s, retURL=%s]", checkURL, err.(*url.Error).URL)
176 return true
177 }
178 }
179
180 ret = err == nil
181 if ret {
182 break
183 }
184
185 logging.LogWarnf("check url [%s] is online failed: %s", checkURL, err)
186 time.Sleep(1 * time.Second)
187 }
188 return
189}
190
191func GetRemoteAddr(req *http.Request) string {
192 ret := req.Header.Get("X-forwarded-for")
193 ret = strings.TrimSpace(ret)
194 if "" == ret {
195 ret = req.Header.Get("X-Real-IP")
196 }
197 ret = strings.TrimSpace(ret)
198 if "" == ret {
199 return req.RemoteAddr
200 }
201 return strings.Split(ret, ",")[0]
202}
203
204func JsonArg(c *gin.Context, result *gulu.Result) (arg map[string]interface{}, ok bool) {
205 arg = map[string]interface{}{}
206 if err := c.BindJSON(&arg); err != nil {
207 result.Code = -1
208 result.Msg = "parses request failed"
209 return
210 }
211
212 ok = true
213 return
214}
215
216func InvalidIDPattern(idArg string, result *gulu.Result) bool {
217 if ast.IsNodeIDPattern(idArg) {
218 return false
219 }
220
221 result.Code = -1
222 result.Msg = "invalid ID argument"
223 return true
224}
225
226func initHttpClient() {
227 http.DefaultClient = httpclient.GetCloudFileClient2Min()
228 http.DefaultTransport = httpclient.NewTransport(false)
229}
230
231func ParsePort(portString string) (uint16, error) {
232 port, err := strconv.ParseUint(portString, 10, 16)
233 if err != nil {
234 logging.LogErrorf("parse port [%s] failed: %s", portString, err)
235 return 0, err
236 }
237 return uint16(port), nil
238}