Server.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. package dynamicproxy
  2. import (
  3. _ "embed"
  4. "errors"
  5. "log"
  6. "net/http"
  7. "net/url"
  8. "os"
  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. Subdomain Routing
  70. */
  71. if strings.Contains(r.Host, ".") {
  72. //This might be a subdomain. See if there are any subdomain proxy router for this
  73. sep := h.Parent.getSubdomainProxyEndpointFromHostname(domainOnly)
  74. if sep != nil {
  75. if sep.RequireBasicAuth {
  76. err := h.handleBasicAuthRouting(w, r, sep)
  77. if err != nil {
  78. return
  79. }
  80. }
  81. h.subdomainRequest(w, r, sep)
  82. return
  83. }
  84. }
  85. /*
  86. Virtual Directory Routing
  87. */
  88. //Clean up the request URI
  89. proxyingPath := strings.TrimSpace(r.RequestURI)
  90. targetProxyEndpoint := h.Parent.getTargetProxyEndpointFromRequestURI(proxyingPath)
  91. if targetProxyEndpoint != nil {
  92. if targetProxyEndpoint.RequireBasicAuth {
  93. err := h.handleBasicAuthRouting(w, r, targetProxyEndpoint)
  94. if err != nil {
  95. return
  96. }
  97. }
  98. h.proxyRequest(w, r, targetProxyEndpoint)
  99. } else if !strings.HasSuffix(proxyingPath, "/") {
  100. potentialProxtEndpoint := h.Parent.getTargetProxyEndpointFromRequestURI(proxyingPath + "/")
  101. if potentialProxtEndpoint != nil {
  102. //Missing tailing slash. Redirect to target proxy endpoint
  103. http.Redirect(w, r, r.RequestURI+"/", http.StatusTemporaryRedirect)
  104. } else {
  105. //Passthrough the request to root
  106. h.handleRootRouting(w, r)
  107. }
  108. } else {
  109. //No routing rules found.
  110. h.handleRootRouting(w, r)
  111. }
  112. }
  113. /*
  114. handleRootRouting
  115. This function handle root routing situations where there are no subdomain
  116. , vdir or special routing rule matches the requested URI.
  117. Once entered this routing segment, the root routing options will take over
  118. for the routing logic.
  119. */
  120. func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request) {
  121. domainOnly := r.Host
  122. if strings.Contains(r.Host, ":") {
  123. hostPath := strings.Split(r.Host, ":")
  124. domainOnly = hostPath[0]
  125. }
  126. if h.Parent.RootRoutingOptions.EnableRedirectForUnsetRules {
  127. //Route to custom domain
  128. if h.Parent.RootRoutingOptions.UnsetRuleRedirectTarget == "" {
  129. //Not set. Redirect to first level of domain redirectable
  130. fld, err := h.getTopLevelRedirectableDomain(domainOnly)
  131. if err != nil {
  132. //Redirect to proxy root
  133. log.Println("[Router] Unable to resolve top level redirectable domain: " + err.Error())
  134. h.proxyRequest(w, r, h.Parent.Root)
  135. } else {
  136. log.Println("[Router] Redirecting request from " + domainOnly + " to " + fld)
  137. h.logRequest(r, false, 307, "root-redirect", domainOnly)
  138. http.Redirect(w, r, fld, http.StatusTemporaryRedirect)
  139. }
  140. return
  141. } else {
  142. //Validate the redirection target URL
  143. parsedURL, err := url.Parse(h.Parent.RootRoutingOptions.UnsetRuleRedirectTarget)
  144. if err != nil {
  145. //Error when parsing target. Send to root
  146. h.proxyRequest(w, r, h.Parent.Root)
  147. return
  148. }
  149. hostname := parsedURL.Hostname()
  150. if domainOnly != hostname {
  151. //Redirect to target
  152. h.logRequest(r, false, 307, "root-redirect", domainOnly)
  153. http.Redirect(w, r, h.Parent.RootRoutingOptions.UnsetRuleRedirectTarget, http.StatusTemporaryRedirect)
  154. return
  155. } else {
  156. //Loopback request due to bad settings (Shd leave it empty)
  157. //Forward it to root proxy
  158. h.proxyRequest(w, r, h.Parent.Root)
  159. }
  160. }
  161. } else {
  162. //Route to root
  163. h.proxyRequest(w, r, h.Parent.Root)
  164. }
  165. }
  166. // Handle access routing logic. Return true if the request is handled or blocked by the access control logic
  167. // if the return value is false, you can continue process the response writer
  168. func (h *ProxyHandler) handleAccessRouting(w http.ResponseWriter, r *http.Request) bool {
  169. //Check if this ip is in blacklist
  170. clientIpAddr := geodb.GetRequesterIP(r)
  171. if h.Parent.Option.GeodbStore.IsBlacklisted(clientIpAddr) {
  172. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  173. w.WriteHeader(http.StatusForbidden)
  174. template, err := os.ReadFile("./web/forbidden.html")
  175. if err != nil {
  176. w.Write([]byte("403 - Forbidden"))
  177. } else {
  178. w.Write(template)
  179. }
  180. h.logRequest(r, false, 403, "blacklist", "")
  181. return true
  182. }
  183. //Check if this ip is in whitelist
  184. if !h.Parent.Option.GeodbStore.IsWhitelisted(clientIpAddr) {
  185. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  186. w.WriteHeader(http.StatusForbidden)
  187. template, err := os.ReadFile("./web/forbidden.html")
  188. if err != nil {
  189. w.Write([]byte("403 - Forbidden"))
  190. } else {
  191. w.Write(template)
  192. }
  193. h.logRequest(r, false, 403, "whitelist", "")
  194. return true
  195. }
  196. return false
  197. }
  198. // GetTopLevelRedirectableDomain returns the toppest level of domain
  199. // that is redirectable. E.g. a.b.c.example.co.uk will return example.co.uk
  200. func (h *ProxyHandler) getTopLevelRedirectableDomain(unsetSubdomainHost string) (string, error) {
  201. parts := strings.Split(unsetSubdomainHost, ".")
  202. if len(parts) > 2 {
  203. //Cases where strange tld is used like .co.uk or .com.hk
  204. _, ok := h.Parent.tldMap[strings.Join(parts[1:], ".")]
  205. if ok {
  206. //Already topped
  207. return "", errors.New("already at top level domain")
  208. }
  209. } else {
  210. //Already topped
  211. return "", errors.New("already at top level domain")
  212. }
  213. for i := 0; i < len(parts); i++ {
  214. possibleTld := parts[i:]
  215. _, ok := h.Parent.tldMap[strings.Join(possibleTld, ".")]
  216. if ok {
  217. //This is tld length
  218. tld := strings.Join(parts[i-1:], ".")
  219. return "//" + tld, nil
  220. }
  221. }
  222. return "", errors.New("unsupported top level domain given")
  223. }