tcp.ac/termbin/main.go

161 lines
3.9 KiB
Go
Raw Normal View History

2021-07-28 02:48:58 +00:00
package termbin
import (
"golang.org/x/tools/godoc/util"
2021-08-24 09:56:10 +00:00
ipa "inet.af/netaddr"
2021-07-28 02:48:58 +00:00
"net"
"strconv"
2021-08-24 09:56:10 +00:00
"tcp.ac/ratelimit"
2021-07-28 02:48:58 +00:00
"time"
)
var (
// Msg is a channel for status information during concurrent server operations
Msg chan Message
2021-07-28 07:31:34 +00:00
// Reply is a channel to receive messages to send our client upon completion
Reply chan Message
2021-07-28 02:48:58 +00:00
// Timeout is the read timeout for incoming data in seconds
Timeout int = 4
// Max size for incoming data (default: 3 << 30 or 3 MiB)
MaxSize = 3 << 20
// Toggle this to enable or disable gzip compression
Gzip = true
// Toggle this to enable or disable sending status messages through an exported channel, otherwise print
UseChannel = true
2021-08-24 09:56:10 +00:00
Rater *ratelimit.Queue
2021-07-28 02:48:58 +00:00
)
// Message is a struct that encapsulates status messages to send down the Msg channel
type Message struct {
Type string
RAddr string
Content string
Bytes []byte
Size int
}
2021-08-24 09:56:10 +00:00
type TermbinSource struct {
Actual net.IP
Addr net.Addr
}
func (t *TermbinSource) UniqueKey() string {
if t.Addr != nil {
t.Actual = net.ParseIP(ipa.MustParseIPPort(t.Addr.String()).String())
}
return t.Actual.String()
}
2021-07-28 02:48:58 +00:00
func init() {
Msg = make(chan Message)
2021-07-28 07:31:34 +00:00
Reply = make(chan Message)
2021-08-24 09:56:10 +00:00
Rater = ratelimit.NewDefaultLimiter(new(TermbinSource))
2021-07-28 02:48:58 +00:00
}
func termStatus(m Message) {
if UseChannel {
Msg <- m
} else {
var suffix string
if m.Size != 0 {
suffix = " (" + strconv.Itoa(m.Size) + ")"
}
println("[" + m.RAddr + "][" + m.Type + "] " + m.Content + suffix)
}
}
func serve(c net.Conn) {
termStatus(Message{Type: "NEW_CONN", RAddr: c.RemoteAddr().String()})
var (
data []byte
final []byte
length int
done bool
)
2021-08-24 09:56:10 +00:00
if Rater.Check(&TermbinSource{Addr: c.RemoteAddr()}) {
2021-07-28 02:48:58 +00:00
c.Write([]byte("RATELIMIT_REACHED"))
c.Close()
termStatus(Message{Type: "ERROR", RAddr: c.RemoteAddr().String(), Content: "RATELIMITED"})
return
}
for {
c.SetReadDeadline(time.Now().Add(time.Duration(Timeout) * time.Second))
buf := make([]byte, MaxSize)
n, err := c.Read(buf)
if err != nil {
switch err.Error() {
case "EOF":
c.Close()
termStatus(Message{Type: "ERROR", RAddr: c.RemoteAddr().String(), Content: "EOF"})
return
case "read tcp " + c.LocalAddr().String() + "->" + c.RemoteAddr().String() + ": i/o timeout":
termStatus(Message{Type: "FINISH", RAddr: c.RemoteAddr().String(), Size: length, Content: "TIMEOUT"})
done = true
default:
c.Close()
termStatus(Message{Type: "ERROR", Size: length, Content: err.Error()})
return
}
}
if done {
break
}
length += n
if length > MaxSize {
termStatus(Message{Type: "ERROR", RAddr: c.RemoteAddr().String(), Size: length, Content: "MAX_SIZE_EXCEEDED"})
c.Write([]byte("MAX_SIZE_EXCEEDED"))
c.Close()
return
}
data = append(data, buf[:n]...)
termStatus(Message{Type: "INCOMING_DATA", RAddr: c.RemoteAddr().String(), Size: length})
}
if !util.IsText(data) {
termStatus(Message{Type: "ERROR", RAddr: c.RemoteAddr().String(), Content: "BINARY_DATA_REJECTED"})
c.Write([]byte("BINARY_DATA_REJECTED"))
c.Close()
return
}
final = data
if Gzip {
var gzerr error
if final, gzerr = gzipCompress(data); gzerr != nil {
termStatus(Message{Type: "ERROR", RAddr: c.RemoteAddr().String(), Content: "GZIP_ERROR: " + gzerr.Error()})
} else {
diff := len(data) - len(final)
termStatus(Message{Type: "DEBUG", RAddr: c.RemoteAddr().String(), Content: "GZIP_RESULT", Size: diff})
}
}
termStatus(Message{Type: "FINAL", RAddr: c.RemoteAddr().String(), Size: len(final), Bytes: final, Content: "SUCCESS"})
2021-08-24 09:56:10 +00:00
url := <-Reply
2021-07-28 07:31:34 +00:00
switch url.Type {
case "URL":
c.Write([]byte(url.Content))
case "ERROR":
c.Write([]byte("ERROR: " + url.Content))
}
2021-07-28 02:48:58 +00:00
c.Close()
}
// Listen starts the TCP server
func Listen(addr string, port string) error {
l, err := net.Listen("tcp", addr+":"+port)
if err != nil {
return err
}
defer l.Close()
for {
c, err := l.Accept()
if err != nil {
termStatus(Message{Type: "ERROR", Content: err.Error()})
}
go serve(c)
}
}