forked from External/ergo
255 lines
5.8 KiB
Go
255 lines
5.8 KiB
Go
// Copyright (c) 2017 Daniel Oaks <daniel@danieloaks.net>
|
|
// released under the MIT license
|
|
|
|
package utils
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/cshum/imagor/imagorpath"
|
|
"github.com/ergochat/irc-go/ircmsg"
|
|
"net/http"
|
|
"os"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func IsRestrictedCTCPMessage(message string) bool {
|
|
// block all CTCP privmsgs to Tor clients except for ACTION
|
|
// DCC can potentially be used for deanonymization, the others for fingerprinting
|
|
return strings.HasPrefix(message, "\x01") && !strings.HasPrefix(message, "\x01ACTION")
|
|
}
|
|
|
|
type MessagePair struct {
|
|
Message string
|
|
Tags map[string]string
|
|
Concat bool // should be relayed with the multiline-concat tag
|
|
}
|
|
|
|
// SplitMessage represents a message that's been split for sending.
|
|
// Two possibilities:
|
|
// (a) Standard message that can be relayed on a single 512-byte line
|
|
//
|
|
// (MessagePair contains the message, Split == nil)
|
|
//
|
|
// (b) multiline message that was split on the client side
|
|
//
|
|
// (Message == "", Split contains the split lines)
|
|
type SplitMessage struct {
|
|
Message string
|
|
Msgid string
|
|
Split []MessagePair
|
|
Time time.Time
|
|
}
|
|
|
|
func MakeMessage(original string) (result SplitMessage) {
|
|
result.Message = original
|
|
result.Msgid = GenerateMessageIdStr()
|
|
result.SetTime()
|
|
|
|
return
|
|
}
|
|
|
|
func (sm *SplitMessage) Append(message string, concat bool, tags map[string]string) {
|
|
if sm.Msgid == "" {
|
|
sm.Msgid = GenerateMessageIdStr()
|
|
}
|
|
sm.Split = append(sm.Split, MessagePair{
|
|
Message: message,
|
|
Concat: concat,
|
|
Tags: tags,
|
|
})
|
|
}
|
|
|
|
func (sm *SplitMessage) SetTime() {
|
|
// strip the monotonic time, it's a potential source of problems:
|
|
sm.Time = time.Now().UTC().Round(0)
|
|
}
|
|
|
|
func (sm *SplitMessage) LenLines() int {
|
|
if sm.Split == nil {
|
|
if sm.Message == "" {
|
|
return 0
|
|
} else {
|
|
return 1
|
|
}
|
|
}
|
|
return len(sm.Split)
|
|
}
|
|
|
|
func (sm *SplitMessage) ValidMultiline() bool {
|
|
// must contain at least one nonblank line
|
|
for i := 0; i < len(sm.Split); i++ {
|
|
if len(sm.Split[i].Message) != 0 {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (sm *SplitMessage) IsRestrictedCTCPMessage() bool {
|
|
if IsRestrictedCTCPMessage(sm.Message) {
|
|
return true
|
|
}
|
|
for i := 0; i < len(sm.Split); i++ {
|
|
if IsRestrictedCTCPMessage(sm.Split[i].Message) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (sm *SplitMessage) Is512() bool {
|
|
return sm.Split == nil
|
|
}
|
|
|
|
// TokenLineBuilder is a helper for building IRC lines composed of delimited tokens,
|
|
// with a maximum line length.
|
|
type TokenLineBuilder struct {
|
|
lineLen int
|
|
delim string
|
|
buf strings.Builder
|
|
result []string
|
|
}
|
|
|
|
func (t *TokenLineBuilder) Initialize(lineLen int, delim string) {
|
|
t.lineLen = lineLen
|
|
t.delim = delim
|
|
}
|
|
|
|
// Add adds a token to the line, creating a new line if necessary.
|
|
func (t *TokenLineBuilder) Add(token string) {
|
|
tokenLen := len(token)
|
|
if t.buf.Len() != 0 {
|
|
tokenLen += len(t.delim)
|
|
}
|
|
if t.lineLen < t.buf.Len()+tokenLen {
|
|
t.result = append(t.result, t.buf.String())
|
|
t.buf.Reset()
|
|
}
|
|
if t.buf.Len() != 0 {
|
|
t.buf.WriteString(t.delim)
|
|
}
|
|
t.buf.WriteString(token)
|
|
}
|
|
|
|
// AddParts concatenates `parts` into a token and adds it to the line,
|
|
// creating a new line if necessary.
|
|
func (t *TokenLineBuilder) AddParts(parts ...string) {
|
|
var tokenLen int
|
|
for _, part := range parts {
|
|
tokenLen += len(part)
|
|
}
|
|
if t.buf.Len() != 0 {
|
|
tokenLen += len(t.delim)
|
|
}
|
|
if t.lineLen < t.buf.Len()+tokenLen {
|
|
t.result = append(t.result, t.buf.String())
|
|
t.buf.Reset()
|
|
}
|
|
if t.buf.Len() != 0 {
|
|
t.buf.WriteString(t.delim)
|
|
}
|
|
for _, part := range parts {
|
|
t.buf.WriteString(part)
|
|
}
|
|
}
|
|
|
|
// Lines terminates the line-building and returns all the lines.
|
|
func (t *TokenLineBuilder) Lines() (result []string) {
|
|
result = t.result
|
|
t.result = nil
|
|
if t.buf.Len() != 0 {
|
|
result = append(result, t.buf.String())
|
|
t.buf.Reset()
|
|
}
|
|
return
|
|
}
|
|
|
|
// BuildTokenLines is a convenience to apply TokenLineBuilder to a predetermined
|
|
// slice of tokens.
|
|
func BuildTokenLines(lineLen int, tokens []string, delim string) []string {
|
|
var tl TokenLineBuilder
|
|
tl.Initialize(lineLen, delim)
|
|
for _, arg := range tokens {
|
|
tl.Add(arg)
|
|
}
|
|
return tl.Lines()
|
|
}
|
|
|
|
var urlRegex = regexp.MustCompile("https?:\\/\\/[\\w-]+(\\.[\\w-]+)+([\\w.,@?^=%&:/~+#-]*[\\w@?^=%&/~+#-])?")
|
|
|
|
func GenerateImagorSignaturesFromMessage(message *ircmsg.Message) string {
|
|
line, err := message.Line()
|
|
if err == nil {
|
|
return GenerateImagorSignatures(line)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
var secretKey = os.Getenv("IMAGOR_SECRET")
|
|
var baseUrl = os.Getenv("IMAGOR_URL")
|
|
|
|
func GetUrlMime(url string) string {
|
|
// hacky, should fix
|
|
if !strings.Contains(url, "?") {
|
|
url += "?"
|
|
}
|
|
params := imagorpath.Params{
|
|
Image: url,
|
|
Meta: true,
|
|
}
|
|
metaPath := imagorpath.Generate(params, imagorpath.NewHMACSigner(sha256.New, 0, secretKey))
|
|
client := http.Client{
|
|
Timeout: 5 * time.Second,
|
|
}
|
|
resp, err := client.Get(baseUrl + metaPath)
|
|
if err != nil {
|
|
println("Failed on the initial get")
|
|
println(err.Error())
|
|
return ""
|
|
}
|
|
defer resp.Body.Close()
|
|
var meta map[string]interface{}
|
|
err = json.NewDecoder(resp.Body).Decode(&meta)
|
|
if err != nil {
|
|
println("Failed on the JSON decode")
|
|
return ""
|
|
}
|
|
contentType, valid := meta["format"].(string)
|
|
fmt.Printf("%+v\n", meta)
|
|
if !valid {
|
|
println("No content type")
|
|
return ""
|
|
}
|
|
return contentType
|
|
}
|
|
|
|
// Process a message to add Imagor signatures
|
|
func GenerateImagorSignatures(str string) string {
|
|
urls := urlRegex.FindAllString(str, -1)
|
|
var sigs []string
|
|
for _, url := range urls {
|
|
params := imagorpath.Params{
|
|
Image: url,
|
|
FitIn: true,
|
|
Width: 600,
|
|
Height: 600,
|
|
}
|
|
path := imagorpath.Generate(params, imagorpath.NewHMACSigner(sha256.New, 0, secretKey))
|
|
signature := path[:strings.IndexByte(path, '/')]
|
|
contentType := GetUrlMime(url)
|
|
if contentType != "" {
|
|
sigs = append(sigs, signature+"|"+strings.ReplaceAll(contentType, "/", "_"))
|
|
} else {
|
|
sigs = append(sigs, signature)
|
|
}
|
|
|
|
}
|
|
if len(sigs) > 0 {
|
|
return strings.Join(sigs, ",")
|
|
}
|
|
return ""
|
|
}
|