A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at upstream/main 317 lines 8.8 kB view raw
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 api 18 19import ( 20 "encoding/base32" 21 "encoding/base64" 22 "encoding/hex" 23 "fmt" 24 "io" 25 "math" 26 "net/http" 27 "net/textproto" 28 "net/url" 29 "strings" 30 "time" 31 32 "github.com/88250/gulu" 33 "github.com/gin-gonic/gin" 34 "github.com/imroc/req/v3" 35 "github.com/siyuan-note/logging" 36 "github.com/siyuan-note/siyuan/kernel/util" 37) 38 39type File struct { 40 Filename string 41 Header textproto.MIMEHeader 42 Size int64 43 Content string 44} 45 46type MultipartForm struct { 47 Value map[string][]string 48 File map[string][]File 49} 50 51func echo(c *gin.Context) { 52 ret := gulu.Ret.NewResult() 53 defer c.JSON(http.StatusOK, ret) 54 55 var ( 56 password string 57 passwordSet bool 58 multipartForm *MultipartForm 59 rawData any 60 ) 61 62 password, passwordSet = c.Request.URL.User.Password() 63 64 if form, err := c.MultipartForm(); err != nil || nil == form { 65 multipartForm = nil 66 } else { 67 multipartForm = &MultipartForm{ 68 Value: form.Value, 69 File: map[string][]File{}, 70 } 71 for k, handlers := range form.File { 72 files := make([]File, len(handlers)) 73 multipartForm.File[k] = files 74 for i, handler := range handlers { 75 files[i].Filename = handler.Filename 76 files[i].Header = handler.Header 77 files[i].Size = handler.Size 78 if file, err := handler.Open(); err != nil { 79 logging.LogWarnf("echo open form [%s] file [%s] error: %s", k, handler.Filename, err.Error()) 80 } else { 81 content := make([]byte, handler.Size) 82 if _, err := file.Read(content); err != nil { 83 logging.LogWarnf("echo read form [%s] file [%s] error: %s", k, handler.Filename, err.Error()) 84 } else { 85 files[i].Content = base64.StdEncoding.EncodeToString(content) 86 } 87 } 88 } 89 } 90 } 91 92 if data, err := c.GetRawData(); err == nil { 93 rawData = base64.StdEncoding.EncodeToString(data) 94 } else { 95 logging.LogWarnf("echo get raw data error: %s", err.Error()) 96 rawData = nil 97 } 98 c.Request.ParseForm() 99 c.Request.ParseMultipartForm(math.MaxInt64) 100 101 ret.Data = map[string]interface{}{ 102 "Context": map[string]interface{}{ 103 "Params": c.Params, 104 "HandlerNames": c.HandlerNames(), 105 "FullPath": c.FullPath(), 106 "ClientIP": c.ClientIP(), 107 "RemoteIP": c.RemoteIP(), 108 "ContentType": c.ContentType(), 109 "IsWebsocket": c.IsWebsocket(), 110 "RawData": rawData, 111 }, 112 "Request": map[string]interface{}{ 113 "Method": c.Request.Method, 114 "URL": c.Request.URL, 115 "Proto": c.Request.Proto, 116 "ProtoMajor": c.Request.ProtoMajor, 117 "ProtoMinor": c.Request.ProtoMinor, 118 "Header": c.Request.Header, 119 "ContentLength": c.Request.ContentLength, 120 "TransferEncoding": c.Request.TransferEncoding, 121 "Close": c.Request.Close, 122 "Host": c.Request.Host, 123 "Form": c.Request.Form, 124 "PostForm": c.Request.PostForm, 125 "MultipartForm": multipartForm, 126 "Trailer": c.Request.Trailer, 127 "RemoteAddr": c.Request.RemoteAddr, 128 "TLS": c.Request.TLS, 129 "UserAgent": c.Request.UserAgent(), 130 "Cookies": c.Request.Cookies(), 131 "Referer": c.Request.Referer(), 132 }, 133 "URL": map[string]interface{}{ 134 "EscapedPath": c.Request.URL.EscapedPath(), 135 "EscapedFragment": c.Request.URL.EscapedFragment(), 136 "String": c.Request.URL.String(), 137 "Redacted": c.Request.URL.Redacted(), 138 "IsAbs": c.Request.URL.IsAbs(), 139 "Query": c.Request.URL.Query(), 140 "RequestURI": c.Request.URL.RequestURI(), 141 "Hostname": c.Request.URL.Hostname(), 142 "Port": c.Request.URL.Port(), 143 }, 144 "User": map[string]interface{}{ 145 "Username": c.Request.URL.User.Username(), 146 "Password": password, 147 "PasswordSet": passwordSet, 148 "String": c.Request.URL.User.String(), 149 }, 150 } 151} 152 153func forwardProxy(c *gin.Context) { 154 ret := gulu.Ret.NewResult() 155 defer c.JSON(http.StatusOK, ret) 156 157 arg, ok := util.JsonArg(c, ret) 158 if !ok { 159 return 160 } 161 162 destURL := arg["url"].(string) 163 if _, e := url.ParseRequestURI(destURL); nil != e { 164 ret.Code = -1 165 ret.Msg = "invalid [url]" 166 return 167 } 168 169 method := "POST" 170 if methodArg := arg["method"]; nil != methodArg { 171 method = strings.ToUpper(methodArg.(string)) 172 } 173 timeout := 7 * 1000 174 if timeoutArg := arg["timeout"]; nil != timeoutArg { 175 timeout = int(timeoutArg.(float64)) 176 if 1 > timeout { 177 timeout = 7 * 1000 178 } 179 } 180 181 client := req.C() 182 client.SetTimeout(time.Duration(timeout) * time.Millisecond) 183 request := client.R() 184 headers := arg["headers"].([]interface{}) 185 for _, pair := range headers { 186 for k, v := range pair.(map[string]interface{}) { 187 request.SetHeader(k, fmt.Sprintf("%s", v)) 188 } 189 } 190 191 contentType := "application/json" 192 if contentTypeArg := arg["contentType"]; nil != contentTypeArg { 193 contentType = contentTypeArg.(string) 194 } 195 request.SetHeader("Content-Type", contentType) 196 197 payloadEncoding := "json" 198 if payloadEncodingArg := arg["payloadEncoding"]; nil != payloadEncodingArg { 199 payloadEncoding = payloadEncodingArg.(string) 200 } 201 202 switch payloadEncoding { 203 case "base64": 204 fallthrough 205 case "base64-std": 206 if payload, err := base64.StdEncoding.DecodeString(arg["payload"].(string)); err != nil { 207 ret.Code = -2 208 ret.Msg = "decode base64-std payload failed: " + err.Error() 209 return 210 } else { 211 request.SetBody(payload) 212 } 213 case "base64-url": 214 if payload, err := base64.URLEncoding.DecodeString(arg["payload"].(string)); err != nil { 215 ret.Code = -2 216 ret.Msg = "decode base64-url payload failed: " + err.Error() 217 return 218 } else { 219 request.SetBody(payload) 220 } 221 case "base32": 222 fallthrough 223 case "base32-std": 224 if payload, err := base32.StdEncoding.DecodeString(arg["payload"].(string)); err != nil { 225 ret.Code = -2 226 ret.Msg = "decode base32-std payload failed: " + err.Error() 227 return 228 } else { 229 request.SetBody(payload) 230 } 231 case "base32-hex": 232 if payload, err := base32.HexEncoding.DecodeString(arg["payload"].(string)); err != nil { 233 ret.Code = -2 234 ret.Msg = "decode base32-hex payload failed: " + err.Error() 235 return 236 } else { 237 request.SetBody(payload) 238 } 239 case "hex": 240 if payload, err := hex.DecodeString(arg["payload"].(string)); err != nil { 241 ret.Code = -2 242 ret.Msg = "decode hex payload failed: " + err.Error() 243 return 244 } else { 245 request.SetBody(payload) 246 } 247 case "text": 248 default: 249 request.SetBody(arg["payload"]) 250 } 251 252 started := time.Now() 253 resp, err := request.Send(method, destURL) 254 if err != nil { 255 ret.Code = -1 256 ret.Msg = "forward request failed: " + err.Error() 257 return 258 } 259 260 bodyData, err := io.ReadAll(resp.Body) 261 if err != nil { 262 ret.Code = -1 263 ret.Msg = "read response body failed: " + err.Error() 264 return 265 } 266 267 elapsed := time.Now().Sub(started) 268 269 responseEncoding := "text" 270 if responseEncodingArg := arg["responseEncoding"]; nil != responseEncodingArg { 271 responseEncoding = responseEncodingArg.(string) 272 } 273 274 body := "" 275 switch responseEncoding { 276 case "base64": 277 fallthrough 278 case "base64-std": 279 body = base64.StdEncoding.EncodeToString(bodyData) 280 case "base64-url": 281 body = base64.URLEncoding.EncodeToString(bodyData) 282 case "base32": 283 fallthrough 284 case "base32-std": 285 body = base32.StdEncoding.EncodeToString(bodyData) 286 case "base32-hex": 287 body = base32.HexEncoding.EncodeToString(bodyData) 288 case "hex": 289 body = hex.EncodeToString(bodyData) 290 case "text": 291 fallthrough 292 default: 293 responseEncoding = "text" 294 body = string(bodyData) 295 } 296 297 data := map[string]interface{}{ 298 "url": destURL, 299 "status": resp.StatusCode, 300 "contentType": resp.GetHeader("content-type"), 301 "body": body, 302 "bodyEncoding": responseEncoding, 303 "headers": resp.Header, 304 "elapsed": elapsed.Milliseconds(), 305 } 306 ret.Data = data 307 308 //shortBody := "" 309 //if 64 > len(body) { 310 // shortBody = body 311 //} else { 312 // shortBody = body[:64] 313 //} 314 // 315 //logging.LogInfof("elapsed [%.1fs], length [%d], request [url=%s, headers=%s, content-type=%s, body=%s], status [%d], body [%s]", 316 // elapsed.Seconds(), len(bodyData), data["url"], headers, contentType, arg["payload"], data["status"], shortBody) 317}