utils.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. package dpcore
  2. import (
  3. "bytes"
  4. "io"
  5. "net"
  6. "net/http"
  7. "net/url"
  8. "strings"
  9. )
  10. // replaceLocationHost rewrite the backend server's location header to a new URL based on the given proxy rules
  11. // If you have issues with tailing slash, you can try to fix them here (and remember to PR :D )
  12. func replaceLocationHost(urlString string, rrr *ResponseRewriteRuleSet, useTLS bool) (string, error) {
  13. u, err := url.Parse(urlString)
  14. if err != nil {
  15. return "", err
  16. }
  17. //Update the schemetic if the proxying target is http
  18. //but exposed as https to the internet via Zoraxy
  19. if useTLS {
  20. u.Scheme = "https"
  21. } else {
  22. u.Scheme = "http"
  23. }
  24. //Issue #39: Check if it is location target match the proxying domain
  25. //E.g. Proxy config: blog.example.com -> example.com/blog
  26. //Check if it is actually redirecting to example.com instead of a new domain
  27. //like news.example.com.
  28. // The later check bypass apache screw up method of redirection header
  29. // e.g. https://imuslab.com -> http://imuslab.com:443
  30. if rrr.ProxyDomain != u.Host && !strings.Contains(u.Host, rrr.OriginalHost+":") {
  31. //New location domain not matching proxy target domain.
  32. //Do not modify location header
  33. return urlString, nil
  34. }
  35. u.Host = rrr.OriginalHost
  36. if strings.Contains(rrr.ProxyDomain, "/") {
  37. //The proxy domain itself seems contain subpath.
  38. //Trim it off from Location header to prevent URL segment duplicate
  39. //E.g. Proxy config: blog.example.com -> example.com/blog
  40. //Location Header: /blog/post?id=1
  41. //Expected Location Header send to client:
  42. // blog.example.com/post?id=1 instead of blog.example.com/blog/post?id=1
  43. ProxyDomainURL := "http://" + rrr.ProxyDomain
  44. if rrr.UseTLS {
  45. ProxyDomainURL = "https://" + rrr.ProxyDomain
  46. }
  47. ru, err := url.Parse(ProxyDomainURL)
  48. if err == nil {
  49. //Trim off the subpath
  50. u.Path = strings.TrimPrefix(u.Path, ru.Path)
  51. }
  52. }
  53. return u.String(), nil
  54. }
  55. // Debug functions for replaceLocationHost
  56. func ReplaceLocationHost(urlString string, rrr *ResponseRewriteRuleSet, useTLS bool) (string, error) {
  57. return replaceLocationHost(urlString, rrr, useTLS)
  58. }
  59. // isExternalDomainName check and return if the hostname is external domain name (e.g. github.com)
  60. // instead of internal (like 192.168.1.202:8443 (ip address) or domains end with .local or .internal)
  61. func isExternalDomainName(hostname string) bool {
  62. host, _, err := net.SplitHostPort(hostname)
  63. if err != nil {
  64. //hostname doesnt contain port
  65. ip := net.ParseIP(hostname)
  66. if ip != nil {
  67. //IP address, not a domain name
  68. return false
  69. }
  70. } else {
  71. //Hostname contain port, use hostname without port to check if it is ip
  72. ip := net.ParseIP(host)
  73. if ip != nil {
  74. //IP address, not a domain name
  75. return false
  76. }
  77. }
  78. //Check if it is internal DNS assigned domains
  79. internalDNSTLD := []string{".local", ".internal", ".localhost", ".home.arpa"}
  80. for _, tld := range internalDNSTLD {
  81. if strings.HasSuffix(strings.ToLower(hostname), tld) {
  82. return false
  83. }
  84. }
  85. return true
  86. }
  87. // DeepCopyRequest returns a deep copy of the given http.Request.
  88. func DeepCopyRequest(req *http.Request) (*http.Request, error) {
  89. // Copy the URL
  90. urlCopy := *req.URL
  91. // Copy the headers
  92. headersCopy := make(http.Header, len(req.Header))
  93. for k, vv := range req.Header {
  94. vvCopy := make([]string, len(vv))
  95. copy(vvCopy, vv)
  96. headersCopy[k] = vvCopy
  97. }
  98. // Copy the cookies
  99. cookiesCopy := make([]*http.Cookie, len(req.Cookies()))
  100. for i, cookie := range req.Cookies() {
  101. cookieCopy := *cookie
  102. cookiesCopy[i] = &cookieCopy
  103. }
  104. // Copy the body, if present
  105. var bodyCopy io.ReadCloser
  106. if req.Body != nil {
  107. var buf bytes.Buffer
  108. if _, err := buf.ReadFrom(req.Body); err != nil {
  109. return nil, err
  110. }
  111. // Reset the request body so it can be read again
  112. if err := req.Body.Close(); err != nil {
  113. return nil, err
  114. }
  115. req.Body = io.NopCloser(&buf)
  116. bodyCopy = io.NopCloser(bytes.NewReader(buf.Bytes()))
  117. }
  118. // Create the new request
  119. reqCopy := &http.Request{
  120. Method: req.Method,
  121. URL: &urlCopy,
  122. Proto: req.Proto,
  123. ProtoMajor: req.ProtoMajor,
  124. ProtoMinor: req.ProtoMinor,
  125. Header: headersCopy,
  126. Body: bodyCopy,
  127. ContentLength: req.ContentLength,
  128. TransferEncoding: append([]string(nil), req.TransferEncoding...),
  129. Close: req.Close,
  130. Host: req.Host,
  131. Form: req.Form,
  132. PostForm: req.PostForm,
  133. MultipartForm: req.MultipartForm,
  134. Trailer: req.Trailer,
  135. RemoteAddr: req.RemoteAddr,
  136. TLS: req.TLS,
  137. // Cancel and Context are not copied as it might cause issues
  138. }
  139. return reqCopy, nil
  140. }