Server.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  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 && !sep.Disabled {
  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 && !targetProxyEndpoint.Disabled {
  83. //Virtual directory routing rule found. Route via vdir mode
  84. h.vdirRequest(w, r, targetProxyEndpoint)
  85. return
  86. } else if !strings.HasSuffix(proxyingPath, "/") && sep.ProxyType != ProxyType_Root {
  87. potentialProxtEndpoint := sep.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath + "/")
  88. if potentialProxtEndpoint != nil && !targetProxyEndpoint.Disabled {
  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. //Check if any virtual directory rules matches
  138. proxyingPath := strings.TrimSpace(r.RequestURI)
  139. targetProxyEndpoint := proot.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath)
  140. if targetProxyEndpoint != nil && !targetProxyEndpoint.Disabled {
  141. //Virtual directory routing rule found. Route via vdir mode
  142. h.vdirRequest(w, r, targetProxyEndpoint)
  143. return
  144. } else if !strings.HasSuffix(proxyingPath, "/") && proot.ProxyType != ProxyType_Root {
  145. potentialProxtEndpoint := proot.GetVirtualDirectoryHandlerFromRequestURI(proxyingPath + "/")
  146. if potentialProxtEndpoint != nil && !targetProxyEndpoint.Disabled {
  147. //Missing tailing slash. Redirect to target proxy endpoint
  148. http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect)
  149. return
  150. }
  151. }
  152. //No vdir match. Route via root router
  153. h.hostRequest(w, r, h.Parent.Root)
  154. case DefaultSite_Redirect:
  155. redirectTarget := strings.TrimSpace(proot.DefaultSiteValue)
  156. if redirectTarget == "" {
  157. redirectTarget = "about:blank"
  158. }
  159. //Check if it is an infinite loopback redirect
  160. parsedURL, err := url.Parse(proot.DefaultSiteValue)
  161. if err != nil {
  162. //Error when parsing target. Send to root
  163. h.hostRequest(w, r, h.Parent.Root)
  164. return
  165. }
  166. hostname := parsedURL.Hostname()
  167. if hostname == domainOnly {
  168. h.logRequest(r, false, 500, "root-redirect", domainOnly)
  169. http.Error(w, "Loopback redirects due to invalid settings", 500)
  170. return
  171. }
  172. h.logRequest(r, false, 307, "root-redirect", domainOnly)
  173. http.Redirect(w, r, redirectTarget, http.StatusTemporaryRedirect)
  174. case DefaultSite_NotFoundPage:
  175. http.NotFound(w, r)
  176. }
  177. }
  178. // Handle access routing logic. Return true if the request is handled or blocked by the access control logic
  179. // if the return value is false, you can continue process the response writer
  180. func (h *ProxyHandler) handleAccessRouting(w http.ResponseWriter, r *http.Request) bool {
  181. //Check if this ip is in blacklist
  182. clientIpAddr := geodb.GetRequesterIP(r)
  183. if h.Parent.Option.GeodbStore.IsBlacklisted(clientIpAddr) {
  184. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  185. w.WriteHeader(http.StatusForbidden)
  186. template, err := os.ReadFile(filepath.Join(h.Parent.Option.WebDirectory, "templates/blacklist.html"))
  187. if err != nil {
  188. w.Write(page_forbidden)
  189. } else {
  190. w.Write(template)
  191. }
  192. h.logRequest(r, false, 403, "blacklist", "")
  193. return true
  194. }
  195. //Check if this ip is in whitelist
  196. if !h.Parent.Option.GeodbStore.IsWhitelisted(clientIpAddr) {
  197. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  198. w.WriteHeader(http.StatusForbidden)
  199. template, err := os.ReadFile(filepath.Join(h.Parent.Option.WebDirectory, "templates/whitelist.html"))
  200. if err != nil {
  201. w.Write(page_forbidden)
  202. } else {
  203. w.Write(template)
  204. }
  205. h.logRequest(r, false, 403, "whitelist", "")
  206. return true
  207. }
  208. return false
  209. }
  210. // Return if the given host is already topped (e.g. example.com or example.co.uk) instead of
  211. // a host with subdomain (e.g. test.example.com)
  212. func (h *ProxyHandler) isTopLevelRedirectableDomain(requestHost string) bool {
  213. parts := strings.Split(requestHost, ".")
  214. if len(parts) > 2 {
  215. //Cases where strange tld is used like .co.uk or .com.hk
  216. _, ok := h.Parent.tldMap[strings.Join(parts[1:], ".")]
  217. if ok {
  218. //Already topped
  219. return true
  220. }
  221. } else {
  222. //Already topped
  223. return true
  224. }
  225. return false
  226. }
  227. // GetTopLevelRedirectableDomain returns the toppest level of domain
  228. // that is redirectable. E.g. a.b.c.example.co.uk will return example.co.uk
  229. func (h *ProxyHandler) getTopLevelRedirectableDomain(unsetSubdomainHost string) (string, error) {
  230. parts := strings.Split(unsetSubdomainHost, ".")
  231. if h.isTopLevelRedirectableDomain(unsetSubdomainHost) {
  232. //Already topped
  233. return "", errors.New("already at top level domain")
  234. }
  235. for i := 0; i < len(parts); i++ {
  236. possibleTld := parts[i:]
  237. _, ok := h.Parent.tldMap[strings.Join(possibleTld, ".")]
  238. if ok {
  239. //This is tld length
  240. tld := strings.Join(parts[i-1:], ".")
  241. return "//" + tld, nil
  242. }
  243. }
  244. return "", errors.New("unsupported top level domain given")
  245. }