pathrule.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. package pathrule
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "net/http"
  6. "os"
  7. "path/filepath"
  8. "strings"
  9. "imuslab.com/zoraxy/mod/utils"
  10. )
  11. /*
  12. Pathblock.go
  13. This script block off some of the specific pathname in access
  14. For example, this module can help you block request for a particular
  15. apache directory or functional endpoints like /.well-known/ when you
  16. are not using it
  17. */
  18. type Options struct {
  19. ConfigFolder string //The folder to store the path blocking config files
  20. }
  21. type BlockingPath struct {
  22. UUID string
  23. MatchingPath string
  24. ExactMatch bool
  25. StatusCode int
  26. CustomHeaders http.Header
  27. CustomHTML []byte
  28. Enabled bool
  29. CaseSenitive bool
  30. }
  31. type Handler struct {
  32. Options *Options
  33. BlockingPaths []*BlockingPath
  34. }
  35. // Create a new path blocker handler
  36. func NewPathBlocker(options *Options) *Handler {
  37. //Create folder if not exists
  38. if !utils.FileExists(options.ConfigFolder) {
  39. os.Mkdir(options.ConfigFolder, 0775)
  40. }
  41. //Load the configs from file
  42. //TODO
  43. return &Handler{
  44. Options: options,
  45. BlockingPaths: []*BlockingPath{},
  46. }
  47. }
  48. func (h *Handler) ListBlockingPath() []*BlockingPath {
  49. return h.BlockingPaths
  50. }
  51. // Get the blocker from matching path (path match, ignore tailing slash)
  52. func (h *Handler) GetPathBlockerFromMatchingPath(matchingPath string) *BlockingPath {
  53. for _, blocker := range h.BlockingPaths {
  54. if blocker.MatchingPath == matchingPath {
  55. return blocker
  56. } else if strings.TrimSuffix(blocker.MatchingPath, "/") == strings.TrimSuffix(matchingPath, "/") {
  57. return blocker
  58. }
  59. }
  60. return nil
  61. }
  62. func (h *Handler) GetPathBlockerFromUUID(UUID string) *BlockingPath {
  63. for _, blocker := range h.BlockingPaths {
  64. if blocker.UUID == UUID {
  65. return blocker
  66. }
  67. }
  68. return nil
  69. }
  70. func (h *Handler) AddBlockingPath(pathBlocker *BlockingPath) error {
  71. //Check if the blocker exists
  72. blockerPath := pathBlocker.MatchingPath
  73. targetBlocker := h.GetPathBlockerFromMatchingPath(blockerPath)
  74. if targetBlocker != nil {
  75. //Blocker with the same matching path already exists
  76. return errors.New("path blocker with the same path already exists")
  77. }
  78. h.BlockingPaths = append(h.BlockingPaths, pathBlocker)
  79. //Write the new config to file
  80. return h.SaveBlockerToFile(pathBlocker)
  81. }
  82. func (h *Handler) RemoveBlockingPathByUUID(uuid string) error {
  83. newBlockingList := []*BlockingPath{}
  84. for _, thisBlocker := range h.BlockingPaths {
  85. if thisBlocker.UUID != uuid {
  86. newBlockingList = append(newBlockingList, thisBlocker)
  87. }
  88. }
  89. if len(h.BlockingPaths) == len(newBlockingList) {
  90. //Nothing is removed
  91. return errors.New("given matching path blocker not exists")
  92. }
  93. h.BlockingPaths = newBlockingList
  94. return h.RemoveBlockerFromFile(uuid)
  95. }
  96. func (h *Handler) SaveBlockerToFile(pathBlocker *BlockingPath) error {
  97. saveFilename := filepath.Join(h.Options.ConfigFolder, pathBlocker.UUID)
  98. js, _ := json.MarshalIndent(pathBlocker, "", " ")
  99. return os.WriteFile(saveFilename, js, 0775)
  100. }
  101. func (h *Handler) RemoveBlockerFromFile(uuid string) error {
  102. expectedConfigFile := filepath.Join(h.Options.ConfigFolder, uuid)
  103. if !utils.FileExists(expectedConfigFile) {
  104. return errors.New("config file not found on disk")
  105. }
  106. return os.Remove(expectedConfigFile)
  107. }
  108. // Get all the matching blockers for the given URL path
  109. // return all the path blockers and the max length matching rule
  110. func (h *Handler) GetMatchingBlockers(urlPath string) ([]*BlockingPath, *BlockingPath) {
  111. urlPath = strings.TrimSuffix(urlPath, "/")
  112. matchingBlockers := []*BlockingPath{}
  113. var longestMatchingPrefix *BlockingPath = nil
  114. for _, thisBlocker := range h.BlockingPaths {
  115. if thisBlocker.Enabled == false {
  116. //This blocker is not enabled. Ignore this
  117. continue
  118. }
  119. incomingURLPath := urlPath
  120. matchingPath := strings.TrimSuffix(thisBlocker.MatchingPath, "/")
  121. if !thisBlocker.CaseSenitive {
  122. //This is not case sensitive
  123. incomingURLPath = strings.ToLower(incomingURLPath)
  124. matchingPath = strings.ToLower(matchingPath)
  125. }
  126. if matchingPath == incomingURLPath {
  127. //This blocker have exact url path match
  128. matchingBlockers = append(matchingBlockers, thisBlocker)
  129. if longestMatchingPrefix == nil || len(thisBlocker.MatchingPath) > len(longestMatchingPrefix.MatchingPath) {
  130. longestMatchingPrefix = thisBlocker
  131. }
  132. continue
  133. }
  134. if !thisBlocker.ExactMatch && strings.HasPrefix(incomingURLPath, matchingPath) {
  135. //This blocker have prefix url match
  136. matchingBlockers = append(matchingBlockers, thisBlocker)
  137. if longestMatchingPrefix == nil || len(thisBlocker.MatchingPath) > len(longestMatchingPrefix.MatchingPath) {
  138. longestMatchingPrefix = thisBlocker
  139. }
  140. continue
  141. }
  142. }
  143. return matchingBlockers, longestMatchingPrefix
  144. }