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 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}