common.go 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. package common
  2. import (
  3. "net/url"
  4. "strings"
  5. "github.com/imgproxy/imgproxy/v3/config"
  6. )
  7. func EscapeURL(u string) string {
  8. // Non-http(s) URLs may contain percent symbol outside of the percent-encoded sequences.
  9. // Parsing such URLs will fail with an error.
  10. // To prevent this, we replace all percent symbols with %25.
  11. //
  12. // Also, such URLs may contain a hash symbol (a fragment identifier) or a question mark
  13. // (a query string).
  14. // We replace them with %23 and %3F to make `url.Parse` treat them as a part of the path.
  15. // Since we already replaced all percent symbols, we won't mix up %23/%3F that were in the
  16. // original URL and %23/%3F that appeared after the replacement.
  17. //
  18. // We will revert these replacements in `GetBucketAndKey`.
  19. if !strings.HasPrefix(u, "http://") && !strings.HasPrefix(u, "https://") {
  20. u = strings.ReplaceAll(u, "%", "%25")
  21. u = strings.ReplaceAll(u, "?", "%3F")
  22. u = strings.ReplaceAll(u, "#", "%23")
  23. }
  24. return u
  25. }
  26. func GetBucketAndKey(u *url.URL) (bucket, key, query string) {
  27. bucket = u.Host
  28. // We can't use u.Path here because `url.Parse` unescapes the original URL's path.
  29. // So we have to use `u.RawPath` if it's available.
  30. // If it is not available, then `u.EscapedPath()` is the same as the original URL's path
  31. // before `url.Parse`.
  32. // See: https://cs.opensource.google/go/go/+/refs/tags/go1.22.4:src/net/url/url.go;l=680
  33. if len(u.RawPath) > 0 {
  34. key = u.RawPath
  35. } else {
  36. key = u.EscapedPath()
  37. }
  38. key = strings.TrimLeft(key, "/")
  39. // We percent-encoded `%`, `#`, and `?` in `EscapeURL` to prevent parsing errors.
  40. // Now we need to revert these replacements.
  41. //
  42. // It's important to revert %25 last because %23/%3F may appear in the original URL and
  43. // we don't want to mix them up.
  44. bucket = strings.ReplaceAll(bucket, "%23", "#")
  45. bucket = strings.ReplaceAll(bucket, "%3F", "?")
  46. bucket = strings.ReplaceAll(bucket, "%25", "%")
  47. key = strings.ReplaceAll(key, "%23", "#")
  48. key = strings.ReplaceAll(key, "%3F", "?")
  49. key = strings.ReplaceAll(key, "%25", "%")
  50. // Cut the query string if it's present.
  51. // Since we replaced `?` with `%3F` in `EscapeURL`, `url.Parse` will treat query
  52. // string as a part of the path.
  53. // Also, query string separator may be different from `?`, so we can't rely on `url.URL.RawQuery`.
  54. if len(config.SourceURLQuerySeparator) > 0 {
  55. key, query, _ = strings.Cut(key, config.SourceURLQuerySeparator)
  56. }
  57. return
  58. }