tcp.ac/termbin/main.go

161 lines
3.9 KiB
Go

package termbin
import (
"golang.org/x/tools/godoc/util"
ipa "inet.af/netaddr"
"net"
"strconv"
"tcp.ac/ratelimit"
"time"
)
var (
// Msg is a channel for status information during concurrent server operations
Msg chan Message
// Reply is a channel to receive messages to send our client upon completion
Reply chan Message
// 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
Rater *ratelimit.Queue
)
// 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
}
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()
}
func init() {
Msg = make(chan Message)
Reply = make(chan Message)
Rater = ratelimit.NewDefaultLimiter(new(TermbinSource))
}
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
)
if Rater.Check(&TermbinSource{Addr: c.RemoteAddr()}) {
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"})
url := <-Reply
switch url.Type {
case "URL":
c.Write([]byte(url.Content))
case "ERROR":
c.Write([]byte("ERROR: " + url.Content))
}
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)
}
}