request_options.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. package frankenphp
  2. import (
  3. "github.com/dunglas/frankenphp/internal/fastabs"
  4. "net/http"
  5. "path/filepath"
  6. "sync"
  7. "sync/atomic"
  8. "go.uber.org/zap"
  9. )
  10. // RequestOption instances allow to configure a FrankenPHP Request.
  11. type RequestOption func(h *FrankenPHPContext) error
  12. var (
  13. documentRootCache sync.Map
  14. documentRootCacheLen atomic.Uint32
  15. )
  16. // WithRequestDocumentRoot sets the root directory of the PHP application.
  17. // if resolveSymlink is true, oath declared as root directory will be resolved
  18. // to its absolute value after the evaluation of any symbolic links.
  19. // Due to the nature of PHP opcache, root directory path is cached: when
  20. // using a symlinked directory as root this could generate errors when
  21. // symlink is changed without PHP being restarted; enabling this
  22. // directive will set $_SERVER['DOCUMENT_ROOT'] to the real directory path.
  23. func WithRequestDocumentRoot(documentRoot string, resolveSymlink bool) RequestOption {
  24. return func(o *FrankenPHPContext) (err error) {
  25. v, ok := documentRootCache.Load(documentRoot)
  26. if !ok {
  27. // make sure file root is absolute
  28. v, err = fastabs.FastAbs(documentRoot)
  29. if err != nil {
  30. return err
  31. }
  32. // prevent the cache to grow forever, this is a totally arbitrary value
  33. if documentRootCacheLen.Load() < 1024 {
  34. documentRootCache.LoadOrStore(documentRoot, v)
  35. documentRootCacheLen.Add(1)
  36. }
  37. }
  38. if resolveSymlink {
  39. if v, err = filepath.EvalSymlinks(v.(string)); err != nil {
  40. return err
  41. }
  42. }
  43. o.documentRoot = v.(string)
  44. return nil
  45. }
  46. }
  47. // WithRequestResolvedDocumentRoot is similar to WithRequestDocumentRoot
  48. // but doesn't do any checks or resolving on the path to improve performance.
  49. func WithRequestResolvedDocumentRoot(documentRoot string) RequestOption {
  50. return func(o *FrankenPHPContext) error {
  51. o.documentRoot = documentRoot
  52. return nil
  53. }
  54. }
  55. // WithRequestSplitPath contains a list of split path strings.
  56. //
  57. // The path in the URL will be split into two, with the first piece ending
  58. // with the value of splitPath. The first piece will be assumed as the
  59. // actual resource (CGI script) name, and the second piece will be set to
  60. // PATH_INFO for the CGI script to use.
  61. //
  62. // Future enhancements should be careful to avoid CVE-2019-11043,
  63. // which can be mitigated with use of a try_files-like behavior
  64. // that 404s if the FastCGI path info is not found.
  65. func WithRequestSplitPath(splitPath []string) RequestOption {
  66. return func(o *FrankenPHPContext) error {
  67. o.splitPath = splitPath
  68. return nil
  69. }
  70. }
  71. type PreparedEnv = map[string]string
  72. func PrepareEnv(env map[string]string) PreparedEnv {
  73. preparedEnv := make(PreparedEnv, len(env))
  74. for k, v := range env {
  75. preparedEnv[k+"\x00"] = v
  76. }
  77. return preparedEnv
  78. }
  79. // WithRequestEnv set CGI-like environment variables that will be available in $_SERVER.
  80. // Values set with WithEnv always have priority over automatically populated values.
  81. func WithRequestEnv(env map[string]string) RequestOption {
  82. return WithRequestPreparedEnv(PrepareEnv(env))
  83. }
  84. func WithRequestPreparedEnv(env PreparedEnv) RequestOption {
  85. return func(o *FrankenPHPContext) error {
  86. o.env = env
  87. return nil
  88. }
  89. }
  90. func WithOriginalRequest(r *http.Request) RequestOption {
  91. return func(o *FrankenPHPContext) error {
  92. o.originalRequest = r
  93. return nil
  94. }
  95. }
  96. // WithRequestLogger sets the logger associated with the current request
  97. func WithRequestLogger(logger *zap.Logger) RequestOption {
  98. return func(o *FrankenPHPContext) error {
  99. o.logger = logger
  100. return nil
  101. }
  102. }