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.go 3.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. package httplistener
  2. import (
  3. "bytes"
  4. "encoding/base64"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "strings"
  9. "github.com/irccloud/irccat/dispatcher"
  10. "github.com/spf13/viper"
  11. )
  12. func handleUrlEncodedPostForm(request *http.Request) string {
  13. request.ParseForm()
  14. parts := []string{}
  15. for key, val := range request.PostForm {
  16. if key != "" {
  17. parts = append(parts, key)
  18. }
  19. for _, v := range val {
  20. if v != "" {
  21. parts = append(parts, v)
  22. }
  23. }
  24. }
  25. return strings.Join(parts, " ")
  26. }
  27. // handleMixed blindly concatenates all bodies in a mixed message.
  28. //
  29. // Headers are discarded. Binary data, including illegal IRC chars, are
  30. // retained as is. Quoted-printable and base64 encodings are recognized.
  31. func handleMixed(request *http.Request) (string, error) {
  32. mr, err := request.MultipartReader()
  33. if err != nil {
  34. return "", err
  35. }
  36. parts := []string{}
  37. for {
  38. p, err := mr.NextPart()
  39. if err == io.EOF {
  40. break
  41. }
  42. if err != nil {
  43. return "", err
  44. }
  45. b, err := io.ReadAll(p)
  46. if err != nil {
  47. return "", err
  48. }
  49. if len(b) != 0 {
  50. if p.Header.Get("content-transfer-encoding") == "base64" {
  51. encoder := base64.StdEncoding
  52. if decoded, err := encoder.DecodeString(string(b)); err != nil {
  53. return "", err
  54. } else if len(decoded) > 0 {
  55. b = decoded
  56. }
  57. }
  58. parts = append(parts, string(b))
  59. }
  60. }
  61. return strings.Join(parts, " "), nil
  62. }
  63. var genericSender = dispatcher.Send
  64. // Examples of using curl to post to /send.
  65. //
  66. // echo "Hello, world" | curl -d @- http://irccat.example.com/send
  67. // echo "#test,@alice Hello, world" | curl -d @- http://irccat.example.com/send
  68. //
  69. // See httplistener/generic_tests.go for info on strict mode, which behaves
  70. // differently and is enabled by config option http.listeners.generic.strict
  71. func (hl *HTTPListener) genericHandler(w http.ResponseWriter, request *http.Request) {
  72. if request.Method != "POST" {
  73. http.NotFound(w, request)
  74. return
  75. }
  76. // Optional simple auth via token
  77. secret := viper.GetString("http.listeners.generic.secret")
  78. if secret != "" {
  79. auth := request.Header.Get("Authorization")
  80. expecting := fmt.Sprintf("Bearer %s", secret)
  81. if auth != expecting {
  82. http.Error(w, "Invalid Authorization", http.StatusUnauthorized)
  83. log.Warningf("%s - Invalid Authorization!", request.RemoteAddr)
  84. return
  85. }
  86. }
  87. var message string
  88. strict := viper.GetBool("http.listeners.generic.strict")
  89. if strict {
  90. contentType := request.Header.Get("Content-Type")
  91. if strings.HasPrefix(contentType, "application/x-www-form-urlencoded") {
  92. message = handleUrlEncodedPostForm(request)
  93. } else if strings.HasPrefix(contentType, "multipart/") {
  94. if msg, err := handleMixed(request); err == nil {
  95. message = msg // otherwise message is "", which triggers 400
  96. }
  97. }
  98. }
  99. if message == "" {
  100. body := new(bytes.Buffer)
  101. body.ReadFrom(request.Body)
  102. message = body.String()
  103. }
  104. if message == "" {
  105. if strict {
  106. http.Error(w, "Bad Request", http.StatusBadRequest)
  107. }
  108. log.Warningf("%s - No message body in POST request", request.RemoteAddr)
  109. return
  110. }
  111. genericSender(hl.irc, message, log, request.RemoteAddr)
  112. }