URL shortener and file dump for hashru.link https://hashru.link
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

222 lines
5.9 KiB

  1. package rushlink
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io/ioutil"
  6. "mime/multipart"
  7. "net/http"
  8. "net/http/httptest"
  9. "net/url"
  10. "os"
  11. "path/filepath"
  12. "strings"
  13. "testing"
  14. "gitea.hashru.nl/dsprenkels/rushlink/internal/db"
  15. "github.com/gorilla/mux"
  16. "go.etcd.io/bbolt"
  17. )
  18. // createTemporaryRouter initializes a rushlink instance, with temporary
  19. // filestore and database.
  20. //
  21. // It will use testing.T.Cleanup to cleanup after itself.
  22. func createTemporaryRouter(t *testing.T) (*mux.Router, *rushlink) {
  23. tempDir, err := ioutil.TempDir("", "rushlink-tmp-*")
  24. if err != nil {
  25. t.Fatalf("creating temporary directory: %s\n", err)
  26. }
  27. t.Cleanup(func() {
  28. os.RemoveAll(tempDir)
  29. })
  30. fileStore, err := db.OpenFileStore(filepath.Join(tempDir, "filestore"))
  31. if err != nil {
  32. t.Fatalf("opening temporary filestore: %s\n", err)
  33. }
  34. databasePath := filepath.Join(tempDir, "rushlink.db")
  35. database, err := db.OpenDB(databasePath, fileStore)
  36. if err != nil {
  37. t.Fatalf("opening temporary database: %s\n", err)
  38. }
  39. t.Cleanup(func() {
  40. if err := database.Close(); err != nil {
  41. t.Errorf("closing database: %d\n", err)
  42. }
  43. })
  44. // *.invalid. is guaranteed not to exist (RFC 6761).
  45. rootURL, err := url.Parse("https://rushlink.invalid")
  46. if err != nil {
  47. t.Fatalf("parsing URL: %s\n", err)
  48. }
  49. rl := rushlink{
  50. db: database,
  51. fs: fileStore,
  52. rootURL: rootURL,
  53. }
  54. r := mux.NewRouter()
  55. InitMainRouter(r, &rl)
  56. return r, &rl
  57. }
  58. // checkStatusCode checks whether the status code from a recorded response is equal
  59. // to some response code.
  60. func checkStatusCode(t *testing.T, rr *httptest.ResponseRecorder, code int) {
  61. if actual := rr.Code; actual != code {
  62. t.Logf("request body:\n%v\n", rr.Body.String())
  63. t.Fatalf("handler returned wrong status code: got %v want %v\n",
  64. actual, code)
  65. }
  66. }
  67. // checkLocationHeader checks whether the status code from a recorded response is equal
  68. // to some expected URL.
  69. func checkLocationHeader(t *testing.T, rr *httptest.ResponseRecorder, expected string) {
  70. location := rr.Header().Get("Location")
  71. if location != expected {
  72. t.Fatalf("handler returned bad redirect location: got %v want %v", location, expected)
  73. }
  74. }
  75. func TestIssue43(t *testing.T) {
  76. srv, _ := createTemporaryRouter(t)
  77. // Put a URL with a fragment identifier into the database.
  78. var body bytes.Buffer
  79. form := multipart.NewWriter(&body)
  80. form.WriteField("shorten", "https://example.com#fragment")
  81. form.Close()
  82. req, err := http.NewRequest("POST", "/", bytes.NewReader(body.Bytes()))
  83. if err != nil {
  84. t.Fatal(err)
  85. }
  86. req.Header.Add("Content-Type", form.FormDataContentType())
  87. rr := httptest.NewRecorder()
  88. srv.ServeHTTP(rr, req)
  89. checkStatusCode(t, rr, http.StatusFound)
  90. rawURL := strings.SplitN(rr.Body.String(), "\n", 2)[0]
  91. pasteURL, err := url.Parse(rawURL)
  92. if err != nil {
  93. t.Fatal(err)
  94. }
  95. // Check if the URL was encoded correctly.
  96. req, err = http.NewRequest("GET", pasteURL.Path, nil)
  97. if err != nil {
  98. t.Fatal(err)
  99. }
  100. req = mux.SetURLVars(req, map[string]string{"key": pasteURL.Path[1:]})
  101. rr = httptest.NewRecorder()
  102. srv.ServeHTTP(rr, req)
  103. checkStatusCode(t, rr, http.StatusTemporaryRedirect)
  104. checkLocationHeader(t, rr, "https://example.com#fragment")
  105. }
  106. func TestIssue53(t *testing.T) {
  107. srv, rl := createTemporaryRouter(t)
  108. // Put a URL with a fragment identifier into the database.
  109. var body bytes.Buffer
  110. form := multipart.NewWriter(&body)
  111. if _, err := form.CreateFormFile("file", "../directory-traversal/file.txt"); err != nil {
  112. t.Fatal(err)
  113. }
  114. form.Close()
  115. req, err := http.NewRequest("POST", "/", bytes.NewReader(body.Bytes()))
  116. if err != nil {
  117. t.Fatal(err)
  118. }
  119. req.Header.Add("Content-Type", form.FormDataContentType())
  120. rr := httptest.NewRecorder()
  121. srv.ServeHTTP(rr, req)
  122. checkStatusCode(t, rr, http.StatusFound)
  123. // Check that any attempt to do directory traversal has failed.
  124. rl.db.Bolt.View(func(tx *bbolt.Tx) error {
  125. fus, err := db.AllFileUploads(tx)
  126. if err != nil {
  127. t.Fatal(err)
  128. }
  129. for _, fu := range fus {
  130. if strings.ContainsAny(fu.FileName, "/\\") {
  131. t.Fatalf(fmt.Sprintf("found a slash in file name: %v", fu.FileName))
  132. }
  133. }
  134. return nil
  135. })
  136. }
  137. func TestIssue60(t *testing.T) {
  138. srv, _ := createTemporaryRouter(t)
  139. // Request a nonexistent static file
  140. req, err := http.NewRequest("GET", "/css/nonexistent_file.css", nil)
  141. if err != nil {
  142. t.Fatal(err)
  143. }
  144. rr := httptest.NewRecorder()
  145. srv.ServeHTTP(rr, req)
  146. checkStatusCode(t, rr, http.StatusNotFound)
  147. }
  148. func TestIssue56(t *testing.T) {
  149. srv, _ := createTemporaryRouter(t)
  150. // Make a POST request with both a 'file' *and* a 'shorten' part.
  151. var body bytes.Buffer
  152. form := multipart.NewWriter(&body)
  153. if _, err := form.CreateFormFile("file", "empty.txt"); err != nil {
  154. t.Fatal(err)
  155. }
  156. if _, err := form.CreateFormField("shorten"); err != nil {
  157. t.Fatal(err)
  158. }
  159. form.Close()
  160. req, err := http.NewRequest("POST", "/", bytes.NewReader(body.Bytes()))
  161. if err != nil {
  162. t.Fatal(err)
  163. }
  164. req.Header.Add("Content-Type", form.FormDataContentType())
  165. rr := httptest.NewRecorder()
  166. srv.ServeHTTP(rr, req)
  167. checkStatusCode(t, rr, http.StatusBadRequest)
  168. }
  169. func TestIssue68(t *testing.T) {
  170. srv, _ := createTemporaryRouter(t)
  171. originalURL := "https://example.com"
  172. var body bytes.Buffer
  173. form := multipart.NewWriter(&body)
  174. form.WriteField("shorten", "https://example.com")
  175. form.Close()
  176. req, err := http.NewRequest("POST", "/", bytes.NewReader(body.Bytes()))
  177. if err != nil {
  178. t.Fatal(err)
  179. }
  180. req.Header.Add("Content-Type", form.FormDataContentType())
  181. rr := httptest.NewRecorder()
  182. srv.ServeHTTP(rr, req)
  183. checkStatusCode(t, rr, http.StatusFound)
  184. rawURL := strings.SplitN(rr.Body.String(), "\n", 2)[0]
  185. pasteURL, err := url.Parse(rawURL)
  186. if err != nil {
  187. t.Fatal(err)
  188. }
  189. // Check if the no-redirect handler works properly.
  190. req, err = http.NewRequest("GET", pasteURL.Path+"/nr", nil)
  191. if err != nil {
  192. t.Fatal(err)
  193. }
  194. rr = httptest.NewRecorder()
  195. srv.ServeHTTP(rr, req)
  196. checkStatusCode(t, rr, http.StatusOK)
  197. if rr.Body.String() != originalURL {
  198. t.Errorf("incorrect URL = %v, want %v", rr.Body.String(), originalURL)
  199. }
  200. }