console.go 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. package setting
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/url"
  6. "one-api/common"
  7. "regexp"
  8. "sort"
  9. "strings"
  10. "time"
  11. )
  12. // ValidateConsoleSettings 验证控制台设置信息格式
  13. func ValidateConsoleSettings(settingsStr string, settingType string) error {
  14. if settingsStr == "" {
  15. return nil // 空字符串是合法的
  16. }
  17. switch settingType {
  18. case "ApiInfo":
  19. return validateApiInfo(settingsStr)
  20. case "Announcements":
  21. return validateAnnouncements(settingsStr)
  22. case "FAQ":
  23. return validateFAQ(settingsStr)
  24. default:
  25. return fmt.Errorf("未知的设置类型:%s", settingType)
  26. }
  27. }
  28. // validateApiInfo 验证API信息格式
  29. func validateApiInfo(apiInfoStr string) error {
  30. var apiInfoList []map[string]interface{}
  31. if err := json.Unmarshal([]byte(apiInfoStr), &apiInfoList); err != nil {
  32. return fmt.Errorf("API信息格式错误:%s", err.Error())
  33. }
  34. // 验证数组长度
  35. if len(apiInfoList) > 50 {
  36. return fmt.Errorf("API信息数量不能超过50个")
  37. }
  38. // 允许的颜色值
  39. validColors := map[string]bool{
  40. "blue": true, "green": true, "cyan": true, "purple": true, "pink": true,
  41. "red": true, "orange": true, "amber": true, "yellow": true, "lime": true,
  42. "light-green": true, "teal": true, "light-blue": true, "indigo": true,
  43. "violet": true, "grey": true,
  44. }
  45. // URL正则表达式,支持域名和IP地址格式
  46. // 域名格式:https://example.com 或 https://sub.example.com:8080
  47. // IP地址格式:https://192.168.1.1 或 https://192.168.1.1:8080
  48. urlRegex := regexp.MustCompile(`^https?://(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))(?::[0-9]{1,5})?(?:/.*)?$`)
  49. for i, apiInfo := range apiInfoList {
  50. // 检查必填字段
  51. urlStr, ok := apiInfo["url"].(string)
  52. if !ok || urlStr == "" {
  53. return fmt.Errorf("第%d个API信息缺少URL字段", i+1)
  54. }
  55. route, ok := apiInfo["route"].(string)
  56. if !ok || route == "" {
  57. return fmt.Errorf("第%d个API信息缺少线路描述字段", i+1)
  58. }
  59. description, ok := apiInfo["description"].(string)
  60. if !ok || description == "" {
  61. return fmt.Errorf("第%d个API信息缺少说明字段", i+1)
  62. }
  63. color, ok := apiInfo["color"].(string)
  64. if !ok || color == "" {
  65. return fmt.Errorf("第%d个API信息缺少颜色字段", i+1)
  66. }
  67. // 验证URL格式
  68. if !urlRegex.MatchString(urlStr) {
  69. return fmt.Errorf("第%d个API信息的URL格式不正确", i+1)
  70. }
  71. // 验证URL可解析性
  72. if _, err := url.Parse(urlStr); err != nil {
  73. return fmt.Errorf("第%d个API信息的URL无法解析:%s", i+1, err.Error())
  74. }
  75. // 验证字段长度
  76. if len(urlStr) > 500 {
  77. return fmt.Errorf("第%d个API信息的URL长度不能超过500字符", i+1)
  78. }
  79. if len(route) > 100 {
  80. return fmt.Errorf("第%d个API信息的线路描述长度不能超过100字符", i+1)
  81. }
  82. if len(description) > 200 {
  83. return fmt.Errorf("第%d个API信息的说明长度不能超过200字符", i+1)
  84. }
  85. // 验证颜色值
  86. if !validColors[color] {
  87. return fmt.Errorf("第%d个API信息的颜色值不合法", i+1)
  88. }
  89. // 检查并过滤危险字符(防止XSS)
  90. dangerousChars := []string{"<script", "<iframe", "javascript:", "onload=", "onerror=", "onclick="}
  91. for _, dangerous := range dangerousChars {
  92. if strings.Contains(strings.ToLower(description), dangerous) {
  93. return fmt.Errorf("第%d个API信息的说明包含不允许的内容", i+1)
  94. }
  95. if strings.Contains(strings.ToLower(route), dangerous) {
  96. return fmt.Errorf("第%d个API信息的线路描述包含不允许的内容", i+1)
  97. }
  98. }
  99. }
  100. return nil
  101. }
  102. // ValidateApiInfo 保持向后兼容的函数
  103. func ValidateApiInfo(apiInfoStr string) error {
  104. return validateApiInfo(apiInfoStr)
  105. }
  106. // GetApiInfo 获取API信息列表
  107. func GetApiInfo() []map[string]interface{} {
  108. // 从OptionMap中获取API信息,如果不存在则返回空数组
  109. common.OptionMapRWMutex.RLock()
  110. apiInfoStr, exists := common.OptionMap["ApiInfo"]
  111. common.OptionMapRWMutex.RUnlock()
  112. if !exists || apiInfoStr == "" {
  113. // 如果没有配置,返回空数组
  114. return []map[string]interface{}{}
  115. }
  116. // 解析存储的API信息
  117. var apiInfo []map[string]interface{}
  118. if err := json.Unmarshal([]byte(apiInfoStr), &apiInfo); err != nil {
  119. // 如果解析失败,返回空数组
  120. return []map[string]interface{}{}
  121. }
  122. return apiInfo
  123. }
  124. // validateAnnouncements 验证系统公告格式
  125. func validateAnnouncements(announcementsStr string) error {
  126. var announcementsList []map[string]interface{}
  127. if err := json.Unmarshal([]byte(announcementsStr), &announcementsList); err != nil {
  128. return fmt.Errorf("系统公告格式错误:%s", err.Error())
  129. }
  130. // 验证数组长度
  131. if len(announcementsList) > 100 {
  132. return fmt.Errorf("系统公告数量不能超过100个")
  133. }
  134. // 允许的类型值
  135. validTypes := map[string]bool{
  136. "default": true, "ongoing": true, "success": true, "warning": true, "error": true,
  137. }
  138. for i, announcement := range announcementsList {
  139. // 检查必填字段
  140. content, ok := announcement["content"].(string)
  141. if !ok || content == "" {
  142. return fmt.Errorf("第%d个公告缺少内容字段", i+1)
  143. }
  144. // 检查发布日期字段
  145. publishDate, exists := announcement["publishDate"]
  146. if !exists {
  147. return fmt.Errorf("第%d个公告缺少发布日期字段", i+1)
  148. }
  149. publishDateStr, ok := publishDate.(string)
  150. if !ok || publishDateStr == "" {
  151. return fmt.Errorf("第%d个公告的发布日期不能为空", i+1)
  152. }
  153. // 验证ISO日期格式
  154. if _, err := time.Parse(time.RFC3339, publishDateStr); err != nil {
  155. return fmt.Errorf("第%d个公告的发布日期格式错误", i+1)
  156. }
  157. // 验证可选字段
  158. if announcementType, exists := announcement["type"]; exists {
  159. if typeStr, ok := announcementType.(string); ok {
  160. if !validTypes[typeStr] {
  161. return fmt.Errorf("第%d个公告的类型值不合法", i+1)
  162. }
  163. }
  164. }
  165. // 验证字段长度
  166. if len(content) > 500 {
  167. return fmt.Errorf("第%d个公告的内容长度不能超过500字符", i+1)
  168. }
  169. if extra, exists := announcement["extra"]; exists {
  170. if extraStr, ok := extra.(string); ok && len(extraStr) > 200 {
  171. return fmt.Errorf("第%d个公告的说明长度不能超过200字符", i+1)
  172. }
  173. }
  174. // 检查并过滤危险字符(防止XSS)
  175. dangerousChars := []string{"<script", "<iframe", "javascript:", "onload=", "onerror=", "onclick="}
  176. for _, dangerous := range dangerousChars {
  177. if strings.Contains(strings.ToLower(content), dangerous) {
  178. return fmt.Errorf("第%d个公告的内容包含不允许的内容", i+1)
  179. }
  180. }
  181. }
  182. return nil
  183. }
  184. // validateFAQ 验证常见问答格式
  185. func validateFAQ(faqStr string) error {
  186. var faqList []map[string]interface{}
  187. if err := json.Unmarshal([]byte(faqStr), &faqList); err != nil {
  188. return fmt.Errorf("常见问答格式错误:%s", err.Error())
  189. }
  190. // 验证数组长度
  191. if len(faqList) > 100 {
  192. return fmt.Errorf("常见问答数量不能超过100个")
  193. }
  194. for i, faq := range faqList {
  195. // 检查必填字段
  196. title, ok := faq["title"].(string)
  197. if !ok || title == "" {
  198. return fmt.Errorf("第%d个问答缺少标题字段", i+1)
  199. }
  200. content, ok := faq["content"].(string)
  201. if !ok || content == "" {
  202. return fmt.Errorf("第%d个问答缺少内容字段", i+1)
  203. }
  204. // 验证字段长度
  205. if len(title) > 200 {
  206. return fmt.Errorf("第%d个问答的标题长度不能超过200字符", i+1)
  207. }
  208. if len(content) > 1000 {
  209. return fmt.Errorf("第%d个问答的内容长度不能超过1000字符", i+1)
  210. }
  211. // 检查并过滤危险字符(防止XSS)
  212. dangerousChars := []string{"<script", "<iframe", "javascript:", "onload=", "onerror=", "onclick="}
  213. for _, dangerous := range dangerousChars {
  214. if strings.Contains(strings.ToLower(title), dangerous) {
  215. return fmt.Errorf("第%d个问答的标题包含不允许的内容", i+1)
  216. }
  217. if strings.Contains(strings.ToLower(content), dangerous) {
  218. return fmt.Errorf("第%d个问答的内容包含不允许的内容", i+1)
  219. }
  220. }
  221. }
  222. return nil
  223. }
  224. // GetAnnouncements 获取系统公告列表(返回最新的前20条)
  225. func GetAnnouncements() []map[string]interface{} {
  226. common.OptionMapRWMutex.RLock()
  227. announcementsStr, exists := common.OptionMap["Announcements"]
  228. common.OptionMapRWMutex.RUnlock()
  229. if !exists || announcementsStr == "" {
  230. return []map[string]interface{}{}
  231. }
  232. var announcements []map[string]interface{}
  233. if err := json.Unmarshal([]byte(announcementsStr), &announcements); err != nil {
  234. return []map[string]interface{}{}
  235. }
  236. // 按发布日期降序排序(最新的在前)
  237. sort.Slice(announcements, func(i, j int) bool {
  238. dateI, okI := announcements[i]["publishDate"].(string)
  239. dateJ, okJ := announcements[j]["publishDate"].(string)
  240. if !okI || !okJ {
  241. return false
  242. }
  243. timeI, errI := time.Parse(time.RFC3339, dateI)
  244. timeJ, errJ := time.Parse(time.RFC3339, dateJ)
  245. if errI != nil || errJ != nil {
  246. return false
  247. }
  248. return timeI.After(timeJ)
  249. })
  250. // 限制返回前20条
  251. if len(announcements) > 20 {
  252. announcements = announcements[:20]
  253. }
  254. return announcements
  255. }
  256. // GetFAQ 获取常见问答列表
  257. func GetFAQ() []map[string]interface{} {
  258. common.OptionMapRWMutex.RLock()
  259. faqStr, exists := common.OptionMap["FAQ"]
  260. common.OptionMapRWMutex.RUnlock()
  261. if !exists || faqStr == "" {
  262. return []map[string]interface{}{}
  263. }
  264. var faq []map[string]interface{}
  265. if err := json.Unmarshal([]byte(faqStr), &faq); err != nil {
  266. return []map[string]interface{}{}
  267. }
  268. return faq
  269. }