/vendor/github.com/mholt/caddy/caddyhttp/rewrite/rewrite.go

https://github.com/ehazlett/stellar · Go · 263 lines · 160 code · 41 blank · 62 comment · 40 complexity · 86f5242a120a457b21bebe5ccf6bc330 MD5 · raw file

  1. // Copyright 2015 Light Code Labs, LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. // Package rewrite is middleware for rewriting requests internally to
  15. // a different path.
  16. package rewrite
  17. import (
  18. "fmt"
  19. "net/http"
  20. "net/url"
  21. "path"
  22. "path/filepath"
  23. "regexp"
  24. "strings"
  25. "github.com/mholt/caddy/caddyhttp/httpserver"
  26. )
  27. // Result is the result of a rewrite
  28. type Result int
  29. const (
  30. // RewriteIgnored is returned when rewrite is not done on request.
  31. RewriteIgnored Result = iota
  32. // RewriteDone is returned when rewrite is done on request.
  33. RewriteDone
  34. )
  35. // Rewrite is middleware to rewrite request locations internally before being handled.
  36. type Rewrite struct {
  37. Next httpserver.Handler
  38. FileSys http.FileSystem
  39. Rules []httpserver.HandlerConfig
  40. }
  41. // ServeHTTP implements the httpserver.Handler interface.
  42. func (rw Rewrite) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
  43. if rule := httpserver.ConfigSelector(rw.Rules).Select(r); rule != nil {
  44. rule.(Rule).Rewrite(rw.FileSys, r)
  45. }
  46. return rw.Next.ServeHTTP(w, r)
  47. }
  48. // Rule describes an internal location rewrite rule.
  49. type Rule interface {
  50. httpserver.HandlerConfig
  51. // Rewrite rewrites the internal location of the current request.
  52. Rewrite(http.FileSystem, *http.Request) Result
  53. }
  54. // SimpleRule is a simple rewrite rule.
  55. type SimpleRule struct {
  56. Regexp *regexp.Regexp
  57. To string
  58. Negate bool
  59. }
  60. // NewSimpleRule creates a new Simple Rule
  61. func NewSimpleRule(from, to string, negate bool) (*SimpleRule, error) {
  62. r, err := regexp.Compile(from)
  63. if err != nil {
  64. return nil, err
  65. }
  66. return &SimpleRule{
  67. Regexp: r,
  68. To: to,
  69. Negate: negate,
  70. }, nil
  71. }
  72. // BasePath satisfies httpserver.Config
  73. func (s SimpleRule) BasePath() string { return "/" }
  74. // Match satisfies httpserver.Config
  75. func (s *SimpleRule) Match(r *http.Request) bool {
  76. matches := regexpMatches(s.Regexp, "/", r.URL.Path)
  77. if s.Negate {
  78. return len(matches) == 0
  79. }
  80. return len(matches) > 0
  81. }
  82. // Rewrite rewrites the internal location of the current request.
  83. func (s *SimpleRule) Rewrite(fs http.FileSystem, r *http.Request) Result {
  84. // attempt rewrite
  85. return To(fs, r, s.To, newReplacer(r))
  86. }
  87. // ComplexRule is a rewrite rule based on a regular expression
  88. type ComplexRule struct {
  89. // Path base. Request to this path and subpaths will be rewritten
  90. Base string
  91. // Path to rewrite to
  92. To string
  93. // Extensions to filter by
  94. Exts []string
  95. // Request matcher
  96. httpserver.RequestMatcher
  97. Regexp *regexp.Regexp
  98. }
  99. // NewComplexRule creates a new RegexpRule. It returns an error if regexp
  100. // pattern (pattern) or extensions (ext) are invalid.
  101. func NewComplexRule(base, pattern, to string, ext []string, matcher httpserver.RequestMatcher) (ComplexRule, error) {
  102. // validate regexp if present
  103. var r *regexp.Regexp
  104. if pattern != "" {
  105. var err error
  106. r, err = regexp.Compile(pattern)
  107. if err != nil {
  108. return ComplexRule{}, err
  109. }
  110. }
  111. // validate extensions if present
  112. for _, v := range ext {
  113. if len(v) < 2 || (len(v) < 3 && v[0] == '!') {
  114. // check if no extension is specified
  115. if v != "/" && v != "!/" {
  116. return ComplexRule{}, fmt.Errorf("invalid extension %v", v)
  117. }
  118. }
  119. }
  120. // use both IfMatcher and PathMatcher
  121. matcher = httpserver.MergeRequestMatchers(
  122. // If condition matcher
  123. matcher,
  124. // Base path matcher
  125. httpserver.PathMatcher(base),
  126. )
  127. return ComplexRule{
  128. Base: base,
  129. To: to,
  130. Exts: ext,
  131. RequestMatcher: matcher,
  132. Regexp: r,
  133. }, nil
  134. }
  135. // BasePath satisfies httpserver.Config
  136. func (r ComplexRule) BasePath() string { return r.Base }
  137. // Match satisfies httpserver.Config.
  138. //
  139. // Though ComplexRule embeds a RequestMatcher, additional
  140. // checks are needed which requires a custom implementation.
  141. func (r ComplexRule) Match(req *http.Request) bool {
  142. // validate RequestMatcher
  143. // includes if and path
  144. if !r.RequestMatcher.Match(req) {
  145. return false
  146. }
  147. // validate extensions
  148. if !r.matchExt(req.URL.Path) {
  149. return false
  150. }
  151. // if regex is nil, ignore
  152. if r.Regexp == nil {
  153. return true
  154. }
  155. // otherwise validate regex
  156. return regexpMatches(r.Regexp, r.Base, req.URL.Path) != nil
  157. }
  158. // Rewrite rewrites the internal location of the current request.
  159. func (r ComplexRule) Rewrite(fs http.FileSystem, req *http.Request) (re Result) {
  160. replacer := newReplacer(req)
  161. // validate regexp if present
  162. if r.Regexp != nil {
  163. matches := regexpMatches(r.Regexp, r.Base, req.URL.Path)
  164. switch len(matches) {
  165. case 0:
  166. // no match
  167. return
  168. default:
  169. // set regexp match variables {1}, {2} ...
  170. // url escaped values of ? and #.
  171. q, f := url.QueryEscape("?"), url.QueryEscape("#")
  172. for i := 1; i < len(matches); i++ {
  173. // Special case of unescaped # and ? by stdlib regexp.
  174. // Reverse the unescape.
  175. if strings.ContainsAny(matches[i], "?#") {
  176. matches[i] = strings.NewReplacer("?", q, "#", f).Replace(matches[i])
  177. }
  178. replacer.Set(fmt.Sprint(i), matches[i])
  179. }
  180. }
  181. }
  182. // attempt rewrite
  183. return To(fs, req, r.To, replacer)
  184. }
  185. // matchExt matches rPath against registered file extensions.
  186. // Returns true if a match is found and false otherwise.
  187. func (r ComplexRule) matchExt(rPath string) bool {
  188. f := filepath.Base(rPath)
  189. ext := path.Ext(f)
  190. if ext == "" {
  191. ext = "/"
  192. }
  193. mustUse := false
  194. for _, v := range r.Exts {
  195. use := true
  196. if v[0] == '!' {
  197. use = false
  198. v = v[1:]
  199. }
  200. if use {
  201. mustUse = true
  202. }
  203. if ext == v {
  204. return use
  205. }
  206. }
  207. return !mustUse
  208. }
  209. func regexpMatches(regexp *regexp.Regexp, base, rPath string) []string {
  210. if regexp != nil {
  211. // include trailing slash in regexp if present
  212. start := len(base)
  213. if strings.HasSuffix(base, "/") {
  214. start--
  215. }
  216. return regexp.FindStringSubmatch(rPath[start:])
  217. }
  218. return nil
  219. }
  220. func newReplacer(r *http.Request) httpserver.Replacer {
  221. return httpserver.NewReplacer(r, nil, "")
  222. }