1
0

Server.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  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. h.hostRequest(w, r, sep)
  80. return
  81. }
  82. /*
  83. Root Router Handling
  84. */
  85. //Clean up the request URI
  86. proxyingPath := strings.TrimSpace(r.RequestURI)
  87. if !strings.HasSuffix(proxyingPath, "/") {
  88. potentialProxtEndpoint := h.Parent.getTargetProxyEndpointFromRequestURI(proxyingPath + "/")
  89. if potentialProxtEndpoint != nil {
  90. //Missing tailing slash. Redirect to target proxy endpoint
  91. http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect)
  92. } else {
  93. //Passthrough the request to root
  94. h.handleRootRouting(w, r)
  95. }
  96. } else {
  97. //No routing rules found.
  98. h.handleRootRouting(w, r)
  99. }
  100. }
  101. /*
  102. handleRootRouting
  103. This function handle root routing situations where there are no subdomain
  104. , vdir or special routing rule matches the requested URI.
  105. Once entered this routing segment, the root routing options will take over
  106. for the routing logic.
  107. */
  108. func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request) {
  109. domainOnly := r.Host
  110. if strings.Contains(r.Host, ":") {
  111. hostPath := strings.Split(r.Host, ":")
  112. domainOnly = hostPath[0]
  113. }
  114. //Get the proxy root config
  115. proot := h.Parent.Root
  116. switch proot.DefaultSiteOption {
  117. case DefaultSite_InternalStaticWebServer:
  118. fallthrough
  119. case DefaultSite_ReverseProxy:
  120. //They both share the same behavior
  121. h.vdirRequest(w, r, h.Parent.Root)
  122. case DefaultSite_Redirect:
  123. redirectTarget := strings.TrimSpace(proot.DefaultSiteValue)
  124. if redirectTarget == "" {
  125. redirectTarget = "about:blank"
  126. }
  127. //Check if it is an infinite loopback redirect
  128. parsedURL, err := url.Parse(proot.DefaultSiteValue)
  129. if err != nil {
  130. //Error when parsing target. Send to root
  131. h.vdirRequest(w, r, h.Parent.Root)
  132. return
  133. }
  134. hostname := parsedURL.Hostname()
  135. if hostname == domainOnly {
  136. h.logRequest(r, false, 500, "root-redirect", domainOnly)
  137. http.Error(w, "Loopback redirects due to invalid settings", 500)
  138. return
  139. }
  140. h.logRequest(r, false, 307, "root-redirect", domainOnly)
  141. http.Redirect(w, r, redirectTarget, http.StatusTemporaryRedirect)
  142. case DefaultSite_NotFoundPage:
  143. http.NotFound(w, r)
  144. }
  145. }
  146. // Handle access routing logic. Return true if the request is handled or blocked by the access control logic
  147. // if the return value is false, you can continue process the response writer
  148. func (h *ProxyHandler) handleAccessRouting(w http.ResponseWriter, r *http.Request) bool {
  149. //Check if this ip is in blacklist
  150. clientIpAddr := geodb.GetRequesterIP(r)
  151. if h.Parent.Option.GeodbStore.IsBlacklisted(clientIpAddr) {
  152. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  153. w.WriteHeader(http.StatusForbidden)
  154. template, err := os.ReadFile(filepath.Join(h.Parent.Option.WebDirectory, "templates/blacklist.html"))
  155. if err != nil {
  156. w.Write(page_forbidden)
  157. } else {
  158. w.Write(template)
  159. }
  160. h.logRequest(r, false, 403, "blacklist", "")
  161. return true
  162. }
  163. //Check if this ip is in whitelist
  164. if !h.Parent.Option.GeodbStore.IsWhitelisted(clientIpAddr) {
  165. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  166. w.WriteHeader(http.StatusForbidden)
  167. template, err := os.ReadFile(filepath.Join(h.Parent.Option.WebDirectory, "templates/whitelist.html"))
  168. if err != nil {
  169. w.Write(page_forbidden)
  170. } else {
  171. w.Write(template)
  172. }
  173. h.logRequest(r, false, 403, "whitelist", "")
  174. return true
  175. }
  176. return false
  177. }
  178. // Return if the given host is already topped (e.g. example.com or example.co.uk) instead of
  179. // a host with subdomain (e.g. test.example.com)
  180. func (h *ProxyHandler) isTopLevelRedirectableDomain(requestHost string) bool {
  181. parts := strings.Split(requestHost, ".")
  182. if len(parts) > 2 {
  183. //Cases where strange tld is used like .co.uk or .com.hk
  184. _, ok := h.Parent.tldMap[strings.Join(parts[1:], ".")]
  185. if ok {
  186. //Already topped
  187. return true
  188. }
  189. } else {
  190. //Already topped
  191. return true
  192. }
  193. return false
  194. }
  195. // GetTopLevelRedirectableDomain returns the toppest level of domain
  196. // that is redirectable. E.g. a.b.c.example.co.uk will return example.co.uk
  197. func (h *ProxyHandler) getTopLevelRedirectableDomain(unsetSubdomainHost string) (string, error) {
  198. parts := strings.Split(unsetSubdomainHost, ".")
  199. if h.isTopLevelRedirectableDomain(unsetSubdomainHost) {
  200. //Already topped
  201. return "", errors.New("already at top level domain")
  202. }
  203. for i := 0; i < len(parts); i++ {
  204. possibleTld := parts[i:]
  205. _, ok := h.Parent.tldMap[strings.Join(possibleTld, ".")]
  206. if ok {
  207. //This is tld length
  208. tld := strings.Join(parts[i-1:], ".")
  209. return "//" + tld, nil
  210. }
  211. }
  212. return "", errors.New("unsupported top level domain given")
  213. }