controller.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. package httpserver
  2. import (
  3. "fmt"
  4. "io"
  5. "log"
  6. "net/http"
  7. "net/http/cookiejar"
  8. "net/url"
  9. "strings"
  10. "github.com/gin-gonic/gin"
  11. "golang.org/x/net/publicsuffix"
  12. )
  13. // Implementing a 'set'
  14. var NonmutableHeaders = map[string]struct{}{
  15. "Cookie": struct{}{},
  16. "User-Agent": struct{}{},
  17. "Accept-Encoding": struct{}{},
  18. "Referer": struct{}{},
  19. "X-Proxy-Url": struct{}{},
  20. "Host": struct{}{},
  21. }
  22. type Controller struct {
  23. Config *HttpServerConfig
  24. Client *http.Client
  25. SiteUrl *url.URL
  26. }
  27. type ProxyCookies struct {
  28. ck map[*url.URL][]*http.Cookie
  29. }
  30. /*
  31. Returns a new Controller struct to register routes to the gin router
  32. :param cfg: A pointer to an HttpServerConfig struct
  33. */
  34. func NewController(cfg *HttpServerConfig) *Controller {
  35. jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
  36. if err != nil {
  37. log.Fatal(err)
  38. }
  39. sessCookies := cfg.CookieJar
  40. domain, err := url.Parse("https://www.semrush.com")
  41. if err != nil {
  42. log.Fatal(err)
  43. }
  44. jar.SetCookies(domain, sessCookies)
  45. return &Controller{Config: cfg, Client: &http.Client{Jar: jar}, SiteUrl: domain}
  46. }
  47. /*
  48. This handler will be responsible for proxying out the GET requests that the server recieves
  49. */
  50. func (c *Controller) Get(ctx *gin.Context) {
  51. var allowedDomain string
  52. if ctx.Param("ProxiedPath") == "/siteaudit/i18n/messages_en.fd3cadbc.json" {
  53. data, ctype, err := c.RetrieveStaticResource(ctx.Param("ProxiedPath"))
  54. if err != nil {
  55. log.Fatal(err, "failed to get site config")
  56. }
  57. ctx.Data(200, ctype, data)
  58. return
  59. }
  60. if strings.Contains(ctx.Param("ProxiedPath"), "/siteaudit/index/") {
  61. data, ctype, err := c.RetrieveStaticResource(ctx.Param("ProxiedPath"))
  62. if err != nil {
  63. log.Fatal(err, "failed to get site config")
  64. }
  65. ctx.Data(200, ctype, data)
  66. return
  67. }
  68. if strings.Contains(ctx.Param("ProxiedPath"), "/siteaudit/review/") {
  69. data, ctype, err := c.RetrieveStaticResource(ctx.Param("ProxiedPath"))
  70. if err != nil {
  71. log.Fatal(err, "failed to get site config")
  72. }
  73. ctx.Data(200, ctype, data)
  74. return
  75. }
  76. if strings.Contains(ctx.Param("ProxiedPath"), "/projects/api/") {
  77. data, ctype, err := c.SiteauditApiCall(ctx.Request.Method, ctx.Param("ProxiedPath"), ctx.Request.URL.RawQuery, ctx.Request.Body)
  78. if err != nil {
  79. log.Fatal(err, "failed to get site config")
  80. }
  81. ctx.Data(200, ctype, data)
  82. return
  83. }
  84. if strings.Contains(ctx.Param("ProxiedPath"), "/siteaudit/api/") {
  85. data, ctype, err := c.SiteauditApiCall(ctx.Request.Method, ctx.Param("ProxiedPath"), ctx.Request.URL.RawQuery, ctx.Request.Body)
  86. if err != nil {
  87. log.Fatal(err, "failed to get site config")
  88. }
  89. ctx.Data(200, ctype, data)
  90. return
  91. }
  92. allowedDomain = c.Config.AllowedDomain
  93. reqUrl := fmt.Sprintf("https://%s%s", allowedDomain, ctx.Param("ProxiedPath"))
  94. req, err := http.NewRequest(ctx.Request.Method, reqUrl, ctx.Request.Body)
  95. if err != nil {
  96. ctx.JSON(500, map[string]string{
  97. "Error": "Error creating the request.",
  98. "Msg:": err.Error(),
  99. })
  100. return
  101. }
  102. req.URL.Path = ctx.Param("ProxiedPath")
  103. cookie, err := ctx.Cookie("csrftoken")
  104. req.Header.Add("User-Agent", c.Config.UserAgent)
  105. req.AddCookie(&http.Cookie{Name: "csrftoken", Value: cookie, Path: "/siteaudit", MaxAge: 3600, HttpOnly: true, Secure: true, SameSite: http.SameSiteNoneMode})
  106. req.Header.Set("Referer", c.Config.AllowedDomain)
  107. if ctx.Param("ProxiedPath") == "/" {
  108. ctx.Header("Location", "https://sem.bunnytools.shop/analytics/overview/")
  109. }
  110. if ctx.Param("ProxiedPath") == "/_compatibility/traffic/overview/" {
  111. ctx.Header("Location", "https://sem.bunnytools.shop/analytics/traffic/overview/ebay.com")
  112. }
  113. for k, v := range ctx.Request.Header {
  114. _, ok := NonmutableHeaders[k]
  115. if !ok {
  116. req.Header.Add(k, v[0])
  117. }
  118. }
  119. resp, err := c.Client.Do(req)
  120. if err != nil {
  121. ctx.JSON(500, map[string]string{
  122. "Error": "Error creating the request.",
  123. "Msg:": err.Error(),
  124. })
  125. return
  126. }
  127. defer resp.Body.Close()
  128. b, err := io.ReadAll(resp.Body)
  129. if err != nil {
  130. ctx.JSON(500, map[string]string{
  131. "Error": "Error creating the request.",
  132. "Msg:": err.Error(),
  133. })
  134. return
  135. }
  136. for k, v := range resp.Header {
  137. _, ok := NonmutableHeaders[k]
  138. if !ok {
  139. ctx.Header(k, v[0])
  140. }
  141. }
  142. newBody := strings.ReplaceAll(string(b), "\"srf-browser-unhappy\"", "\"srf-browser-unhappy\" style=\"display:none;\"")
  143. newBody = strings.ReplaceAll(newBody, "\"srf-navbar__right\"", "\"srf-navbar__right\" style=\"display:none;\"")
  144. newBody = strings.ReplaceAll(newBody, "<footer", "<footer style=\"display:none;\"")
  145. newBody = strings.ReplaceAll(newBody, "\"srf-report-sidebar-extras\"", "\"srf-report-sidebar-extra\" style=\"display:none;\"")
  146. newBody = strings.ReplaceAll(newBody, "www.semrush.com", "sem.bunnytools.shop")
  147. newBody = strings.ReplaceAll(newBody, "static.semrush.com", "sem.bunnytools.shop")
  148. ctx.Data(resp.StatusCode, resp.Header.Get("Content-Type"), []byte(newBody))
  149. }