network.go 8.6 KB

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