123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121 |
- package httplistener
-
- import (
- "bytes"
- "encoding/base64"
- "fmt"
- "io"
- "net/http"
- "strings"
-
- "github.com/irccloud/irccat/dispatcher"
- "github.com/spf13/viper"
- )
-
- func handleUrlEncodedPostForm(request *http.Request) string {
- request.ParseForm()
- parts := []string{}
- for key, val := range request.PostForm {
- if key != "" {
- parts = append(parts, key)
- }
- for _, v := range val {
- if v != "" {
- parts = append(parts, v)
- }
- }
- }
- return strings.Join(parts, " ")
- }
-
- // handleMixed blindly concatenates all bodies in a mixed message.
- //
- // Headers are discarded. Binary data, including illegal IRC chars, are
- // retained as is. Quoted-printable and base64 encodings are recognized.
- func handleMixed(request *http.Request) (string, error) {
- mr, err := request.MultipartReader()
- if err != nil {
- return "", err
- }
- parts := []string{}
- for {
- p, err := mr.NextPart()
- if err == io.EOF {
- break
- }
- if err != nil {
- return "", err
- }
- b, err := io.ReadAll(p)
- if err != nil {
- return "", err
- }
- if len(b) != 0 {
- if p.Header.Get("content-transfer-encoding") == "base64" {
- encoder := base64.StdEncoding
- if decoded, err := encoder.DecodeString(string(b)); err != nil {
- return "", err
- } else if len(decoded) > 0 {
- b = decoded
- }
- }
- parts = append(parts, string(b))
- }
- }
- return strings.Join(parts, " "), nil
- }
-
- var genericSender = dispatcher.Send
-
- // Examples of using curl to post to /send.
- //
- // echo "Hello, world" | curl -d @- http://irccat.example.com/send
- // echo "#test,@alice Hello, world" | curl -d @- http://irccat.example.com/send
- //
- // See httplistener/generic_tests.go for info on strict mode, which behaves
- // differently and is enabled by config option http.listeners.generic.strict
- func (hl *HTTPListener) genericHandler(w http.ResponseWriter, request *http.Request) {
- if request.Method != "POST" {
- http.NotFound(w, request)
- return
- }
-
- // Optional simple auth via token
- secret := viper.GetString("http.listeners.generic.secret")
- if secret != "" {
- auth := request.Header.Get("Authorization")
- expecting := fmt.Sprintf("Bearer %s", secret)
- if auth != expecting {
- http.Error(w, "Invalid Authorization", http.StatusUnauthorized)
- log.Warningf("%s - Invalid Authorization!", request.RemoteAddr)
- return
- }
- }
- var message string
- strict := viper.GetBool("http.listeners.generic.strict")
- if strict {
- contentType := request.Header.Get("Content-Type")
- if strings.HasPrefix(contentType, "application/x-www-form-urlencoded") {
- message = handleUrlEncodedPostForm(request)
- } else if strings.HasPrefix(contentType, "multipart/") {
- if msg, err := handleMixed(request); err == nil {
- message = msg // otherwise message is "", which triggers 400
- }
- }
- }
-
- if message == "" {
- body := new(bytes.Buffer)
- body.ReadFrom(request.Body)
- message = body.String()
- }
-
- if message == "" {
- if strict {
- http.Error(w, "Bad Request", http.StatusBadRequest)
- }
- log.Warningf("%s - No message body in POST request", request.RemoteAddr)
- return
- }
- genericSender(hl.irc, message, log, request.RemoteAddr)
- }
|