-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathreceive.go
140 lines (121 loc) · 3.51 KB
/
receive.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package main
import (
"context"
"errors"
"fmt"
"github.com/gin-gonic/gin"
"github.com/logrusorgru/aurora/v4"
"log"
"net/http"
"strconv"
"strings"
"time"
)
const (
QueryMailToSend = `SELECT snowflake, data FROM mail WHERE recipient = $1 AND is_sent = false ORDER BY snowflake LIMIT 10`
UpdateSentFlag = `UPDATE mail SET is_sent = true WHERE snowflake = $1`
)
func receive(c *gin.Context) {
mlid := c.PostForm("mlid")
password := c.PostForm("passwd")
// Queries can take extremely long and eat up memory. Prevent this by enforcing a timeout.
ctx, cancel := context.WithTimeout(c.Copy(), 10*time.Second)
defer cancel()
err := validatePassword(ctx, mlid, password)
if errors.Is(err, ErrInvalidCredentials) {
cgi := GenCGIError(250, err.Error())
ReportError(err)
c.String(http.StatusOK, ConvertToCGI(cgi))
return
} else if err != nil {
cgi := GenCGIError(551, "An error has occurred while querying the database.")
ReportError(err)
c.String(http.StatusOK, ConvertToCGI(cgi))
return
}
maxSize, err := strconv.Atoi(c.PostForm("maxsize"))
if err != nil {
cgi := GenCGIError(330, "maxsize needs to be an int.")
c.String(http.StatusOK, ConvertToCGI(cgi))
return
}
mail, err := pool.Query(ctx, QueryMailToSend, mlid[1:])
if err != nil {
cgi := GenCGIError(551, "An error has occurred while querying the database.")
ReportError(err)
c.String(http.StatusOK, ConvertToCGI(cgi))
// Determine if this was a timeout error and log if so.
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
log.Printf("%s %s.", aurora.BgBrightYellow("Database query timed out for Wii"), mlid)
}
return
}
mailSize := 0
mailToSend := new(strings.Builder)
numberOfMail := 0
boundary := generateBoundary()
c.Header("Content-Type", fmt.Sprintf("multipart/mixed; boundary=%s", boundary))
defer mail.Close()
for mail.Next() {
var snowflake int64
var data string
err = mail.Scan(&snowflake, &data)
if err != nil {
// Abandon this mail and report to Sentry
ReportError(err)
continue
}
// Set the flag before adding the message.
// In previous versions the update would time out causing the flag to never be set, while the message still
// sends.
_, err = pool.Exec(ctx, UpdateSentFlag, snowflake)
if err != nil {
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
log.Printf("%s %d.", aurora.BgBrightYellow("Toggling update flag failed for message"), snowflake)
}
ReportError(err)
continue
}
// Upon testing with Doujinsoft, I realized that the Wii expects Windows (CRLF) newlines,
// and will reject UNIX (LF) newlines.
data = strings.Replace(data, "\n", "\r\n", -1)
data = strings.Replace(data, "\r\r\n", "\r\n", -1)
current := "\r\n--" + boundary + "\r\nContent-Type: text/plain\r\n\r\n" + data
if mailToSend.Len()+len(current) > maxSize {
break
}
mailToSend.WriteString(current)
numberOfMail++
mailSize += len(current)
}
cgi := CGIResponse{
code: 100,
message: "Success.",
other: []KV{
{
key: "mailnum",
value: strconv.Itoa(numberOfMail),
},
{
key: "mailsize",
value: strconv.Itoa(mailSize),
},
{
key: "allnum",
value: strconv.Itoa(numberOfMail),
},
},
}
if config.UseDatadog {
err = dataDog.Incr("mail.received_mail", nil, float64(numberOfMail))
if err != nil {
ReportError(err)
}
}
c.String(http.StatusOK, fmt.Sprint("--", boundary, "\r\n",
"Content-Type: text/plain\r\n\r\n",
"This part is ignored.\r\n\r\n\r\n\n",
ConvertToCGI(cgi),
mailToSend.String(),
"\r\n--", boundary, "--\r\n"))
}