Server.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. package dynamicproxy
  2. import (
  3. _ "embed"
  4. "errors"
  5. "net/http"
  6. "net/url"
  7. "os"
  8. "path/filepath"
  9. "strings"
  10. "imuslab.com/zoraxy/mod/geodb"
  11. )
  12. /*
  13. Server.go
  14. Main server for dynamic proxy core
  15. Routing Handler Priority (High to Low)
  16. - Blacklist
  17. - Whitelist
  18. - Redirectable
  19. - Subdomain Routing
  20. - Vitrual Directory Routing
  21. */
  22. var (
  23. //go:embed tld.json
  24. rawTldMap []byte
  25. )
  26. func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  27. /*
  28. Special Routing Rules, bypass most of the limitations
  29. */
  30. //Check if there are external routing rule matches.
  31. //If yes, route them via external rr
  32. matchedRoutingRule := h.Parent.GetMatchingRoutingRule(r)
  33. if matchedRoutingRule != nil {
  34. //Matching routing rule found. Let the sub-router handle it
  35. if matchedRoutingRule.UseSystemAccessControl {
  36. //This matching rule request system access control.
  37. //check access logic
  38. respWritten := h.handleAccessRouting(w, r)
  39. if respWritten {
  40. return
  41. }
  42. }
  43. matchedRoutingRule.Route(w, r)
  44. return
  45. }
  46. /*
  47. General Access Check
  48. */
  49. respWritten := h.handleAccessRouting(w, r)
  50. if respWritten {
  51. return
  52. }
  53. /*
  54. Redirection Routing
  55. */
  56. //Check if this is a redirection url
  57. if h.Parent.Option.RedirectRuleTable.IsRedirectable(r) {
  58. statusCode := h.Parent.Option.RedirectRuleTable.HandleRedirect(w, r)
  59. h.logRequest(r, statusCode != 500, statusCode, "redirect", "")
  60. return
  61. }
  62. //Extract request host to see if it is virtual directory or subdomain
  63. domainOnly := r.Host
  64. if strings.Contains(r.Host, ":") {
  65. hostPath := strings.Split(r.Host, ":")
  66. domainOnly = hostPath[0]
  67. }
  68. /*
  69. Host Routing
  70. */
  71. sep := h.Parent.getProxyEndpointFromHostname(domainOnly)
  72. if sep != nil {
  73. if sep.RequireBasicAuth {
  74. err := h.handleBasicAuthRouting(w, r, sep)
  75. if err != nil {
  76. return
  77. }
  78. }
  79. //Check if any virtual directory rules matches
  80. proxyingPath := strings.TrimSpace(r.RequestURI)
  81. targetProxyEndpoint := sep.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath)
  82. if targetProxyEndpoint != nil {
  83. //Virtual directory routing rule found. Route via vdir mode
  84. h.vdirRequest(w, r, targetProxyEndpoint)
  85. return
  86. } else if !strings.HasSuffix(proxyingPath, "/") {
  87. potentialProxtEndpoint := sep.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath + "/")
  88. if potentialProxtEndpoint != nil {
  89. //Missing tailing slash. Redirect to target proxy endpoint
  90. http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect)
  91. return
  92. }
  93. }
  94. //Fallback to handle by the host proxy forwarder
  95. h.hostRequest(w, r, sep)
  96. return
  97. }
  98. /*
  99. Root Router Handling
  100. */
  101. //Clean up the request URI
  102. proxyingPath := strings.TrimSpace(r.RequestURI)
  103. if !strings.HasSuffix(proxyingPath, "/") {
  104. potentialProxtEndpoint := h.Parent.getTargetProxyEndpointFromRequestURI(proxyingPath + "/")
  105. if potentialProxtEndpoint != nil {
  106. //Missing tailing slash. Redirect to target proxy endpoint
  107. http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect)
  108. } else {
  109. //Passthrough the request to root
  110. h.handleRootRouting(w, r)
  111. }
  112. } else {
  113. //No routing rules found.
  114. h.handleRootRouting(w, r)
  115. }
  116. }
  117. /*
  118. handleRootRouting
  119. This function handle root routing situations where there are no subdomain
  120. , vdir or special routing rule matches the requested URI.
  121. Once entered this routing segment, the root routing options will take over
  122. for the routing logic.
  123. */
  124. func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request) {
  125. domainOnly := r.Host
  126. if strings.Contains(r.Host, ":") {
  127. hostPath := strings.Split(r.Host, ":")
  128. domainOnly = hostPath[0]
  129. }
  130. //Get the proxy root config
  131. proot := h.Parent.Root
  132. switch proot.DefaultSiteOption {
  133. case DefaultSite_InternalStaticWebServer:
  134. fallthrough
  135. case DefaultSite_ReverseProxy:
  136. //They both share the same behavior
  137. h.vdirRequest(w, r, h.Parent.Root)
  138. case DefaultSite_Redirect:
  139. redirectTarget := strings.TrimSpace(proot.DefaultSiteValue)
  140. if redirectTarget == "" {
  141. redirectTarget = "about:blank"
  142. }
  143. //Check if it is an infinite loopback redirect
  144. parsedURL, err := url.Parse(proot.DefaultSiteValue)
  145. if err != nil {
  146. //Error when parsing target. Send to root
  147. h.vdirRequest(w, r, h.Parent.Root)
  148. return
  149. }
  150. hostname := parsedURL.Hostname()
  151. if hostname == domainOnly {
  152. h.logRequest(r, false, 500, "root-redirect", domainOnly)
  153. http.Error(w, "Loopback redirects due to invalid settings", 500)
  154. return
  155. }
  156. h.logRequest(r, false, 307, "root-redirect", domainOnly)
  157. http.Redirect(w, r, redirectTarget, http.StatusTemporaryRedirect)
  158. case DefaultSite_NotFoundPage:
  159. http.NotFound(w, r)
  160. }
  161. }
  162. // Handle access routing logic. Return true if the request is handled or blocked by the access control logic
  163. // if the return value is false, you can continue process the response writer
  164. func (h *ProxyHandler) handleAccessRouting(w http.ResponseWriter, r *http.Request) bool {
  165. //Check if this ip is in blacklist
  166. clientIpAddr := geodb.GetRequesterIP(r)
  167. if h.Parent.Option.GeodbStore.IsBlacklisted(clientIpAddr) {
  168. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  169. w.WriteHeader(http.StatusForbidden)
  170. template, err := os.ReadFile(filepath.Join(h.Parent.Option.WebDirectory, "templates/blacklist.html"))
  171. if err != nil {
  172. w.Write(page_forbidden)
  173. } else {
  174. w.Write(template)
  175. }
  176. h.logRequest(r, false, 403, "blacklist", "")
  177. return true
  178. }
  179. //Check if this ip is in whitelist
  180. if !h.Parent.Option.GeodbStore.IsWhitelisted(clientIpAddr) {
  181. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  182. w.WriteHeader(http.StatusForbidden)
  183. template, err := os.ReadFile(filepath.Join(h.Parent.Option.WebDirectory, "templates/whitelist.html"))
  184. if err != nil {
  185. w.Write(page_forbidden)
  186. } else {
  187. w.Write(template)
  188. }
  189. h.logRequest(r, false, 403, "whitelist", "")
  190. return true
  191. }
  192. return false
  193. }
  194. // Return if the given host is already topped (e.g. example.com or example.co.uk) instead of
  195. // a host with subdomain (e.g. test.example.com)
  196. func (h *ProxyHandler) isTopLevelRedirectableDomain(requestHost string) bool {
  197. parts := strings.Split(requestHost, ".")
  198. if len(parts) > 2 {
  199. //Cases where strange tld is used like .co.uk or .com.hk
  200. _, ok := h.Parent.tldMap[strings.Join(parts[1:], ".")]
  201. if ok {
  202. //Already topped
  203. return true
  204. }
  205. } else {
  206. //Already topped
  207. return true
  208. }
  209. return false
  210. }
  211. // GetTopLevelRedirectableDomain returns the toppest level of domain
  212. // that is redirectable. E.g. a.b.c.example.co.uk will return example.co.uk
  213. func (h *ProxyHandler) getTopLevelRedirectableDomain(unsetSubdomainHost string) (string, error) {
  214. parts := strings.Split(unsetSubdomainHost, ".")
  215. if h.isTopLevelRedirectableDomain(unsetSubdomainHost) {
  216. //Already topped
  217. return "", errors.New("already at top level domain")
  218. }
  219. for i := 0; i < len(parts); i++ {
  220. possibleTld := parts[i:]
  221. _, ok := h.Parent.tldMap[strings.Join(possibleTld, ".")]
  222. if ok {
  223. //This is tld length
  224. tld := strings.Join(parts[i-1:], ".")
  225. return "//" + tld, nil
  226. }
  227. }
  228. return "", errors.New("unsupported top level domain given")
  229. }