Implement DCC streaming

This commit is contained in:
Ken-Håvard Lieng 2020-05-20 07:21:12 +02:00
parent 973578bb49
commit e33b9f05e4
15 changed files with 407 additions and 218 deletions

File diff suppressed because one or more lines are too long

View File

@ -134,6 +134,22 @@ export default function handleSocket({
} }
}, },
dcc_send({ server, from, filename, url }) {
const serverName = getState().servers[server]?.name || server;
dispatch(
openModal('confirm', {
question: `${from} on ${serverName} is sending you: ${filename}`,
confirmation: 'Download',
onConfirm: () => {
const a = document.createElement('a');
a.href = url;
a.click();
}
})
);
},
_connected(connected) { _connected(connected) {
dispatch(setConnected(connected)); dispatch(setConnected(connected));
} }

View File

@ -109,8 +109,14 @@ func init() {
viper.BindPFlags(rootCmd.PersistentFlags()) viper.BindPFlags(rootCmd.PersistentFlags())
viper.BindPFlags(rootCmd.Flags()) viper.BindPFlags(rootCmd.Flags())
viper.SetDefault("hexIP", false)
viper.SetDefault("verify_certificates", true) viper.SetDefault("verify_certificates", true)
viper.SetDefault("https.enabled", true)
viper.SetDefault("https.port", 443)
viper.SetDefault("auth.anonymous", true)
viper.SetDefault("auth.login", true)
viper.SetDefault("auth.registration", true)
viper.SetDefault("dcc.enabled", true)
viper.SetDefault("dcc.autoget.delete", true)
} }
func initConfig(configPath string, overwrite bool) { func initConfig(configPath string, overwrite bool) {

View File

@ -4,8 +4,6 @@ port = 80
# Hex encode the users IP and use it as the ident # Hex encode the users IP and use it as the ident
hexIP = false hexIP = false
verify_certificates = true verify_certificates = true
# Automatically download files from DCC SENDs
autoget = false
# Defaults for the client connect form # Defaults for the client connect form
[defaults] [defaults]
@ -63,6 +61,20 @@ secret = ""
key = "" key = ""
secret = "" secret = ""
[dcc]
# Receive files through DCC, the user gets to choose if they want to accept the file,
# the file then gets streamed to the user
enabled = true
[dcc.autoget]
# Instead of streaming the file to the user, dispatch automatically downloads
# DCC files and sends a download link to the user once its done
enabled = false
# Delete the file after the user has downloaded it once
delete = true
# Delete the file after a certain time period of inactivity, not implemented yet
delete_after = "30m"
# Strict-Transport-Security # Strict-Transport-Security
[https.hsts] [https.hsts]
enabled = false enabled = false

View File

@ -14,12 +14,12 @@ type Config struct {
Dev bool Dev bool
HexIP bool HexIP bool
VerifyCertificates bool `mapstructure:"verify_certificates"` VerifyCertificates bool `mapstructure:"verify_certificates"`
Autoget bool
Headers map[string]string Headers map[string]string
Defaults Defaults Defaults Defaults
HTTPS HTTPS HTTPS HTTPS
LetsEncrypt LetsEncrypt LetsEncrypt LetsEncrypt
Auth Auth Auth Auth
DCC DCC
} }
type Defaults struct { type Defaults struct {
@ -65,6 +65,17 @@ type Provider struct {
Secret string Secret string
} }
type DCC struct {
Enabled bool
Autoget Autoget
}
type Autoget struct {
Enabled bool
Delete bool
DeleteAfter time.Duration `mapstructure:"delete_after"`
}
func LoadConfig() (*Config, chan *Config) { func LoadConfig() (*Config, chan *Config) {
viper.SetConfigName("config") viper.SetConfigName("config")
viper.AddConfigPath(storage.Path.ConfigRoot()) viper.AddConfigPath(storage.Path.ConfigRoot())

View File

@ -39,11 +39,10 @@ func Serve(handler http.Handler, cfg Config) error {
httpSrv.WriteTimeout = 5 * time.Second httpSrv.WriteTimeout = 5 * time.Second
httpsSrv := &http.Server{ httpsSrv := &http.Server{
Addr: net.JoinHostPort(cfg.Addr, cfg.PortHTTPS), Addr: net.JoinHostPort(cfg.Addr, cfg.PortHTTPS),
ReadTimeout: 5 * time.Second, ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second, IdleTimeout: 120 * time.Second,
IdleTimeout: 120 * time.Second, Handler: handler,
Handler: handler,
} }
redirect := HTTPSRedirect(cfg.PortHTTPS, handler) redirect := HTTPSRedirect(cfg.PortHTTPS, handler)
@ -101,7 +100,6 @@ func Serve(handler http.Handler, cfg Config) error {
} }
} else { } else {
httpSrv.ReadTimeout = 5 * time.Second httpSrv.ReadTimeout = 5 * time.Second
httpSrv.WriteTimeout = 10 * time.Second
httpSrv.IdleTimeout = 120 * time.Second httpSrv.IdleTimeout = 120 * time.Second
httpSrv.Handler = handler httpSrv.Handler = handler

View File

@ -26,12 +26,8 @@ type Client struct {
// Source is the reply to SOURCE CTCP messages // Source is the reply to SOURCE CTCP messages
Source string Source string
DownloadFolder string
Autoget bool
Messages chan *Message Messages chan *Message
ConnectionChanged chan ConnectionState ConnectionChanged chan ConnectionState
Progress chan DownloadProgress
Features *Features Features *Features
nick string nick string
channels []string channels []string
@ -59,7 +55,6 @@ func NewClient(nick, username string) *Client {
Realname: nick, Realname: nick,
Messages: make(chan *Message, 32), Messages: make(chan *Message, 32),
ConnectionChanged: make(chan ConnectionState, 4), ConnectionChanged: make(chan ConnectionState, 4),
Progress: make(chan DownloadProgress, 4),
out: make(chan string, 32), out: make(chan string, 32),
quit: make(chan struct{}), quit: make(chan struct{}),
reconnect: make(chan struct{}), reconnect: make(chan struct{}),

View File

@ -47,13 +47,6 @@ func (c *Client) handleCTCP(ctcp *CTCP, msg *Message) {
case "CLIENTINFO": case "CLIENTINFO":
c.ReplyCTCP(msg.Nick, ctcp.Command, ClientInfo) c.ReplyCTCP(msg.Nick, ctcp.Command, ClientInfo)
case "DCC":
if strings.HasPrefix(ctcp.Params, "SEND") {
if dccSend := ParseDCCSend(ctcp); dccSend != nil {
go c.Download(dccSend)
}
}
case "FINGER", "VERSION": case "FINGER", "VERSION":
if c.Version != "" { if c.Version != "" {
c.ReplyCTCP(msg.Nick, ctcp.Command, c.Version) c.ReplyCTCP(msg.Nick, ctcp.Command, c.Version)

View File

@ -2,14 +2,11 @@ package irc
import ( import (
"encoding/binary" "encoding/binary"
"encoding/json"
"fmt" "fmt"
"io" "io"
"math" "math"
"net" "net"
"os"
"path" "path"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -52,27 +49,16 @@ func ParseDCCSend(ctcp *CTCP) *DCCSend {
return nil return nil
} }
func (c *Client) Download(pack *DCCSend) { func DownloadDCC(w io.Writer, pack *DCCSend, progress chan DownloadProgress) error {
if !c.Autoget { if progress != nil {
// TODO: ask user if he/she wants to download the file progress <- DownloadProgress{
return File: pack.File,
}
} }
c.Progress <- DownloadProgress{
File: pack.File,
}
file, err := os.OpenFile(filepath.Join(c.DownloadFolder, pack.File), os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
c.downloadFailed(pack, err)
return
}
defer file.Close()
conn, err := net.Dial("tcp", net.JoinHostPort(pack.IP, pack.Port)) conn, err := net.Dial("tcp", net.JoinHostPort(pack.IP, pack.Port))
if err != nil { if err != nil {
c.downloadFailed(pack, err) return err
return
} }
defer conn.Close() defer conn.Close()
@ -87,64 +73,63 @@ func (c *Client) Download(pack *DCCSend) {
n, err := conn.Read(buf) n, err := conn.Read(buf)
if err != nil { if err != nil {
if err != io.EOF { if err != io.EOF {
c.downloadFailed(pack, err) return err
return
} }
if n == 0 { if n == 0 {
break break
} }
} }
if _, err := file.Write(buf[:n]); err != nil { if _, err := w.Write(buf[:n]); err != nil {
c.downloadFailed(pack, err) return err
return
} }
accBytes += uint64(n) accBytes += uint64(n)
totalBytes += uint64(n) totalBytes += uint64(n)
conn.Write(uint64Bytes(totalBytes)) _, err = conn.Write(uint64Bytes(totalBytes))
if err != nil {
return err
}
if dt := time.Since(prevUpdate); dt >= time.Second { if progress != nil {
prevUpdate = time.Now() if dt := time.Since(prevUpdate); dt >= time.Second {
prevUpdate = time.Now()
speed := float64(accBytes) / dt.Seconds() speed := float64(accBytes) / dt.Seconds()
if averageSpeed == 0 { if averageSpeed == 0 {
averageSpeed = speed averageSpeed = speed
} else { } else {
averageSpeed = 0.2*speed + 0.8*averageSpeed averageSpeed = 0.2*speed + 0.8*averageSpeed
} }
accBytes = 0 accBytes = 0
bytesRemaining := float64(pack.Length - totalBytes) bytesRemaining := float64(pack.Length - totalBytes)
percentage := 100 * (float64(totalBytes) / float64(pack.Length)) percentage := 100 * (float64(totalBytes) / float64(pack.Length))
c.Progress <- DownloadProgress{ progress <- DownloadProgress{
Speed: humanReadableByteCount(averageSpeed, true), Speed: humanReadableByteCount(averageSpeed, true),
PercCompletion: percentage, PercCompletion: percentage,
BytesRemaining: humanReadableByteCount(bytesRemaining, false), BytesRemaining: humanReadableByteCount(bytesRemaining, false),
BytesCompleted: humanReadableByteCount(float64(totalBytes), false), BytesCompleted: humanReadableByteCount(float64(totalBytes), false),
SecondsElapsed: secondsSince(start), SecondsElapsed: secondsSince(start),
SecondsToGo: bytesRemaining / averageSpeed, SecondsToGo: bytesRemaining / averageSpeed,
File: pack.File, File: pack.File,
}
} }
} }
} }
c.Progress <- DownloadProgress{ if progress != nil {
PercCompletion: 100, progress <- DownloadProgress{
BytesCompleted: humanReadableByteCount(float64(totalBytes), false), PercCompletion: 100,
SecondsElapsed: secondsSince(start), BytesCompleted: humanReadableByteCount(float64(totalBytes), false),
File: pack.File, SecondsElapsed: secondsSince(start),
File: pack.File,
}
} }
}
func (c *Client) downloadFailed(pack *DCCSend, err error) { return nil
c.Progress <- DownloadProgress{
PercCompletion: -1,
File: pack.File,
Error: err,
}
} }
type DownloadProgress struct { type DownloadProgress struct {
@ -158,14 +143,6 @@ type DownloadProgress struct {
SecondsToGo float64 `json:"eta"` SecondsToGo float64 `json:"eta"`
} }
func (p DownloadProgress) ToJSON() string {
progress, err := json.Marshal(p)
if err != nil {
return ""
}
return string(progress)
}
func intToIP(n int) string { func intToIP(n int) string {
var byte1 = n & 255 var byte1 = n & 255
var byte2 = ((n >> 8) & 255) var byte2 = ((n >> 8) & 255)

View File

@ -73,9 +73,6 @@ func connectIRC(server *storage.Server, state *State, srcIP []byte) *irc.Client
} }
} }
i.DownloadFolder = storage.Path.Downloads(state.user.Username)
i.Autoget = cfg.Autoget
state.setIRC(server.Host, i) state.setIRC(server.Host, i)
i.Connect(address) i.Connect(address)
go newIRCHandler(i, state).run() go newIRCHandler(i, state).run()

View File

@ -3,8 +3,10 @@ package server
import ( import (
"fmt" "fmt"
"log" "log"
"os"
"strconv" "strconv"
"strings" "strings"
"time"
"unicode" "unicode"
"github.com/kjk/betterguid" "github.com/kjk/betterguid"
@ -26,6 +28,7 @@ type ircHandler struct {
userBuffers map[string][]string userBuffers map[string][]string
motdBuffer MOTD motdBuffer MOTD
listBuffer storage.ChannelListIndex listBuffer storage.ChannelListIndex
dccProgress chan irc.DownloadProgress
handlers map[string]func(*irc.Message) handlers map[string]func(*irc.Message)
} }
@ -35,6 +38,7 @@ func newIRCHandler(client *irc.Client, state *State) *ircHandler {
client: client, client: client,
state: state, state: state,
userBuffers: make(map[string][]string), userBuffers: make(map[string][]string),
dccProgress: make(chan irc.DownloadProgress, 4),
} }
i.initHandlers() i.initHandlers()
return i return i
@ -64,7 +68,7 @@ func (i *ircHandler) run() {
i.log("Connected") i.log("Connected")
} }
case progress := <-i.client.Progress: case progress := <-i.dccProgress:
if progress.Error != nil { if progress.Error != nil {
i.sendDCCInfo("%s: Download failed (%s)", true, progress.File, progress.Error) i.sendDCCInfo("%s: Download failed (%s)", true, progress.File, progress.Error)
} else if progress.PercCompletion == 100 { } else if progress.PercCompletion == 100 {
@ -175,9 +179,45 @@ func (i *ircHandler) mode(msg *irc.Message) {
} }
} }
func (i *ircHandler) receiveDCCSend(pack *irc.DCCSend, msg *irc.Message) {
cfg := i.state.srv.Config()
if cfg.DCC.Enabled {
if cfg.DCC.Autoget.Enabled {
file, err := os.OpenFile(storage.Path.DownloadedFile(i.state.user.Username, pack.File), os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return
}
defer file.Close()
irc.DownloadDCC(file, pack, i.dccProgress)
} else {
i.state.setPendingDCC(pack.File, pack)
i.state.sendJSON("dcc_send", DCCSend{
Server: i.client.Host,
From: msg.Nick,
Filename: pack.File,
URL: fmt.Sprintf("%s://%s/downloads/%s/%s",
i.state.String("scheme"), i.state.String("host"), i.state.user.Username, pack.File),
})
time.Sleep(150 * time.Second)
i.state.deletePendingDCC(pack.File)
}
}
}
func (i *ircHandler) message(msg *irc.Message) { func (i *ircHandler) message(msg *irc.Message) {
if ctcp := msg.ToCTCP(); ctcp != nil && ctcp.Command != "ACTION" { if ctcp := msg.ToCTCP(); ctcp != nil {
return if ctcp.Command == "DCC" && strings.HasPrefix(ctcp.Params, "SEND") {
if pack := irc.ParseDCCSend(ctcp); pack != nil {
go i.receiveDCCSend(pack, msg)
return
}
} else if ctcp.Command != "ACTION" {
return
}
} }
message := Message{ message := Message{
@ -431,8 +471,7 @@ func (i *ircHandler) initHandlers() {
} }
func (i *ircHandler) log(v ...interface{}) { func (i *ircHandler) log(v ...interface{}) {
s := fmt.Sprintln(v...) log.Println("[IRC]", i.state.user.ID, i.client.Host, fmt.Sprint(v...))
log.Println("[IRC]", i.state.user.ID, i.client.Host, s[:len(s)-1])
} }
func (i *ircHandler) sendDCCInfo(message string, log bool, a ...interface{}) { func (i *ircHandler) sendDCCInfo(message string, log bool, a ...interface{}) {

View File

@ -222,6 +222,13 @@ type ChannelForward struct {
New string New string
} }
type DCCSend struct {
Server string
From string
Filename string
URL string
}
type Tab struct { type Tab struct {
storage.Tab storage.Tab
} }

View File

@ -3060,7 +3060,110 @@ func (v *Error) UnmarshalJSON(data []byte) error {
func (v *Error) UnmarshalEasyJSON(l *jlexer.Lexer) { func (v *Error) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson42239ddeDecodeGithubComKhliengDispatchServer27(l, v) easyjson42239ddeDecodeGithubComKhliengDispatchServer27(l, v)
} }
func easyjson42239ddeDecodeGithubComKhliengDispatchServer28(in *jlexer.Lexer, out *ConnectionUpdate) { func easyjson42239ddeDecodeGithubComKhliengDispatchServer28(in *jlexer.Lexer, out *DCCSend) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
in.Consumed()
}
in.Skip()
return
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeFieldName(false)
in.WantColon()
if in.IsNull() {
in.Skip()
in.WantComma()
continue
}
switch key {
case "server":
out.Server = string(in.String())
case "from":
out.From = string(in.String())
case "filename":
out.Filename = string(in.String())
case "url":
out.URL = string(in.String())
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}
func easyjson42239ddeEncodeGithubComKhliengDispatchServer28(out *jwriter.Writer, in DCCSend) {
out.RawByte('{')
first := true
_ = first
if in.Server != "" {
const prefix string = ",\"server\":"
first = false
out.RawString(prefix[1:])
out.String(string(in.Server))
}
if in.From != "" {
const prefix string = ",\"from\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.From))
}
if in.Filename != "" {
const prefix string = ",\"filename\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Filename))
}
if in.URL != "" {
const prefix string = ",\"url\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.URL))
}
out.RawByte('}')
}
// MarshalJSON supports json.Marshaler interface
func (v DCCSend) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{}
easyjson42239ddeEncodeGithubComKhliengDispatchServer28(&w, v)
return w.Buffer.BuildBytes(), w.Error
}
// MarshalEasyJSON supports easyjson.Marshaler interface
func (v DCCSend) MarshalEasyJSON(w *jwriter.Writer) {
easyjson42239ddeEncodeGithubComKhliengDispatchServer28(w, v)
}
// UnmarshalJSON supports json.Unmarshaler interface
func (v *DCCSend) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data}
easyjson42239ddeDecodeGithubComKhliengDispatchServer28(&r, v)
return r.Error()
}
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *DCCSend) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson42239ddeDecodeGithubComKhliengDispatchServer28(l, v)
}
func easyjson42239ddeDecodeGithubComKhliengDispatchServer29(in *jlexer.Lexer, out *ConnectionUpdate) {
isTopLevel := in.IsStart() isTopLevel := in.IsStart()
if in.IsNull() { if in.IsNull() {
if isTopLevel { if isTopLevel {
@ -3097,7 +3200,7 @@ func easyjson42239ddeDecodeGithubComKhliengDispatchServer28(in *jlexer.Lexer, ou
in.Consumed() in.Consumed()
} }
} }
func easyjson42239ddeEncodeGithubComKhliengDispatchServer28(out *jwriter.Writer, in ConnectionUpdate) { func easyjson42239ddeEncodeGithubComKhliengDispatchServer29(out *jwriter.Writer, in ConnectionUpdate) {
out.RawByte('{') out.RawByte('{')
first := true first := true
_ = first _ = first
@ -3143,27 +3246,27 @@ func easyjson42239ddeEncodeGithubComKhliengDispatchServer28(out *jwriter.Writer,
// MarshalJSON supports json.Marshaler interface // MarshalJSON supports json.Marshaler interface
func (v ConnectionUpdate) MarshalJSON() ([]byte, error) { func (v ConnectionUpdate) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{} w := jwriter.Writer{}
easyjson42239ddeEncodeGithubComKhliengDispatchServer28(&w, v) easyjson42239ddeEncodeGithubComKhliengDispatchServer29(&w, v)
return w.Buffer.BuildBytes(), w.Error return w.Buffer.BuildBytes(), w.Error
} }
// MarshalEasyJSON supports easyjson.Marshaler interface // MarshalEasyJSON supports easyjson.Marshaler interface
func (v ConnectionUpdate) MarshalEasyJSON(w *jwriter.Writer) { func (v ConnectionUpdate) MarshalEasyJSON(w *jwriter.Writer) {
easyjson42239ddeEncodeGithubComKhliengDispatchServer28(w, v) easyjson42239ddeEncodeGithubComKhliengDispatchServer29(w, v)
} }
// UnmarshalJSON supports json.Unmarshaler interface // UnmarshalJSON supports json.Unmarshaler interface
func (v *ConnectionUpdate) UnmarshalJSON(data []byte) error { func (v *ConnectionUpdate) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data} r := jlexer.Lexer{Data: data}
easyjson42239ddeDecodeGithubComKhliengDispatchServer28(&r, v) easyjson42239ddeDecodeGithubComKhliengDispatchServer29(&r, v)
return r.Error() return r.Error()
} }
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface // UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *ConnectionUpdate) UnmarshalEasyJSON(l *jlexer.Lexer) { func (v *ConnectionUpdate) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson42239ddeDecodeGithubComKhliengDispatchServer28(l, v) easyjson42239ddeDecodeGithubComKhliengDispatchServer29(l, v)
} }
func easyjson42239ddeDecodeGithubComKhliengDispatchServer29(in *jlexer.Lexer, out *ClientCert) { func easyjson42239ddeDecodeGithubComKhliengDispatchServer30(in *jlexer.Lexer, out *ClientCert) {
isTopLevel := in.IsStart() isTopLevel := in.IsStart()
if in.IsNull() { if in.IsNull() {
if isTopLevel { if isTopLevel {
@ -3196,7 +3299,7 @@ func easyjson42239ddeDecodeGithubComKhliengDispatchServer29(in *jlexer.Lexer, ou
in.Consumed() in.Consumed()
} }
} }
func easyjson42239ddeEncodeGithubComKhliengDispatchServer29(out *jwriter.Writer, in ClientCert) { func easyjson42239ddeEncodeGithubComKhliengDispatchServer30(out *jwriter.Writer, in ClientCert) {
out.RawByte('{') out.RawByte('{')
first := true first := true
_ = first _ = first
@ -3222,27 +3325,27 @@ func easyjson42239ddeEncodeGithubComKhliengDispatchServer29(out *jwriter.Writer,
// MarshalJSON supports json.Marshaler interface // MarshalJSON supports json.Marshaler interface
func (v ClientCert) MarshalJSON() ([]byte, error) { func (v ClientCert) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{} w := jwriter.Writer{}
easyjson42239ddeEncodeGithubComKhliengDispatchServer29(&w, v) easyjson42239ddeEncodeGithubComKhliengDispatchServer30(&w, v)
return w.Buffer.BuildBytes(), w.Error return w.Buffer.BuildBytes(), w.Error
} }
// MarshalEasyJSON supports easyjson.Marshaler interface // MarshalEasyJSON supports easyjson.Marshaler interface
func (v ClientCert) MarshalEasyJSON(w *jwriter.Writer) { func (v ClientCert) MarshalEasyJSON(w *jwriter.Writer) {
easyjson42239ddeEncodeGithubComKhliengDispatchServer29(w, v) easyjson42239ddeEncodeGithubComKhliengDispatchServer30(w, v)
} }
// UnmarshalJSON supports json.Unmarshaler interface // UnmarshalJSON supports json.Unmarshaler interface
func (v *ClientCert) UnmarshalJSON(data []byte) error { func (v *ClientCert) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data} r := jlexer.Lexer{Data: data}
easyjson42239ddeDecodeGithubComKhliengDispatchServer29(&r, v) easyjson42239ddeDecodeGithubComKhliengDispatchServer30(&r, v)
return r.Error() return r.Error()
} }
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface // UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *ClientCert) UnmarshalEasyJSON(l *jlexer.Lexer) { func (v *ClientCert) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson42239ddeDecodeGithubComKhliengDispatchServer29(l, v) easyjson42239ddeDecodeGithubComKhliengDispatchServer30(l, v)
} }
func easyjson42239ddeDecodeGithubComKhliengDispatchServer30(in *jlexer.Lexer, out *ChannelSearchResult) { func easyjson42239ddeDecodeGithubComKhliengDispatchServer31(in *jlexer.Lexer, out *ChannelSearchResult) {
isTopLevel := in.IsStart() isTopLevel := in.IsStart()
if in.IsNull() { if in.IsNull() {
if isTopLevel { if isTopLevel {
@ -3308,7 +3411,7 @@ func easyjson42239ddeDecodeGithubComKhliengDispatchServer30(in *jlexer.Lexer, ou
in.Consumed() in.Consumed()
} }
} }
func easyjson42239ddeEncodeGithubComKhliengDispatchServer30(out *jwriter.Writer, in ChannelSearchResult) { func easyjson42239ddeEncodeGithubComKhliengDispatchServer31(out *jwriter.Writer, in ChannelSearchResult) {
out.RawByte('{') out.RawByte('{')
first := true first := true
_ = first _ = first
@ -3367,25 +3470,25 @@ func easyjson42239ddeEncodeGithubComKhliengDispatchServer30(out *jwriter.Writer,
// MarshalJSON supports json.Marshaler interface // MarshalJSON supports json.Marshaler interface
func (v ChannelSearchResult) MarshalJSON() ([]byte, error) { func (v ChannelSearchResult) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{} w := jwriter.Writer{}
easyjson42239ddeEncodeGithubComKhliengDispatchServer30(&w, v) easyjson42239ddeEncodeGithubComKhliengDispatchServer31(&w, v)
return w.Buffer.BuildBytes(), w.Error return w.Buffer.BuildBytes(), w.Error
} }
// MarshalEasyJSON supports easyjson.Marshaler interface // MarshalEasyJSON supports easyjson.Marshaler interface
func (v ChannelSearchResult) MarshalEasyJSON(w *jwriter.Writer) { func (v ChannelSearchResult) MarshalEasyJSON(w *jwriter.Writer) {
easyjson42239ddeEncodeGithubComKhliengDispatchServer30(w, v) easyjson42239ddeEncodeGithubComKhliengDispatchServer31(w, v)
} }
// UnmarshalJSON supports json.Unmarshaler interface // UnmarshalJSON supports json.Unmarshaler interface
func (v *ChannelSearchResult) UnmarshalJSON(data []byte) error { func (v *ChannelSearchResult) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data} r := jlexer.Lexer{Data: data}
easyjson42239ddeDecodeGithubComKhliengDispatchServer30(&r, v) easyjson42239ddeDecodeGithubComKhliengDispatchServer31(&r, v)
return r.Error() return r.Error()
} }
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface // UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *ChannelSearchResult) UnmarshalEasyJSON(l *jlexer.Lexer) { func (v *ChannelSearchResult) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson42239ddeDecodeGithubComKhliengDispatchServer30(l, v) easyjson42239ddeDecodeGithubComKhliengDispatchServer31(l, v)
} }
func easyjson42239ddeDecodeGithubComKhliengDispatchStorage1(in *jlexer.Lexer, out *storage.ChannelListItem) { func easyjson42239ddeDecodeGithubComKhliengDispatchStorage1(in *jlexer.Lexer, out *storage.ChannelListItem) {
isTopLevel := in.IsStart() isTopLevel := in.IsStart()
@ -3454,7 +3557,7 @@ func easyjson42239ddeEncodeGithubComKhliengDispatchStorage1(out *jwriter.Writer,
} }
out.RawByte('}') out.RawByte('}')
} }
func easyjson42239ddeDecodeGithubComKhliengDispatchServer31(in *jlexer.Lexer, out *ChannelSearch) { func easyjson42239ddeDecodeGithubComKhliengDispatchServer32(in *jlexer.Lexer, out *ChannelSearch) {
isTopLevel := in.IsStart() isTopLevel := in.IsStart()
if in.IsNull() { if in.IsNull() {
if isTopLevel { if isTopLevel {
@ -3489,7 +3592,7 @@ func easyjson42239ddeDecodeGithubComKhliengDispatchServer31(in *jlexer.Lexer, ou
in.Consumed() in.Consumed()
} }
} }
func easyjson42239ddeEncodeGithubComKhliengDispatchServer31(out *jwriter.Writer, in ChannelSearch) { func easyjson42239ddeEncodeGithubComKhliengDispatchServer32(out *jwriter.Writer, in ChannelSearch) {
out.RawByte('{') out.RawByte('{')
first := true first := true
_ = first _ = first
@ -3525,27 +3628,27 @@ func easyjson42239ddeEncodeGithubComKhliengDispatchServer31(out *jwriter.Writer,
// MarshalJSON supports json.Marshaler interface // MarshalJSON supports json.Marshaler interface
func (v ChannelSearch) MarshalJSON() ([]byte, error) { func (v ChannelSearch) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{} w := jwriter.Writer{}
easyjson42239ddeEncodeGithubComKhliengDispatchServer31(&w, v) easyjson42239ddeEncodeGithubComKhliengDispatchServer32(&w, v)
return w.Buffer.BuildBytes(), w.Error return w.Buffer.BuildBytes(), w.Error
} }
// MarshalEasyJSON supports easyjson.Marshaler interface // MarshalEasyJSON supports easyjson.Marshaler interface
func (v ChannelSearch) MarshalEasyJSON(w *jwriter.Writer) { func (v ChannelSearch) MarshalEasyJSON(w *jwriter.Writer) {
easyjson42239ddeEncodeGithubComKhliengDispatchServer31(w, v) easyjson42239ddeEncodeGithubComKhliengDispatchServer32(w, v)
} }
// UnmarshalJSON supports json.Unmarshaler interface // UnmarshalJSON supports json.Unmarshaler interface
func (v *ChannelSearch) UnmarshalJSON(data []byte) error { func (v *ChannelSearch) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data} r := jlexer.Lexer{Data: data}
easyjson42239ddeDecodeGithubComKhliengDispatchServer31(&r, v) easyjson42239ddeDecodeGithubComKhliengDispatchServer32(&r, v)
return r.Error() return r.Error()
} }
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface // UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *ChannelSearch) UnmarshalEasyJSON(l *jlexer.Lexer) { func (v *ChannelSearch) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson42239ddeDecodeGithubComKhliengDispatchServer31(l, v) easyjson42239ddeDecodeGithubComKhliengDispatchServer32(l, v)
} }
func easyjson42239ddeDecodeGithubComKhliengDispatchServer32(in *jlexer.Lexer, out *ChannelForward) { func easyjson42239ddeDecodeGithubComKhliengDispatchServer33(in *jlexer.Lexer, out *ChannelForward) {
isTopLevel := in.IsStart() isTopLevel := in.IsStart()
if in.IsNull() { if in.IsNull() {
if isTopLevel { if isTopLevel {
@ -3580,7 +3683,7 @@ func easyjson42239ddeDecodeGithubComKhliengDispatchServer32(in *jlexer.Lexer, ou
in.Consumed() in.Consumed()
} }
} }
func easyjson42239ddeEncodeGithubComKhliengDispatchServer32(out *jwriter.Writer, in ChannelForward) { func easyjson42239ddeEncodeGithubComKhliengDispatchServer33(out *jwriter.Writer, in ChannelForward) {
out.RawByte('{') out.RawByte('{')
first := true first := true
_ = first _ = first
@ -3616,27 +3719,27 @@ func easyjson42239ddeEncodeGithubComKhliengDispatchServer32(out *jwriter.Writer,
// MarshalJSON supports json.Marshaler interface // MarshalJSON supports json.Marshaler interface
func (v ChannelForward) MarshalJSON() ([]byte, error) { func (v ChannelForward) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{} w := jwriter.Writer{}
easyjson42239ddeEncodeGithubComKhliengDispatchServer32(&w, v) easyjson42239ddeEncodeGithubComKhliengDispatchServer33(&w, v)
return w.Buffer.BuildBytes(), w.Error return w.Buffer.BuildBytes(), w.Error
} }
// MarshalEasyJSON supports easyjson.Marshaler interface // MarshalEasyJSON supports easyjson.Marshaler interface
func (v ChannelForward) MarshalEasyJSON(w *jwriter.Writer) { func (v ChannelForward) MarshalEasyJSON(w *jwriter.Writer) {
easyjson42239ddeEncodeGithubComKhliengDispatchServer32(w, v) easyjson42239ddeEncodeGithubComKhliengDispatchServer33(w, v)
} }
// UnmarshalJSON supports json.Unmarshaler interface // UnmarshalJSON supports json.Unmarshaler interface
func (v *ChannelForward) UnmarshalJSON(data []byte) error { func (v *ChannelForward) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data} r := jlexer.Lexer{Data: data}
easyjson42239ddeDecodeGithubComKhliengDispatchServer32(&r, v) easyjson42239ddeDecodeGithubComKhliengDispatchServer33(&r, v)
return r.Error() return r.Error()
} }
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface // UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *ChannelForward) UnmarshalEasyJSON(l *jlexer.Lexer) { func (v *ChannelForward) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson42239ddeDecodeGithubComKhliengDispatchServer32(l, v) easyjson42239ddeDecodeGithubComKhliengDispatchServer33(l, v)
} }
func easyjson42239ddeDecodeGithubComKhliengDispatchServer33(in *jlexer.Lexer, out *Away) { func easyjson42239ddeDecodeGithubComKhliengDispatchServer34(in *jlexer.Lexer, out *Away) {
isTopLevel := in.IsStart() isTopLevel := in.IsStart()
if in.IsNull() { if in.IsNull() {
if isTopLevel { if isTopLevel {
@ -3669,7 +3772,7 @@ func easyjson42239ddeDecodeGithubComKhliengDispatchServer33(in *jlexer.Lexer, ou
in.Consumed() in.Consumed()
} }
} }
func easyjson42239ddeEncodeGithubComKhliengDispatchServer33(out *jwriter.Writer, in Away) { func easyjson42239ddeEncodeGithubComKhliengDispatchServer34(out *jwriter.Writer, in Away) {
out.RawByte('{') out.RawByte('{')
first := true first := true
_ = first _ = first
@ -3695,23 +3798,23 @@ func easyjson42239ddeEncodeGithubComKhliengDispatchServer33(out *jwriter.Writer,
// MarshalJSON supports json.Marshaler interface // MarshalJSON supports json.Marshaler interface
func (v Away) MarshalJSON() ([]byte, error) { func (v Away) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{} w := jwriter.Writer{}
easyjson42239ddeEncodeGithubComKhliengDispatchServer33(&w, v) easyjson42239ddeEncodeGithubComKhliengDispatchServer34(&w, v)
return w.Buffer.BuildBytes(), w.Error return w.Buffer.BuildBytes(), w.Error
} }
// MarshalEasyJSON supports easyjson.Marshaler interface // MarshalEasyJSON supports easyjson.Marshaler interface
func (v Away) MarshalEasyJSON(w *jwriter.Writer) { func (v Away) MarshalEasyJSON(w *jwriter.Writer) {
easyjson42239ddeEncodeGithubComKhliengDispatchServer33(w, v) easyjson42239ddeEncodeGithubComKhliengDispatchServer34(w, v)
} }
// UnmarshalJSON supports json.Unmarshaler interface // UnmarshalJSON supports json.Unmarshaler interface
func (v *Away) UnmarshalJSON(data []byte) error { func (v *Away) UnmarshalJSON(data []byte) error {
r := jlexer.Lexer{Data: data} r := jlexer.Lexer{Data: data}
easyjson42239ddeDecodeGithubComKhliengDispatchServer33(&r, v) easyjson42239ddeDecodeGithubComKhliengDispatchServer34(&r, v)
return r.Error() return r.Error()
} }
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface // UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *Away) UnmarshalEasyJSON(l *jlexer.Lexer) { func (v *Away) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson42239ddeDecodeGithubComKhliengDispatchServer33(l, v) easyjson42239ddeDecodeGithubComKhliengDispatchServer34(l, v)
} }

View File

@ -3,6 +3,7 @@ package server
import ( import (
"log" "log"
"net/http" "net/http"
"os"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -10,6 +11,7 @@ import (
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/khlieng/dispatch/config" "github.com/khlieng/dispatch/config"
"github.com/khlieng/dispatch/pkg/https" "github.com/khlieng/dispatch/pkg/https"
"github.com/khlieng/dispatch/pkg/irc"
"github.com/khlieng/dispatch/pkg/session" "github.com/khlieng/dispatch/pkg/session"
"github.com/khlieng/dispatch/storage" "github.com/khlieng/dispatch/storage"
) )
@ -202,9 +204,21 @@ func (d *Dispatch) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
filename := params[2] filename := params[2]
w.Header().Set("Content-Disposition", "attachment; filename="+filename) w.Header().Set("Content-Disposition", "attachment; filename="+filename)
http.ServeFile(w, r, storage.Path.DownloadedFile(state.user.Username, filename))
if pack, ok := state.getPendingDCC(filename); ok {
state.deletePendingDCC(filename)
w.Header().Set("Content-Length", strconv.FormatUint(pack.Length, 10))
irc.DownloadDCC(w, pack, nil)
} else {
file := storage.Path.DownloadedFile(state.user.Username, filename)
http.ServeFile(w, r, file)
if d.Config().DCC.Autoget.Delete {
os.Remove(file)
}
}
} else { } else {
fail(w, http.StatusNotFound) fail(w, http.StatusNotFound)
} }

View File

@ -22,6 +22,7 @@ type State struct {
irc map[string]*irc.Client irc map[string]*irc.Client
connectionState map[string]irc.ConnectionState connectionState map[string]irc.ConnectionState
pendingDCCSends map[string]*irc.DCCSend
ircLock sync.Mutex ircLock sync.Mutex
ws map[string]*wsConn ws map[string]*wsConn
@ -39,6 +40,7 @@ func NewState(user *storage.User, srv *Dispatch) *State {
stateData: stateData{m: map[string]interface{}{}}, stateData: stateData{m: map[string]interface{}{}},
irc: make(map[string]*irc.Client), irc: make(map[string]*irc.Client),
connectionState: make(map[string]irc.ConnectionState), connectionState: make(map[string]irc.ConnectionState),
pendingDCCSends: make(map[string]*irc.DCCSend),
ws: make(map[string]*wsConn), ws: make(map[string]*wsConn),
broadcast: make(chan WSResponse, 32), broadcast: make(chan WSResponse, 32),
srv: srv, srv: srv,
@ -102,6 +104,25 @@ func (s *State) setConnectionState(server string, state irc.ConnectionState) {
s.ircLock.Unlock() s.ircLock.Unlock()
} }
func (s *State) getPendingDCC(filename string) (*irc.DCCSend, bool) {
s.ircLock.Lock()
pack, ok := s.pendingDCCSends[filename]
s.ircLock.Unlock()
return pack, ok
}
func (s *State) setPendingDCC(filename string, pack *irc.DCCSend) {
s.ircLock.Lock()
s.pendingDCCSends[filename] = pack
s.ircLock.Unlock()
}
func (s *State) deletePendingDCC(filename string) {
s.ircLock.Lock()
delete(s.pendingDCCSends, filename)
s.ircLock.Unlock()
}
func (s *State) setWS(addr string, w *wsConn) { func (s *State) setWS(addr string, w *wsConn) {
s.wsLock.Lock() s.wsLock.Lock()
s.ws[addr] = w s.ws[addr] = w