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.

generic_test.go 5.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. // Some clients refuse to send payloads that don't match the Content-Type. In
  2. // other words, trying to send "%BOLD hw" or "\x02 hw", as written, wouldn't
  3. // be allowed for Content-Type "application/x-www-form-urlencoded" without
  4. // first being encoded. But irccat by default doesn't do any decoding. Thus,
  5. // when faced with one of these problematic clients, specify config option
  6. // http.listeners.generic.strict to get the behavior shown here:
  7. //
  8. // mismatch
  9. //
  10. // $ echo "%BOLDhw" | curl -d @- http://localhost/send
  11. // 400 Bad Request
  12. //
  13. // urlencoded
  14. //
  15. // $ echo "%BOLDhw" | curl --data-urlencode @- http://localhost/send
  16. // 200 OK
  17. //
  18. // urlencoded non-printable
  19. //
  20. // $ printf "\x02hw" | curl --data-urlencode @- http://localhost/send
  21. // 200 OK
  22. //
  23. // octetstream
  24. //
  25. // $ echo "%BOLDhw" | curl --data-binary @- \
  26. // -H 'Content-Type: application/octet-stream' http://localhost/send
  27. // 200 OK
  28. //
  29. // multipart quoted-printable
  30. //
  31. // $ echo '%BOLDhw' | curl -F 'foo=@-;encoder=quoted-printable' \
  32. // http://localhost/send
  33. // 200 OK
  34. //
  35. // multipart 8bit
  36. //
  37. // $ echo '%BOLDhw' | curl -F 'foo=@-;encoder=8bit' http://localhost/send
  38. // 200 OK
  39. //
  40. // multipart base64
  41. //
  42. // $ echo '%BOLDhw' | curl -F 'foo=@-;encoder=base64' http://localhost/send
  43. // 200 OK
  44. //
  45. // The gist is that when strict mode is active, popular encodings will work
  46. // while mismatches won't, even though they may still appear to at times.
  47. //
  48. package httplistener
  49. import (
  50. "context"
  51. "io"
  52. "net"
  53. "net/http"
  54. "os"
  55. "path"
  56. "strings"
  57. "testing"
  58. "time"
  59. "github.com/juju/loggo"
  60. "github.com/spf13/viper"
  61. irc "github.com/thoj/go-ircevent"
  62. )
  63. var genericTestListen = "localhost:18045"
  64. func genericTestStartHTTPServer(t *testing.T, endpoint string) {
  65. hl := HTTPListener{
  66. http: http.Server{Addr: genericTestListen},
  67. }
  68. http.HandleFunc(endpoint, hl.genericHandler)
  69. go hl.http.ListenAndServe()
  70. t.Cleanup(func() {hl.http.Shutdown(context.Background());})
  71. time.Sleep(time.Millisecond)
  72. }
  73. func genericTestSendOutput(message []byte) ([]byte, error) {
  74. conn, err := net.Dial("tcp", genericTestListen)
  75. if err != nil {
  76. return nil, err
  77. }
  78. _, err = conn.Write(message)
  79. if err != nil {
  80. return nil, err
  81. }
  82. b := make([]byte, 1024)
  83. _ , err = io.ReadAtLeast(conn, b, 24)
  84. if err != nil {
  85. return nil, err
  86. }
  87. return b, nil
  88. }
  89. func runGeneric(t *testing.T, reqFileName string) (string, string) {
  90. var message string
  91. origSender := genericSender
  92. genericSender = func(_ *irc.Connection, m string, _ loggo.Logger, _ string) {
  93. message = m
  94. }
  95. t.Cleanup(func(){genericSender = origSender})
  96. src, err := os.ReadFile(path.Join("testdata", reqFileName))
  97. if err != nil {
  98. t.Fatal(err)
  99. }
  100. resp, err := genericTestSendOutput(src)
  101. if err != nil {
  102. t.Fatal(err)
  103. }
  104. return message, string(resp)
  105. }
  106. // Non-strict
  107. func testGenericBaseline(t *testing.T) {
  108. message, resp := runGeneric(t, "mismatch")
  109. if message != "%BOLDhw" {
  110. t.Fatalf("Expected %q, got: %q", "%BOLDhw", message)
  111. }
  112. if !strings.HasPrefix(string(resp), "HTTP/1.1 200 OK") {
  113. t.Fatalf("Unexpected message: %s", resp)
  114. }
  115. }
  116. // Strict
  117. func testGenericStrict(t *testing.T) {
  118. message, resp := runGeneric(t, "mismatch")
  119. if message != "" {
  120. t.Fatalf("Expected %q, got: %q", "", message)
  121. }
  122. if !strings.HasPrefix(string(resp), "HTTP/1.1 400 Bad Request") {
  123. t.Fatalf("Unexpected message: %s", resp)
  124. }
  125. }
  126. func testURLEncoded(t *testing.T) {
  127. message, resp := runGeneric(t, "urlencoded")
  128. if message != "%BOLDhw\n" { // Note the linefeed
  129. t.Fatalf("Expected %q, got: %q", "%BOLDhw\n", message)
  130. }
  131. if !strings.HasPrefix(string(resp), "HTTP/1.1 200 OK") {
  132. t.Fatalf("Unexpected message: %s", resp)
  133. }
  134. }
  135. func testURLEncodedNonPrintable(t *testing.T) {
  136. message, resp := runGeneric(t, "urlencoded_npc")
  137. if message != "\x02hw" {
  138. t.Fatalf("Expected %q, got: %q", "\x02hw", message)
  139. }
  140. if !strings.HasPrefix(string(resp), "HTTP/1.1 200 OK") {
  141. t.Fatalf("Unexpected message: %s", resp)
  142. }
  143. }
  144. func testOctetStream(t *testing.T) {
  145. message, resp := runGeneric(t, "octetstream")
  146. if message != "%BOLDhw\n" {
  147. t.Fatalf("Expected %q, got: %q", "%BOLDhw\n", message)
  148. }
  149. if !strings.HasPrefix(string(resp), "HTTP/1.1 200 OK") {
  150. t.Fatalf("Unexpected message: %s", resp)
  151. }
  152. }
  153. func testMultipartQP(t *testing.T) {
  154. message, resp := runGeneric(t, "multipart_qp")
  155. if message != "%BOLDhw\n" {
  156. t.Fatalf("Expected %q, got: %q", "%BOLDhw\n", message)
  157. }
  158. if !strings.HasPrefix(string(resp), "HTTP/1.1 200 OK") {
  159. t.Fatalf("Unexpected message: %s", resp)
  160. }
  161. }
  162. func testMultipart8bit(t *testing.T) {
  163. message, resp := runGeneric(t, "multipart_8bit")
  164. if message != "%BOLDhw\n" {
  165. t.Fatalf("Expected %q, got: %q", "%BOLDhw\n", message)
  166. }
  167. if !strings.HasPrefix(string(resp), "HTTP/1.1 200 OK") {
  168. t.Fatalf("Unexpected message: %s", resp)
  169. }
  170. }
  171. func testMultipartBase64(t *testing.T) {
  172. message, resp := runGeneric(t, "multipart_base64")
  173. if message != "%BOLDhw\n" {
  174. t.Fatalf("Expected %q, got: %q", "%BOLDhw\n", message)
  175. }
  176. if !strings.HasPrefix(string(resp), "HTTP/1.1 200 OK") {
  177. t.Fatalf("Unexpected message: %s", resp)
  178. }
  179. }
  180. func TestGeneric(t *testing.T) {
  181. writer, err := loggo.RemoveWriter("default")
  182. if err != nil {
  183. t.Error(err)
  184. }
  185. t.Cleanup(func() {loggo.DefaultContext().AddWriter("default", writer)})
  186. genericTestStartHTTPServer(t, "/send")
  187. t.Run("Baseline", testGenericBaseline)
  188. // Turn on strict for the rest of these
  189. viper.Set("http.listeners.generic.strict", true)
  190. t.Run("Strict Mismatch", testGenericStrict)
  191. t.Run("Strict URL Encoded", testURLEncoded)
  192. t.Run("Strict URL Encoded Non-printable", testURLEncodedNonPrintable)
  193. t.Run("Strict Octet Stream", testOctetStream)
  194. t.Run("Strict Multipart Quoted Printable", testMultipartQP)
  195. t.Run("Strict Multipart 8bit", testMultipart8bit)
  196. t.Run("Strict Multipart Base 64", testMultipartBase64)
  197. // Restore to zero value
  198. viper.Set("http.listeners.generic.strict", false)
  199. }