page_handler_test.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. package handler
  2. import (
  3. "os"
  4. "path/filepath"
  5. "testing"
  6. )
  7. func TestCleanPageImageRelativePath(t *testing.T) {
  8. tests := []struct {
  9. name string
  10. in string
  11. want string
  12. ok bool
  13. }{
  14. {name: "single filename", in: "logo.png", want: "logo.png", ok: true},
  15. {name: "nested path", in: "images/logo.png", want: filepath.Join("images", "logo.png"), ok: true},
  16. {name: "dot prefix", in: "./logo.png", want: "logo.png", ok: true},
  17. {name: "url escaped slash", in: "images%2Flogo.png", want: filepath.Join("images", "logo.png"), ok: true},
  18. {name: "parent traversal", in: "../secret.png", ok: false},
  19. {name: "encoded parent traversal", in: "%2e%2e/secret.png", ok: false},
  20. {name: "backslash traversal", in: `images\secret.png`, ok: false},
  21. {name: "absolute path", in: "/etc/passwd", ok: false},
  22. {name: "encoded absolute path", in: "%2fetc/passwd", ok: false},
  23. {name: "encoded nul byte", in: "logo.png%00", ok: false},
  24. {name: "invalid escape", in: "logo.png%zz", ok: false},
  25. {name: "empty path", in: "", ok: false},
  26. }
  27. for _, tt := range tests {
  28. t.Run(tt.name, func(t *testing.T) {
  29. got, ok := cleanPageImageRelativePath(tt.in)
  30. if ok != tt.ok {
  31. t.Fatalf("ok = %v, want %v", ok, tt.ok)
  32. }
  33. if got != tt.want {
  34. t.Fatalf("path = %q, want %q", got, tt.want)
  35. }
  36. })
  37. }
  38. }
  39. func TestResolvePageImagePath(t *testing.T) {
  40. root := t.TempDir()
  41. pagesDir := filepath.Join(root, "pages")
  42. base := filepath.Join(pagesDir, "guide")
  43. if err := os.MkdirAll(filepath.Join(base, "images"), 0755); err != nil {
  44. t.Fatalf("create images dir: %v", err)
  45. }
  46. if err := os.WriteFile(filepath.Join(base, "logo.png"), []byte("fake"), 0644); err != nil {
  47. t.Fatalf("create direct image: %v", err)
  48. }
  49. if err := os.WriteFile(filepath.Join(base, "images", "logo.png"), []byte("fake"), 0644); err != nil {
  50. t.Fatalf("create image: %v", err)
  51. }
  52. got, ok := resolvePageImagePath(pagesDir, base, "logo.png")
  53. if !ok {
  54. t.Fatal("expected direct image path to be accepted")
  55. }
  56. want := mustEvalSymlinks(t, filepath.Join(base, "logo.png"))
  57. if got != want {
  58. t.Fatalf("path = %q, want %q", got, want)
  59. }
  60. got, ok = resolvePageImagePath(pagesDir, base, "images/logo.png")
  61. if !ok {
  62. t.Fatal("expected nested image path to be accepted")
  63. }
  64. want = mustEvalSymlinks(t, filepath.Join(base, "images", "logo.png"))
  65. if got != want {
  66. t.Fatalf("path = %q, want %q", got, want)
  67. }
  68. if got, ok := resolvePageImagePath(pagesDir, base, "../guide.md"); ok {
  69. t.Fatalf("expected traversal to be rejected, got %q", got)
  70. }
  71. }
  72. func TestResolvePageImagePathRejectsSymlinkEscape(t *testing.T) {
  73. root := t.TempDir()
  74. pagesDir := filepath.Join(root, "pages")
  75. base := filepath.Join(pagesDir, "guide")
  76. outside := filepath.Join(root, "outside")
  77. if err := os.MkdirAll(base, 0755); err != nil {
  78. t.Fatalf("create page dir: %v", err)
  79. }
  80. if err := os.MkdirAll(outside, 0755); err != nil {
  81. t.Fatalf("create outside dir: %v", err)
  82. }
  83. if err := os.WriteFile(filepath.Join(outside, "secret.png"), []byte("secret"), 0644); err != nil {
  84. t.Fatalf("create outside file: %v", err)
  85. }
  86. if err := os.Symlink(outside, filepath.Join(base, "images")); err != nil {
  87. t.Skipf("symlink not supported: %v", err)
  88. }
  89. if got, ok := resolvePageImagePath(pagesDir, base, "images/secret.png"); ok {
  90. t.Fatalf("expected symlink escape to be rejected, got %q", got)
  91. }
  92. }
  93. func mustEvalSymlinks(t *testing.T, path string) string {
  94. t.Helper()
  95. realPath, err := filepath.EvalSymlinks(path)
  96. if err != nil {
  97. t.Fatalf("eval symlinks for %q: %v", path, err)
  98. }
  99. return realPath
  100. }