mirror of
https://github.com/schollz/croc.git
synced 2025-10-11 13:21:00 +02:00
remove bits
This commit is contained in:
parent
7c731a90dc
commit
00e52848b5
30 changed files with 538 additions and 6527 deletions
2
go.mod
2
go.mod
|
@ -1,4 +1,4 @@
|
|||
module github.com/schollz/croc
|
||||
module github.com/schollz/croc/v5
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
|
|
541
main.go
541
main.go
|
@ -1,12 +1,545 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/schollz/croc/src/cli"
|
||||
"bytes"
|
||||
"crypto/elliptic"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
"github.com/pions/webrtc"
|
||||
"github.com/pions/webrtc/examples/util"
|
||||
"github.com/pions/webrtc/pkg/datachannel"
|
||||
"github.com/pions/webrtc/pkg/ice"
|
||||
"github.com/schollz/pake"
|
||||
"github.com/schollz/progressbar/v2"
|
||||
)
|
||||
|
||||
var Version string
|
||||
type Client struct {
|
||||
redisdb *redis.Client
|
||||
|
||||
SharedSecret string
|
||||
IsSender bool
|
||||
Pake *pake.Pake
|
||||
|
||||
// steps involved in forming relationship
|
||||
Step1ChannelSecured bool
|
||||
Step2FileInfoTransfered bool
|
||||
Step3RecipientReady bool
|
||||
Step4SendingData bool
|
||||
|
||||
f *os.File
|
||||
FileInfo FileInfo
|
||||
|
||||
// channel data
|
||||
incomingMessageChannel <-chan *redis.Message
|
||||
nameOutChannel string
|
||||
nameInChannel string
|
||||
|
||||
peerConnection *webrtc.RTCPeerConnection
|
||||
dataChannel *webrtc.RTCDataChannel
|
||||
|
||||
quit chan bool
|
||||
}
|
||||
|
||||
type Message struct {
|
||||
Type string
|
||||
Message string
|
||||
Bytes []byte
|
||||
}
|
||||
|
||||
type Chunk struct {
|
||||
Bytes []byte
|
||||
Location int64
|
||||
}
|
||||
|
||||
type FileInfo struct {
|
||||
Name string
|
||||
Size int64
|
||||
ModTime time.Time
|
||||
IsDir bool
|
||||
SentName string
|
||||
IsCompressed bool
|
||||
IsEncrypted bool
|
||||
}
|
||||
|
||||
func (m Message) String() string {
|
||||
b, _ := json.Marshal(m)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func New(sender bool, sharedSecret string) (c *Client, err error) {
|
||||
c = new(Client)
|
||||
c.redisdb = redis.NewClient(&redis.Options{
|
||||
Addr: "198.199.67.130:6372",
|
||||
Password: "",
|
||||
DB: 4,
|
||||
WriteTimeout: 1 * time.Hour,
|
||||
ReadTimeout: 1 * time.Hour,
|
||||
})
|
||||
_, err = c.redisdb.Ping().Result()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.IsSender = sender
|
||||
c.SharedSecret = sharedSecret
|
||||
c.SharedSecret = sharedSecret
|
||||
if sender {
|
||||
c.nameOutChannel = c.SharedSecret + "2"
|
||||
c.nameInChannel = c.SharedSecret + "1"
|
||||
} else {
|
||||
c.nameOutChannel = c.SharedSecret + "1"
|
||||
c.nameInChannel = c.SharedSecret + "2"
|
||||
}
|
||||
|
||||
pubsub := c.redisdb.Subscribe(c.nameInChannel)
|
||||
_, err = pubsub.Receive()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.incomingMessageChannel = pubsub.Channel()
|
||||
|
||||
if c.IsSender {
|
||||
c.Pake, err = pake.Init([]byte{1, 2, 3}, 1, elliptic.P521(), 1*time.Microsecond)
|
||||
} else {
|
||||
c.Pake, err = pake.Init([]byte{1, 2, 3}, 0, elliptic.P521(), 1*time.Microsecond)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) Transfer(fname string) (err error) {
|
||||
c.quit = make(chan bool)
|
||||
// quit with c.quit <- true
|
||||
if !c.IsSender {
|
||||
// kick it off
|
||||
fmt.Println("sending first pake")
|
||||
err = c.redisdb.Publish(c.nameOutChannel, Message{
|
||||
Type: "pake",
|
||||
Bytes: c.Pake.Bytes(),
|
||||
}.String()).Err()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-c.quit:
|
||||
return
|
||||
case msg := <-c.incomingMessageChannel:
|
||||
var m Message
|
||||
err = json.Unmarshal([]byte(msg.Payload), &m)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = c.processMessage(m)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
default:
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
|
||||
if c.IsSender && c.Step1ChannelSecured && !c.Step2FileInfoTransfered {
|
||||
var fstats os.FileInfo
|
||||
fstats, err = os.Stat("test.txt")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.FileInfo = FileInfo{
|
||||
Name: fstats.Name(),
|
||||
Size: fstats.Size(),
|
||||
ModTime: fstats.ModTime(),
|
||||
IsDir: fstats.IsDir(),
|
||||
}
|
||||
b, _ := json.Marshal(c.FileInfo)
|
||||
err = c.redisdb.Publish(c.nameOutChannel, Message{
|
||||
Type: "fileinfo",
|
||||
Bytes: b,
|
||||
}.String()).Err()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.Step2FileInfoTransfered = true
|
||||
}
|
||||
if !c.IsSender && c.Step2FileInfoTransfered && !c.Step3RecipientReady {
|
||||
err = c.redisdb.Publish(c.nameOutChannel, Message{
|
||||
Type: "recipientready",
|
||||
}.String()).Err()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.Step3RecipientReady = true
|
||||
// start receiving data
|
||||
go func() {
|
||||
err = c.dataChannelReceive()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
if c.IsSender && c.Step3RecipientReady && !c.Step4SendingData {
|
||||
fmt.Println("start sending data!")
|
||||
c.Step4SendingData = true
|
||||
go func() {
|
||||
err = c.dataChannelSend()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) sendOverRedis() (err error) {
|
||||
go func() {
|
||||
bar := progressbar.NewOptions(
|
||||
int(c.FileInfo.Size),
|
||||
progressbar.OptionSetRenderBlankState(true),
|
||||
progressbar.OptionSetBytes(int(c.FileInfo.Size)),
|
||||
progressbar.OptionSetWriter(os.Stderr),
|
||||
progressbar.OptionThrottle(1/60*time.Second),
|
||||
)
|
||||
c.f, err = os.Open(c.FileInfo.Name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
location := int64(0)
|
||||
for {
|
||||
buf := make([]byte, 4096*128)
|
||||
n, errRead := c.f.Read(buf)
|
||||
bar.Add(n)
|
||||
chunk := Chunk{
|
||||
Bytes: buf[:n],
|
||||
Location: location,
|
||||
}
|
||||
chunkB, _ := json.Marshal(chunk)
|
||||
err = c.redisdb.Publish(c.nameOutChannel, Message{
|
||||
Type: "chunk",
|
||||
Bytes: chunkB,
|
||||
}.String()).Err()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
location += int64(n)
|
||||
if errRead == io.EOF {
|
||||
break
|
||||
}
|
||||
if errRead != nil {
|
||||
panic(errRead)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) processMessage(m Message) (err error) {
|
||||
switch m.Type {
|
||||
case "pake":
|
||||
notVerified := !c.Pake.IsVerified()
|
||||
err = c.Pake.Update(m.Bytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if (notVerified && c.Pake.IsVerified() && !c.IsSender) || !c.Pake.IsVerified() {
|
||||
err = c.redisdb.Publish(c.nameOutChannel, Message{
|
||||
Type: "pake",
|
||||
Bytes: c.Pake.Bytes(),
|
||||
}.String()).Err()
|
||||
}
|
||||
if c.Pake.IsVerified() {
|
||||
fmt.Println(c.Pake.SessionKey())
|
||||
c.Step1ChannelSecured = true
|
||||
}
|
||||
case "fileinfo":
|
||||
err = json.Unmarshal(m.Bytes, &c.FileInfo)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println(c.FileInfo)
|
||||
c.f, err = os.Create("d.txt")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = c.f.Truncate(c.FileInfo.Size)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.Step2FileInfoTransfered = true
|
||||
case "recipientready":
|
||||
c.Step3RecipientReady = true
|
||||
case "chunk":
|
||||
var chunk Chunk
|
||||
err = json.Unmarshal(m.Bytes, &chunk)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = c.f.WriteAt(chunk.Bytes, chunk.Location)
|
||||
fmt.Println("writing chunk", chunk.Location)
|
||||
case "datachannel-offer":
|
||||
offer := util.Decode(m.Message)
|
||||
fmt.Println("got offer:", m.Message)
|
||||
// Set the remote SessionDescription
|
||||
err = c.peerConnection.SetRemoteDescription(offer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Sets the LocalDescription, and starts our UDP listeners
|
||||
var answer webrtc.RTCSessionDescription
|
||||
answer, err = c.peerConnection.CreateAnswer(nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Output the answer in base64 so we can paste it in browser
|
||||
err = c.redisdb.Publish(c.nameOutChannel, Message{
|
||||
Type: "datachannel-answer",
|
||||
Message: util.Encode(answer),
|
||||
}.String()).Err()
|
||||
case "datachannel-answer":
|
||||
answer := util.Decode(m.Message)
|
||||
// Apply the answer as the remote description
|
||||
err = c.peerConnection.SetRemoteDescription(answer)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) dataChannelReceive() (err error) {
|
||||
// Prepare the configuration
|
||||
config := webrtc.RTCConfiguration{
|
||||
IceServers: []webrtc.RTCIceServer{
|
||||
{
|
||||
URLs: []string{"stun:stun.l.google.com:19302"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create a new RTCPeerConnection
|
||||
c.peerConnection, err = webrtc.New(config)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Set the handler for ICE connection state
|
||||
// This will notify you when the peer has connected/disconnected
|
||||
c.peerConnection.OnICEConnectionStateChange(func(connectionState ice.ConnectionState) {
|
||||
fmt.Printf("ICE Connection State has changed: %s\n", connectionState.String())
|
||||
})
|
||||
|
||||
// Register data channel creation handling
|
||||
c.peerConnection.OnDataChannel(func(d *webrtc.RTCDataChannel) {
|
||||
fmt.Printf("New DataChannel %s %d\n", d.Label, d.ID)
|
||||
sendBytes := make(chan []byte, 1024)
|
||||
// Register channel opening handling
|
||||
d.OnOpen(func() {
|
||||
fmt.Printf("Data channel '%s'-'%d' open. Random messages will now be sent to any connected DataChannels every 5 seconds\n", d.Label, d.ID)
|
||||
for {
|
||||
data := <-sendBytes
|
||||
err := d.Send(datachannel.PayloadBinary{Data: data})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
startTime := false
|
||||
timer := time.Now()
|
||||
var mutex = &sync.Mutex{}
|
||||
piecesToDo := make(map[int64]bool)
|
||||
for i := int64(0); i < c.FileInfo.Size; i += 4096 {
|
||||
piecesToDo[i] = true
|
||||
}
|
||||
// Register message handling
|
||||
d.OnMessage(func(payload datachannel.Payload) {
|
||||
|
||||
switch p := payload.(type) {
|
||||
case *datachannel.PayloadString:
|
||||
fmt.Printf("Message '%s' from DataChannel '%s' payload '%s'\n", p.PayloadType().String(), d.Label, string(p.Data))
|
||||
if bytes.Equal(p.Data, []byte("done")) {
|
||||
c.f.Close()
|
||||
fmt.Println(time.Since(timer))
|
||||
}
|
||||
case *datachannel.PayloadBinary:
|
||||
if !startTime {
|
||||
startTime = true
|
||||
timer = time.Now()
|
||||
}
|
||||
var chunk Chunk
|
||||
errM := json.Unmarshal(p.Data, &chunk)
|
||||
if errM != nil {
|
||||
panic(errM)
|
||||
}
|
||||
var n int
|
||||
mutex.Lock()
|
||||
n, err = c.f.WriteAt(chunk.Bytes, chunk.Location)
|
||||
mutex.Unlock()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
log.Printf("wrote %d bytes to %d", n, chunk.Location)
|
||||
mutex.Lock()
|
||||
piecesToDo[chunk.Location] = false
|
||||
mutex.Unlock()
|
||||
go func() {
|
||||
numToDo := 0
|
||||
thingsToDo := make([]int64, len(piecesToDo))
|
||||
mutex.Lock()
|
||||
for k := range piecesToDo {
|
||||
if piecesToDo[k] {
|
||||
thingsToDo[numToDo] = k
|
||||
numToDo++
|
||||
}
|
||||
}
|
||||
mutex.Unlock()
|
||||
thingsToDo = thingsToDo[:numToDo]
|
||||
fmt.Println("num to do: ", len(thingsToDo))
|
||||
if len(thingsToDo) < 10 {
|
||||
fmt.Println(thingsToDo)
|
||||
}
|
||||
}()
|
||||
default:
|
||||
fmt.Printf("Message '%s' from DataChannel '%s' no payload \n", p.PayloadType().String(), d.Label)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Block forever
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) dataChannelSend() (err error) {
|
||||
recievedBytes := make(chan []byte, 1024)
|
||||
// Everything below is the pion-WebRTC API! Thanks for using it ❤️.
|
||||
|
||||
// Prepare the configuration
|
||||
config := webrtc.RTCConfiguration{
|
||||
IceServers: []webrtc.RTCIceServer{
|
||||
{
|
||||
URLs: []string{"stun:stun1.l.google.com:19305"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Create a new RTCPeerConnection
|
||||
c.peerConnection, err = webrtc.New(config)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Create a datachannel with label 'data'
|
||||
c.dataChannel, err = c.peerConnection.CreateDataChannel("data", nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Set the handler for ICE connection state
|
||||
// This will notify you when the peer has connected/disconnected
|
||||
c.peerConnection.OnICEConnectionStateChange(func(connectionState ice.ConnectionState) {
|
||||
fmt.Printf("ICE Connection State has changed: %s\n", connectionState.String())
|
||||
})
|
||||
|
||||
// Register channel opening handling
|
||||
c.dataChannel.OnOpen(func() {
|
||||
fmt.Printf("Data channel '%s'-'%d' open\n", c.dataChannel.Label, c.dataChannel.ID)
|
||||
time.Sleep(100 * time.Microsecond)
|
||||
|
||||
fmt.Println("sending file")
|
||||
const BufferSize = 4096
|
||||
file, err := os.Open("test.txt")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
buffer := make([]byte, BufferSize)
|
||||
var location int64
|
||||
for {
|
||||
bytesread, err := file.Read(buffer)
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
fmt.Println(err)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
mSend := Chunk{
|
||||
Bytes: buffer[:bytesread],
|
||||
Location: location,
|
||||
}
|
||||
dataToSend, _ := json.Marshal(mSend)
|
||||
|
||||
log.Printf("sending %d bytes at %d", bytesread, location)
|
||||
err = c.dataChannel.Send(datachannel.PayloadBinary{Data: dataToSend})
|
||||
if err != nil {
|
||||
log.Println("Could not send on data channel", err.Error())
|
||||
continue
|
||||
}
|
||||
location += int64(bytesread)
|
||||
time.Sleep(100 * time.Microsecond)
|
||||
}
|
||||
log.Println("sending done signal")
|
||||
err = c.dataChannel.Send(datachannel.PayloadString{Data: []byte("done")})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
})
|
||||
|
||||
// Register the OnMessage to handle incoming messages
|
||||
c.dataChannel.OnMessage(func(payload datachannel.Payload) {
|
||||
switch p := payload.(type) {
|
||||
case *datachannel.PayloadString:
|
||||
fmt.Printf("Message '%s' from DataChannel '%s' payload '%s'\n", p.PayloadType().String(), c.dataChannel.Label, string(p.Data))
|
||||
case *datachannel.PayloadBinary:
|
||||
fmt.Printf("Message '%s' from DataChannel '%s' payload '% 02x'\n", p.PayloadType().String(), c.dataChannel.Label, p.Data)
|
||||
recievedBytes <- p.Data
|
||||
default:
|
||||
fmt.Printf("Message '%s' from DataChannel '%s' no payload \n", p.PayloadType().String(), c.dataChannel.Label)
|
||||
}
|
||||
})
|
||||
|
||||
// Create an offer to send to the browser
|
||||
offer, err := c.peerConnection.CreateOffer(nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Output the offer in base64 so we can paste it in browser
|
||||
fmt.Println("sending offer")
|
||||
err = c.redisdb.Publish(c.nameOutChannel, Message{
|
||||
Type: "datachannel-offer",
|
||||
Message: util.Encode(offer),
|
||||
}.String()).Err()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func main() {
|
||||
cli.Version = Version
|
||||
cli.Run()
|
||||
var sender bool
|
||||
flag.BoolVar(&sender, "sender", false, "sender")
|
||||
flag.Parse()
|
||||
c, err := New(sender, "foo")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = c.Transfer("test.txt")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
|
257
src/cli/cli.go
257
src/cli/cli.go
|
@ -1,257 +0,0 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/schollz/croc/src/croc"
|
||||
"github.com/schollz/croc/src/utils"
|
||||
"github.com/skratchdot/open-golang/open"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var Version string
|
||||
var cr *croc.Croc
|
||||
|
||||
func Run() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
app := cli.NewApp()
|
||||
app.Name = "croc"
|
||||
if Version == "" {
|
||||
Version = "dev"
|
||||
}
|
||||
|
||||
app.Version = Version
|
||||
app.Compiled = time.Now()
|
||||
app.Usage = "easily and securely transfer stuff from one computer to another"
|
||||
app.UsageText = "croc allows any two computers to directly and securely transfer files"
|
||||
// app.ArgsUsage = "[args and such]"
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "send",
|
||||
Usage: "send a file",
|
||||
Description: "send a file over the relay",
|
||||
ArgsUsage: "[filename]",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{Name: "no-compress, o", Usage: "disable compression"},
|
||||
cli.BoolFlag{Name: "no-encrypt, e", Usage: "disable encryption"},
|
||||
cli.StringFlag{Name: "code, c", Usage: "codephrase used to connect to relay"},
|
||||
},
|
||||
HelpName: "croc send",
|
||||
Action: func(c *cli.Context) error {
|
||||
return send(c)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "relay",
|
||||
Usage: "start a croc relay",
|
||||
Description: "the croc relay will handle websocket and TCP connections",
|
||||
Flags: []cli.Flag{},
|
||||
HelpName: "croc relay",
|
||||
Action: func(c *cli.Context) error {
|
||||
return relay(c)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "config",
|
||||
Usage: "generates a config file",
|
||||
Description: "the croc config can be used to set static parameters",
|
||||
Flags: []cli.Flag{},
|
||||
HelpName: "croc config",
|
||||
Action: func(c *cli.Context) error {
|
||||
return saveDefaultConfig(c)
|
||||
},
|
||||
},
|
||||
}
|
||||
app.Flags = []cli.Flag{
|
||||
cli.StringFlag{Name: "addr", Value: "croc4.schollz.com", Usage: "address of the public relay"},
|
||||
cli.StringFlag{Name: "addr-ws", Value: "8153", Usage: "port of the public relay websocket server to connect"},
|
||||
cli.StringFlag{Name: "addr-tcp", Value: "8154,8155,8156,8157,8158,8159,8160,8161", Usage: "tcp ports of the public relay server to connect"},
|
||||
cli.BoolFlag{Name: "no-local", Usage: "disable local mode"},
|
||||
cli.BoolFlag{Name: "local", Usage: "use only local mode"},
|
||||
cli.BoolFlag{Name: "debug", Usage: "increase verbosity (a lot)"},
|
||||
cli.BoolFlag{Name: "yes", Usage: "automatically agree to all prompts"},
|
||||
cli.BoolFlag{Name: "stdout", Usage: "redirect file to stdout"},
|
||||
cli.BoolFlag{Name: "force-tcp", Usage: "force TCP"},
|
||||
cli.BoolFlag{Name: "force-web", Usage: "force websockets"},
|
||||
cli.StringFlag{Name: "port", Value: "8153", Usage: "port that the websocket listens on"},
|
||||
cli.StringFlag{Name: "tcp-port", Value: "8154,8155,8156,8157,8158,8159,8160,8161", Usage: "ports that the tcp server listens on"},
|
||||
cli.StringFlag{Name: "curve", Value: "siec", Usage: "specify elliptic curve to use for PAKE (p256, p384, p521, siec)"},
|
||||
cli.StringFlag{Name: "out", Value: ".", Usage: "specify an output folder to receive the file"},
|
||||
}
|
||||
app.EnableBashCompletion = true
|
||||
app.HideHelp = false
|
||||
app.HideVersion = false
|
||||
app.BashComplete = func(c *cli.Context) {
|
||||
fmt.Fprintf(c.App.Writer, "send\nreceive\relay")
|
||||
}
|
||||
app.Action = func(c *cli.Context) error {
|
||||
// if trying to send but forgot send, let the user know
|
||||
if c.Args().First() != "" && utils.Exists(c.Args().First()) {
|
||||
_, fname := filepath.Split(c.Args().First())
|
||||
yn := utils.GetInput(fmt.Sprintf("Did you mean to send '%s'? (y/n) ", fname))
|
||||
if strings.ToLower(yn) == "y" {
|
||||
return send(c)
|
||||
}
|
||||
}
|
||||
return receive(c)
|
||||
}
|
||||
app.Before = func(c *cli.Context) error {
|
||||
cr = croc.Init(c.GlobalBool("debug"))
|
||||
cr.Version = Version
|
||||
cr.AllowLocalDiscovery = true
|
||||
cr.Address = c.GlobalString("addr")
|
||||
cr.AddressTCPPorts = strings.Split(c.GlobalString("addr-tcp"), ",")
|
||||
cr.AddressWebsocketPort = c.GlobalString("addr-ws")
|
||||
cr.NoRecipientPrompt = c.GlobalBool("yes")
|
||||
cr.Stdout = c.GlobalBool("stdout")
|
||||
cr.LocalOnly = c.GlobalBool("local")
|
||||
cr.NoLocal = c.GlobalBool("no-local")
|
||||
cr.ShowText = true
|
||||
cr.RelayWebsocketPort = c.String("port")
|
||||
cr.RelayTCPPorts = strings.Split(c.String("tcp-port"), ",")
|
||||
cr.CurveType = c.String("curve")
|
||||
if c.GlobalBool("force-tcp") {
|
||||
cr.ForceSend = 2
|
||||
}
|
||||
if c.GlobalBool("force-web") {
|
||||
cr.ForceSend = 1
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "\r\n%s", err.Error())
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\r\n")
|
||||
}
|
||||
|
||||
func saveDefaultConfig(c *cli.Context) error {
|
||||
return croc.SaveDefaultConfig()
|
||||
}
|
||||
|
||||
func send(c *cli.Context) error {
|
||||
stat, _ := os.Stdin.Stat()
|
||||
var fname string
|
||||
if (stat.Mode() & os.ModeCharDevice) == 0 {
|
||||
f, err := ioutil.TempFile(".", "croc-stdin-")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(f, os.Stdin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fname = f.Name()
|
||||
defer func() {
|
||||
err = os.Remove(fname)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
fname = c.Args().First()
|
||||
}
|
||||
if fname == "" {
|
||||
return errors.New("must specify file: croc send [filename]")
|
||||
}
|
||||
cr.UseCompression = !c.Bool("no-compress")
|
||||
cr.UseEncryption = !c.Bool("no-encrypt")
|
||||
if c.String("code") != "" {
|
||||
cr.Codephrase = c.String("code")
|
||||
}
|
||||
cr.LoadConfig()
|
||||
if len(cr.Codephrase) == 0 {
|
||||
// generate code phrase
|
||||
cr.Codephrase = utils.GetRandomName()
|
||||
}
|
||||
|
||||
// print the text
|
||||
finfo, err := os.Stat(fname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fname, _ = filepath.Abs(fname)
|
||||
fname = filepath.Clean(fname)
|
||||
_, filename := filepath.Split(fname)
|
||||
fileOrFolder := "file"
|
||||
fsize := finfo.Size()
|
||||
if finfo.IsDir() {
|
||||
fileOrFolder = "folder"
|
||||
fsize, err = dirSize(fname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(os.Stderr,
|
||||
"Sending %s %s named '%s'\nCode is: %s\nOn the other computer, please run:\n\ncroc %s\n\n",
|
||||
humanize.Bytes(uint64(fsize)),
|
||||
fileOrFolder,
|
||||
filename,
|
||||
cr.Codephrase,
|
||||
cr.Codephrase,
|
||||
)
|
||||
if cr.Debug {
|
||||
croc.SetDebugLevel("debug")
|
||||
}
|
||||
return cr.Send(fname, cr.Codephrase)
|
||||
}
|
||||
|
||||
func receive(c *cli.Context) error {
|
||||
if c.GlobalString("code") != "" {
|
||||
cr.Codephrase = c.GlobalString("code")
|
||||
}
|
||||
if c.Args().First() != "" {
|
||||
cr.Codephrase = c.Args().First()
|
||||
}
|
||||
if c.GlobalString("out") != "" {
|
||||
os.Chdir(c.GlobalString("out"))
|
||||
}
|
||||
cr.LoadConfig()
|
||||
openFolder := false
|
||||
if len(os.Args) == 1 {
|
||||
// open folder since they didn't give any arguments
|
||||
openFolder = true
|
||||
}
|
||||
if cr.Codephrase == "" {
|
||||
cr.Codephrase = utils.GetInput("Enter receive code: ")
|
||||
}
|
||||
if cr.Debug {
|
||||
croc.SetDebugLevel("debug")
|
||||
}
|
||||
err := cr.Receive(cr.Codephrase)
|
||||
if err == nil && openFolder {
|
||||
cwd, _ := os.Getwd()
|
||||
open.Run(cwd)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func relay(c *cli.Context) error {
|
||||
return cr.Relay()
|
||||
}
|
||||
|
||||
func dirSize(path string) (int64, error) {
|
||||
var size int64
|
||||
err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
|
||||
if !info.IsDir() {
|
||||
size += info.Size()
|
||||
}
|
||||
return err
|
||||
})
|
||||
return size, err
|
||||
}
|
120
src/comm/comm.go
120
src/comm/comm.go
|
@ -1,120 +0,0 @@
|
|||
package comm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Comm is some basic TCP communication
|
||||
type Comm struct {
|
||||
connection net.Conn
|
||||
}
|
||||
|
||||
// New returns a new comm
|
||||
func New(c net.Conn) Comm {
|
||||
c.SetReadDeadline(time.Now().Add(3 * time.Hour))
|
||||
c.SetDeadline(time.Now().Add(3 * time.Hour))
|
||||
c.SetWriteDeadline(time.Now().Add(3 * time.Hour))
|
||||
return Comm{c}
|
||||
}
|
||||
|
||||
// Connection returns the net.Conn connection
|
||||
func (c Comm) Connection() net.Conn {
|
||||
return c.connection
|
||||
}
|
||||
|
||||
// Close closes the connection
|
||||
func (c Comm) Close() {
|
||||
c.connection.Close()
|
||||
}
|
||||
|
||||
func (c Comm) Write(b []byte) (int, error) {
|
||||
tmpCopy := make([]byte, len(b)+5)
|
||||
// Copy the buffer so it doesn't get changed while read by the recipient.
|
||||
copy(tmpCopy[:5], []byte(fmt.Sprintf("%0.5d", len(b))))
|
||||
copy(tmpCopy[5:], b)
|
||||
n, err := c.connection.Write(tmpCopy)
|
||||
if n != len(tmpCopy) {
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("wanted to write %d but wrote %d", len(b), n))
|
||||
} else {
|
||||
err = fmt.Errorf("wanted to write %d but wrote %d", len(b), n)
|
||||
}
|
||||
}
|
||||
// log.Printf("wanted to write %d but wrote %d", n, len(b))
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c Comm) Read() (buf []byte, numBytes int, bs []byte, err error) {
|
||||
// read until we get 5 bytes
|
||||
tmp := make([]byte, 5)
|
||||
n, err := c.connection.Read(tmp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tmpCopy := make([]byte, n)
|
||||
// Copy the buffer so it doesn't get changed while read by the recipient.
|
||||
copy(tmpCopy, tmp[:n])
|
||||
bs = tmpCopy
|
||||
|
||||
tmp = make([]byte, 1)
|
||||
for {
|
||||
// see if we have enough bytes
|
||||
bs = bytes.Trim(bs, "\x00")
|
||||
if len(bs) == 5 {
|
||||
break
|
||||
}
|
||||
n, err := c.connection.Read(tmp)
|
||||
if err != nil {
|
||||
return nil, 0, nil, err
|
||||
}
|
||||
tmpCopy = make([]byte, n)
|
||||
// Copy the buffer so it doesn't get changed while read by the recipient.
|
||||
copy(tmpCopy, tmp[:n])
|
||||
bs = append(bs, tmpCopy...)
|
||||
}
|
||||
|
||||
numBytes, err = strconv.Atoi(strings.TrimLeft(string(bs), "0"))
|
||||
if err != nil {
|
||||
return nil, 0, nil, err
|
||||
}
|
||||
buf = []byte{}
|
||||
tmp = make([]byte, numBytes)
|
||||
for {
|
||||
n, err := c.connection.Read(tmp)
|
||||
if err != nil {
|
||||
return nil, 0, nil, err
|
||||
}
|
||||
tmpCopy = make([]byte, n)
|
||||
// Copy the buffer so it doesn't get changed while read by the recipient.
|
||||
copy(tmpCopy, tmp[:n])
|
||||
buf = append(buf, bytes.TrimRight(tmpCopy, "\x00")...)
|
||||
if len(buf) < numBytes {
|
||||
// shrink the amount we need to read
|
||||
tmp = tmp[:numBytes-len(buf)]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
// log.Printf("wanted %d and got %d", numBytes, len(buf))
|
||||
return
|
||||
}
|
||||
|
||||
// Send a message
|
||||
func (c Comm) Send(message string) (err error) {
|
||||
_, err = c.Write([]byte(message))
|
||||
return
|
||||
}
|
||||
|
||||
// Receive a message
|
||||
func (c Comm) Receive() (s string, err error) {
|
||||
b, _, _, err := c.Read()
|
||||
s = string(b)
|
||||
return
|
||||
}
|
|
@ -1,187 +0,0 @@
|
|||
package croc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/schollz/croc/src/utils"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
// Relay parameters
|
||||
RelayWebsocketPort string
|
||||
RelayTCPPorts []string
|
||||
|
||||
// Sender parameters
|
||||
CurveType string
|
||||
|
||||
// Options for connecting to server
|
||||
PublicServerIP string
|
||||
AddressTCPPorts []string
|
||||
AddressWebsocketPort string
|
||||
Timeout time.Duration
|
||||
LocalOnly bool
|
||||
NoLocal bool
|
||||
|
||||
// Options for file transfering
|
||||
UseEncryption bool
|
||||
UseCompression bool
|
||||
AllowLocalDiscovery bool
|
||||
NoRecipientPrompt bool
|
||||
ForceTCP bool
|
||||
ForceWebsockets bool
|
||||
Codephrase string
|
||||
}
|
||||
|
||||
func defaultConfig() Config {
|
||||
c := Config{}
|
||||
cr := Init(false)
|
||||
c.RelayWebsocketPort = cr.RelayWebsocketPort
|
||||
c.RelayTCPPorts = cr.RelayTCPPorts
|
||||
c.CurveType = cr.CurveType
|
||||
c.PublicServerIP = cr.Address
|
||||
c.AddressTCPPorts = cr.AddressTCPPorts
|
||||
c.AddressWebsocketPort = cr.AddressWebsocketPort
|
||||
c.Timeout = cr.Timeout
|
||||
c.LocalOnly = cr.LocalOnly
|
||||
c.NoLocal = cr.NoLocal
|
||||
c.UseEncryption = cr.UseEncryption
|
||||
c.UseCompression = cr.UseCompression
|
||||
c.AllowLocalDiscovery = cr.AllowLocalDiscovery
|
||||
c.NoRecipientPrompt = cr.NoRecipientPrompt
|
||||
c.ForceTCP = false
|
||||
c.ForceWebsockets = false
|
||||
c.Codephrase = ""
|
||||
return c
|
||||
}
|
||||
|
||||
func SaveDefaultConfig() error {
|
||||
homedir, err := homedir.Dir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
os.MkdirAll(path.Join(homedir, ".config", "croc"), 0755)
|
||||
c := defaultConfig()
|
||||
buf := new(bytes.Buffer)
|
||||
toml.NewEncoder(buf).Encode(c)
|
||||
confTOML := buf.String()
|
||||
err = ioutil.WriteFile(path.Join(homedir, ".config", "croc", "config.toml"), []byte(confTOML), 0644)
|
||||
if err == nil {
|
||||
fmt.Printf("Default config file written at '%s'\r\n", filepath.Clean(path.Join(homedir, ".config", "croc", "config.toml")))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// LoadConfig will override parameters
|
||||
func (cr *Croc) LoadConfig() (err error) {
|
||||
homedir, err := homedir.Dir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pathToConfig := path.Join(homedir, ".config", "croc", "config.toml")
|
||||
if !utils.Exists(pathToConfig) {
|
||||
// ignore if doesn't exist
|
||||
return nil
|
||||
}
|
||||
|
||||
var c Config
|
||||
_, err = toml.DecodeFile(pathToConfig, &c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cDefault := defaultConfig()
|
||||
// only load if things are different than defaults
|
||||
// just in case the CLI parameters are used
|
||||
if c.RelayWebsocketPort != cDefault.RelayWebsocketPort && cr.RelayWebsocketPort == cDefault.RelayWebsocketPort {
|
||||
cr.RelayWebsocketPort = c.RelayWebsocketPort
|
||||
fmt.Printf("loaded RelayWebsocketPort from config\n")
|
||||
}
|
||||
if !slicesEqual(c.RelayTCPPorts, cDefault.RelayTCPPorts) && slicesEqual(cr.RelayTCPPorts, cDefault.RelayTCPPorts) {
|
||||
cr.RelayTCPPorts = c.RelayTCPPorts
|
||||
fmt.Printf("loaded RelayTCPPorts from config\n")
|
||||
}
|
||||
if c.CurveType != cDefault.CurveType && cr.CurveType == cDefault.CurveType {
|
||||
cr.CurveType = c.CurveType
|
||||
fmt.Printf("loaded CurveType from config\n")
|
||||
}
|
||||
if c.PublicServerIP != cDefault.PublicServerIP && cr.Address == cDefault.PublicServerIP {
|
||||
cr.Address = c.PublicServerIP
|
||||
fmt.Printf("loaded Address from config\n")
|
||||
}
|
||||
if !slicesEqual(c.AddressTCPPorts, cDefault.AddressTCPPorts) {
|
||||
cr.AddressTCPPorts = c.AddressTCPPorts
|
||||
fmt.Printf("loaded AddressTCPPorts from config\n")
|
||||
}
|
||||
if c.AddressWebsocketPort != cDefault.AddressWebsocketPort && cr.AddressWebsocketPort == cDefault.AddressWebsocketPort {
|
||||
cr.AddressWebsocketPort = c.AddressWebsocketPort
|
||||
fmt.Printf("loaded AddressWebsocketPort from config\n")
|
||||
}
|
||||
if c.Timeout != cDefault.Timeout && cr.Timeout == cDefault.Timeout {
|
||||
cr.Timeout = c.Timeout
|
||||
fmt.Printf("loaded Timeout from config\n")
|
||||
}
|
||||
if c.LocalOnly != cDefault.LocalOnly && cr.LocalOnly == cDefault.LocalOnly {
|
||||
cr.LocalOnly = c.LocalOnly
|
||||
fmt.Printf("loaded LocalOnly from config\n")
|
||||
}
|
||||
if c.NoLocal != cDefault.NoLocal && cr.NoLocal == cDefault.NoLocal {
|
||||
cr.NoLocal = c.NoLocal
|
||||
fmt.Printf("loaded NoLocal from config\n")
|
||||
}
|
||||
if c.UseEncryption != cDefault.UseEncryption && cr.UseEncryption == cDefault.UseEncryption {
|
||||
cr.UseEncryption = c.UseEncryption
|
||||
fmt.Printf("loaded UseEncryption from config\n")
|
||||
}
|
||||
if c.UseCompression != cDefault.UseCompression && cr.UseCompression == cDefault.UseCompression {
|
||||
cr.UseCompression = c.UseCompression
|
||||
fmt.Printf("loaded UseCompression from config\n")
|
||||
}
|
||||
if c.AllowLocalDiscovery != cDefault.AllowLocalDiscovery && cr.AllowLocalDiscovery == cDefault.AllowLocalDiscovery {
|
||||
cr.AllowLocalDiscovery = c.AllowLocalDiscovery
|
||||
fmt.Printf("loaded AllowLocalDiscovery from config\n")
|
||||
}
|
||||
if c.NoRecipientPrompt != cDefault.NoRecipientPrompt && cr.NoRecipientPrompt == cDefault.NoRecipientPrompt {
|
||||
cr.NoRecipientPrompt = c.NoRecipientPrompt
|
||||
fmt.Printf("loaded NoRecipientPrompt from config\n")
|
||||
}
|
||||
if c.ForceWebsockets {
|
||||
cr.ForceSend = 1
|
||||
}
|
||||
if c.ForceTCP {
|
||||
cr.ForceSend = 2
|
||||
}
|
||||
if c.Codephrase != cDefault.Codephrase && cr.Codephrase == cDefault.Codephrase {
|
||||
cr.Codephrase = c.Codephrase
|
||||
fmt.Printf("loaded Codephrase from config\n")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// slicesEqual checcks if two slices are equal
|
||||
// from https://stackoverflow.com/a/15312097
|
||||
func slicesEqual(a, b []string) bool {
|
||||
// If one is nil, the other must also be nil.
|
||||
if (a == nil) != (b == nil) {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
package croc
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/schollz/croc/src/logger"
|
||||
"github.com/schollz/croc/src/models"
|
||||
"github.com/schollz/croc/src/relay"
|
||||
"github.com/schollz/croc/src/zipper"
|
||||
"github.com/schollz/progressbar/v2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
}
|
||||
|
||||
// Croc options
|
||||
type Croc struct {
|
||||
// Version is the version of croc
|
||||
Version string
|
||||
// Options for all
|
||||
Debug bool
|
||||
// ShowText will display text on the stderr
|
||||
ShowText bool
|
||||
|
||||
// Options for relay
|
||||
RelayWebsocketPort string
|
||||
RelayTCPPorts []string
|
||||
CurveType string
|
||||
|
||||
// Options for connecting to server
|
||||
Address string
|
||||
AddressTCPPorts []string
|
||||
AddressWebsocketPort string
|
||||
Timeout time.Duration
|
||||
LocalOnly bool
|
||||
NoLocal bool
|
||||
|
||||
// Options for file transfering
|
||||
UseEncryption bool
|
||||
UseCompression bool
|
||||
AllowLocalDiscovery bool
|
||||
NoRecipientPrompt bool
|
||||
Stdout bool
|
||||
ForceSend int // 0: ignore, 1: websockets, 2: TCP
|
||||
|
||||
// Parameters for file transfer
|
||||
Filename string
|
||||
Codephrase string
|
||||
|
||||
// localIP address
|
||||
localIP string
|
||||
// is using local relay
|
||||
isLocal bool
|
||||
normalFinish bool
|
||||
|
||||
// state variables
|
||||
StateString string
|
||||
Bar *progressbar.ProgressBar
|
||||
FileInfo models.FileStats
|
||||
OtherIP string
|
||||
|
||||
// special for window
|
||||
WindowRecipientPrompt bool
|
||||
WindowRecipientAccept bool
|
||||
WindowReceivingString string
|
||||
}
|
||||
|
||||
// Init will initiate with the default parameters
|
||||
func Init(debug bool) (c *Croc) {
|
||||
c = new(Croc)
|
||||
c.UseCompression = true
|
||||
c.UseEncryption = true
|
||||
c.AllowLocalDiscovery = true
|
||||
c.RelayWebsocketPort = "8153"
|
||||
c.RelayTCPPorts = []string{"8154", "8155", "8156", "8157", "8158", "8159", "8160", "8161"}
|
||||
c.CurveType = "siec"
|
||||
c.Address = "croc4.schollz.com"
|
||||
c.AddressWebsocketPort = "8153"
|
||||
c.AddressTCPPorts = []string{"8154", "8155", "8156", "8157", "8158", "8159", "8160", "8161"}
|
||||
c.NoRecipientPrompt = true
|
||||
debugLevel := "info"
|
||||
if debug {
|
||||
debugLevel = "debug"
|
||||
c.Debug = true
|
||||
}
|
||||
SetDebugLevel(debugLevel)
|
||||
return
|
||||
}
|
||||
|
||||
func SetDebugLevel(debugLevel string) {
|
||||
logger.SetLogLevel(debugLevel)
|
||||
relay.DebugLevel = debugLevel
|
||||
zipper.DebugLevel = debugLevel
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
package croc
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/schollz/croc/src/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func sendAndReceive(t *testing.T, forceSend int, local bool) {
|
||||
room := utils.GetRandomName()
|
||||
var startTime time.Time
|
||||
var durationPerMegabyte float64
|
||||
megabytes := 1
|
||||
if local {
|
||||
megabytes = 100
|
||||
}
|
||||
fname := generateRandomFile(megabytes)
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
c := Init(true)
|
||||
c.NoLocal = !local
|
||||
// c.AddressTCPPorts = []string{"8154", "8155"}
|
||||
c.ForceSend = forceSend
|
||||
c.UseCompression = true
|
||||
c.UseEncryption = true
|
||||
assert.Nil(t, c.Send(fname, room))
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
time.Sleep(5 * time.Second)
|
||||
os.MkdirAll("test", 0755)
|
||||
os.Chdir("test")
|
||||
c := Init(true)
|
||||
c.NoLocal = !local
|
||||
// c.AddressTCPPorts = []string{"8154", "8155"}
|
||||
c.ForceSend = forceSend
|
||||
startTime = time.Now()
|
||||
assert.Nil(t, c.Receive(room))
|
||||
durationPerMegabyte = float64(megabytes) / time.Since(startTime).Seconds()
|
||||
assert.True(t, utils.Exists(fname))
|
||||
}()
|
||||
wg.Wait()
|
||||
os.Chdir("..")
|
||||
os.RemoveAll("test")
|
||||
os.Remove(fname)
|
||||
fmt.Printf("\n-----\n%2.1f MB/s\n----\n", durationPerMegabyte)
|
||||
}
|
||||
|
||||
func TestSendReceivePubWebsockets(t *testing.T) {
|
||||
sendAndReceive(t, 1, false)
|
||||
}
|
||||
|
||||
func TestSendReceivePubTCP(t *testing.T) {
|
||||
sendAndReceive(t, 2, false)
|
||||
}
|
||||
|
||||
func TestSendReceiveLocalWebsockets(t *testing.T) {
|
||||
sendAndReceive(t, 1, true)
|
||||
}
|
||||
|
||||
// func TestSendReceiveLocalTCP(t *testing.T) {
|
||||
// sendAndReceive(t, 2, true)
|
||||
// }
|
||||
|
||||
func generateRandomFile(megabytes int) (fname string) {
|
||||
// generate a random file
|
||||
bigBuff := make([]byte, 1024*1024*megabytes)
|
||||
rand.Read(bigBuff)
|
||||
fname = fmt.Sprintf("%dmb.file", megabytes)
|
||||
ioutil.WriteFile(fname, bigBuff, 0666)
|
||||
return
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package croc
|
||||
|
||||
type WebSocketMessage struct {
|
||||
messageType int
|
||||
message []byte
|
||||
err error
|
||||
}
|
|
@ -1,624 +0,0 @@
|
|||
package croc
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/cihub/seelog"
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/schollz/croc/src/comm"
|
||||
"github.com/schollz/croc/src/compress"
|
||||
"github.com/schollz/croc/src/crypt"
|
||||
"github.com/schollz/croc/src/logger"
|
||||
"github.com/schollz/croc/src/models"
|
||||
"github.com/schollz/croc/src/utils"
|
||||
"github.com/schollz/croc/src/zipper"
|
||||
"github.com/schollz/pake"
|
||||
"github.com/schollz/progressbar/v2"
|
||||
"github.com/schollz/spinner"
|
||||
)
|
||||
|
||||
var DebugLevel string
|
||||
|
||||
// Receive is the async operation to receive a file
|
||||
func (cr *Croc) startRecipient(forceSend int, serverAddress string, tcpPorts []string, isLocal bool, done chan error, c *websocket.Conn, codephrase string, noPrompt bool, useStdout bool) {
|
||||
logger.SetLogLevel(DebugLevel)
|
||||
err := cr.receive(forceSend, serverAddress, tcpPorts, isLocal, c, codephrase, noPrompt, useStdout)
|
||||
if err != nil && strings.HasPrefix(err.Error(), "websocket: close 100") {
|
||||
err = nil
|
||||
}
|
||||
done <- err
|
||||
}
|
||||
|
||||
func (cr *Croc) receive(forceSend int, serverAddress string, tcpPorts []string, isLocal bool, c *websocket.Conn, codephrase string, noPrompt bool, useStdout bool) (err error) {
|
||||
var sessionKey []byte
|
||||
var transferTime time.Duration
|
||||
var hash256 []byte
|
||||
var progressFile string
|
||||
var resumeFile bool
|
||||
var tcpConnections []comm.Comm
|
||||
var Q *pake.Pake
|
||||
|
||||
dataChan := make(chan []byte, 1024*1024)
|
||||
isConnectedIfUsingTCP := make(chan bool)
|
||||
blocks := []string{}
|
||||
|
||||
useWebsockets := true
|
||||
switch forceSend {
|
||||
case 0:
|
||||
if !isLocal {
|
||||
useWebsockets = false
|
||||
}
|
||||
case 1:
|
||||
useWebsockets = true
|
||||
case 2:
|
||||
useWebsockets = false
|
||||
}
|
||||
|
||||
// start a spinner
|
||||
spin := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
|
||||
spin.Writer = os.Stderr
|
||||
spin.Suffix = " connecting..."
|
||||
cr.StateString = "Connecting as recipient..."
|
||||
spin.Start()
|
||||
defer spin.Stop()
|
||||
|
||||
// both parties should have a weak key
|
||||
pw := []byte(codephrase)
|
||||
|
||||
// start the reader
|
||||
websocketMessages := make(chan WebSocketMessage, 1024)
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Debugf("recovered from %s", r)
|
||||
}
|
||||
}()
|
||||
for {
|
||||
messageType, message, err := c.ReadMessage()
|
||||
websocketMessages <- WebSocketMessage{messageType, message, err}
|
||||
}
|
||||
}()
|
||||
|
||||
step := 0
|
||||
for {
|
||||
var websocketMessageMain WebSocketMessage
|
||||
// websocketMessageMain = <-websocketMessages
|
||||
timeWaitingForMessage := time.Now()
|
||||
for {
|
||||
done := false
|
||||
select {
|
||||
case websocketMessageMain = <-websocketMessages:
|
||||
done = true
|
||||
default:
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
if done {
|
||||
break
|
||||
}
|
||||
if time.Since(timeWaitingForMessage).Seconds() > 3 && step == 0 {
|
||||
return fmt.Errorf("You are trying to receive a file with no sender.")
|
||||
}
|
||||
}
|
||||
|
||||
messageType := websocketMessageMain.messageType
|
||||
message := websocketMessageMain.message
|
||||
err := websocketMessageMain.err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if messageType == websocket.PongMessage || messageType == websocket.PingMessage {
|
||||
continue
|
||||
}
|
||||
if messageType == websocket.TextMessage && bytes.Equal(message, []byte("interrupt")) {
|
||||
return errors.New("\rinterrupted by other party")
|
||||
}
|
||||
|
||||
log.Debugf("got %d: %s", messageType, message)
|
||||
switch step {
|
||||
case 0:
|
||||
spin.Stop()
|
||||
spin.Suffix = " performing PAKE..."
|
||||
cr.StateString = "Performing PAKE..."
|
||||
spin.Start()
|
||||
// sender has initiated, sends their initial data
|
||||
var initialData models.Initial
|
||||
err = json.Unmarshal(message, &initialData)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "incompatible versions of croc")
|
||||
return err
|
||||
}
|
||||
cr.OtherIP = initialData.IPAddress
|
||||
log.Debugf("sender IP: %s", cr.OtherIP)
|
||||
|
||||
// check whether the version strings are compatible
|
||||
versionStringsOther := strings.Split(initialData.VersionString, ".")
|
||||
versionStringsSelf := strings.Split(cr.Version, ".")
|
||||
if len(versionStringsOther) == 3 && len(versionStringsSelf) == 3 {
|
||||
if versionStringsSelf[0] != versionStringsOther[0] || versionStringsSelf[1] != versionStringsOther[1] {
|
||||
return fmt.Errorf("version sender %s is not compatible with recipient %s", cr.Version, initialData.VersionString)
|
||||
}
|
||||
}
|
||||
|
||||
// initialize the PAKE with the curve sent from the sender
|
||||
Q, err = pake.InitCurve(pw, 1, initialData.CurveType, 1*time.Millisecond)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "incompatible curve type")
|
||||
return err
|
||||
}
|
||||
|
||||
// recipient begins by sending back initial data to sender
|
||||
ip := ""
|
||||
if isLocal {
|
||||
ip = utils.LocalIP()
|
||||
} else {
|
||||
ip, _ = utils.PublicIP()
|
||||
}
|
||||
initialData.VersionString = cr.Version
|
||||
initialData.IPAddress = ip
|
||||
bInitialData, _ := json.Marshal(initialData)
|
||||
c.WriteMessage(websocket.BinaryMessage, bInitialData)
|
||||
case 1:
|
||||
// Q receives u
|
||||
log.Debugf("[%d] Q computes k, sends H(k), v back to P", step)
|
||||
if err := Q.Update(message); err != nil {
|
||||
return fmt.Errorf("Recipient is using wrong code phrase.")
|
||||
}
|
||||
|
||||
// Q has the session key now, but we will still check if its valid
|
||||
sessionKey, err = Q.SessionKey()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Recipient is using wrong code phrase.")
|
||||
}
|
||||
log.Debugf("%x\n", sessionKey)
|
||||
|
||||
// initialize TCP connections if using (possible, but unlikely, race condition)
|
||||
go func() {
|
||||
log.Debug("initializing TCP connections")
|
||||
if !useWebsockets {
|
||||
log.Debugf("connecting to server")
|
||||
tcpConnections = make([]comm.Comm, len(tcpPorts))
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(tcpPorts))
|
||||
for i, tcpPort := range tcpPorts {
|
||||
go func(i int, tcpPort string) {
|
||||
defer wg.Done()
|
||||
log.Debugf("connecting to %d", i)
|
||||
var message string
|
||||
tcpConnections[i], message, err = connectToTCPServer(utils.SHA256(fmt.Sprintf("%d%x", i, sessionKey)), serverAddress+":"+tcpPort)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
if message != "recipient" {
|
||||
log.Errorf("got wrong message: %s", message)
|
||||
}
|
||||
}(i, tcpPort)
|
||||
}
|
||||
wg.Wait()
|
||||
log.Debugf("fully connected")
|
||||
}
|
||||
isConnectedIfUsingTCP <- true
|
||||
}()
|
||||
|
||||
c.WriteMessage(websocket.BinaryMessage, Q.Bytes())
|
||||
case 2:
|
||||
log.Debugf("[%d] Q recieves H(k) from P", step)
|
||||
// check if everything is still kosher with our computed session key
|
||||
if err := Q.Update(message); err != nil {
|
||||
log.Debug(err)
|
||||
return fmt.Errorf("Recipient is using wrong code phrase.")
|
||||
}
|
||||
c.WriteMessage(websocket.BinaryMessage, []byte("ready"))
|
||||
case 3:
|
||||
spin.Stop()
|
||||
cr.StateString = "Recieving file info..."
|
||||
|
||||
// unmarshal the file info
|
||||
log.Debugf("[%d] recieve file info", step)
|
||||
// do decryption on the file stats
|
||||
enc, err := crypt.FromBytes(message)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
decryptedFileData, err := enc.Decrypt(sessionKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal(decryptedFileData, &cr.FileInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("got file stats: %+v", cr.FileInfo)
|
||||
|
||||
// determine if the file is resuming or not
|
||||
progressFile = fmt.Sprintf("%s.progress", cr.FileInfo.SentName)
|
||||
overwritingOrReceiving := "Receiving"
|
||||
if utils.Exists(cr.FileInfo.Name) || utils.Exists(cr.FileInfo.SentName) {
|
||||
overwritingOrReceiving = "Overwriting"
|
||||
if utils.Exists(progressFile) {
|
||||
overwritingOrReceiving = "Resume receiving"
|
||||
resumeFile = true
|
||||
}
|
||||
}
|
||||
|
||||
// send blocks
|
||||
if resumeFile {
|
||||
fileWithBlocks, _ := os.Open(progressFile)
|
||||
scanner := bufio.NewScanner(fileWithBlocks)
|
||||
for scanner.Scan() {
|
||||
blocks = append(blocks, strings.TrimSpace(scanner.Text()))
|
||||
}
|
||||
fileWithBlocks.Close()
|
||||
}
|
||||
blocksBytes, _ := json.Marshal(blocks)
|
||||
// encrypt the block data and send
|
||||
encblockBytes := crypt.Encrypt(blocksBytes, sessionKey)
|
||||
|
||||
// wait for TCP connections if using them
|
||||
_ = <-isConnectedIfUsingTCP
|
||||
c.WriteMessage(websocket.BinaryMessage, encblockBytes.Bytes())
|
||||
|
||||
// prompt user about the file
|
||||
fileOrFolder := "file"
|
||||
if cr.FileInfo.IsDir {
|
||||
fileOrFolder = "folder"
|
||||
}
|
||||
cr.WindowReceivingString = fmt.Sprintf("%s %s (%s) into: %s",
|
||||
overwritingOrReceiving,
|
||||
fileOrFolder,
|
||||
humanize.Bytes(uint64(cr.FileInfo.Size)),
|
||||
cr.FileInfo.Name,
|
||||
)
|
||||
fmt.Fprintf(os.Stderr, "\r%s\n",
|
||||
cr.WindowReceivingString,
|
||||
)
|
||||
if !noPrompt {
|
||||
if "y" != utils.GetInput("ok? (y/N): ") {
|
||||
fmt.Fprintf(os.Stderr, "Cancelling request")
|
||||
c.WriteMessage(websocket.BinaryMessage, []byte("no"))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if cr.WindowRecipientPrompt {
|
||||
// wait until it switches to false
|
||||
// the window should then set WindowRecipientAccept
|
||||
for {
|
||||
if !cr.WindowRecipientPrompt {
|
||||
if cr.WindowRecipientAccept {
|
||||
break
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Cancelling request")
|
||||
c.WriteMessage(websocket.BinaryMessage, []byte("no"))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
// await file
|
||||
// erase file if overwriting
|
||||
if overwritingOrReceiving == "Overwriting" {
|
||||
os.Remove(cr.FileInfo.SentName)
|
||||
}
|
||||
var f *os.File
|
||||
if utils.Exists(cr.FileInfo.SentName) && resumeFile {
|
||||
if !useWebsockets {
|
||||
f, err = os.OpenFile(cr.FileInfo.SentName, os.O_WRONLY, 0644)
|
||||
} else {
|
||||
f, err = os.OpenFile(cr.FileInfo.SentName, os.O_APPEND|os.O_WRONLY, 0644)
|
||||
}
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
f, err = os.Create(cr.FileInfo.SentName)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
if !useWebsockets {
|
||||
if err = f.Truncate(cr.FileInfo.Size); err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blockSize := 0
|
||||
if useWebsockets {
|
||||
blockSize = models.WEBSOCKET_BUFFER_SIZE / 8
|
||||
} else {
|
||||
blockSize = models.TCP_BUFFER_SIZE / 2
|
||||
}
|
||||
// start the ui for pgoress
|
||||
cr.StateString = "Recieving file..."
|
||||
bytesWritten := 0
|
||||
fmt.Fprintf(os.Stderr, "\nReceiving (<-%s)...\n", cr.OtherIP)
|
||||
cr.Bar = progressbar.NewOptions(
|
||||
int(cr.FileInfo.Size),
|
||||
progressbar.OptionSetRenderBlankState(true),
|
||||
progressbar.OptionSetBytes(int(cr.FileInfo.Size)),
|
||||
progressbar.OptionSetWriter(os.Stderr),
|
||||
progressbar.OptionThrottle(1/60*time.Second),
|
||||
)
|
||||
cr.Bar.Add((len(blocks) * blockSize))
|
||||
finished := make(chan bool)
|
||||
|
||||
go func(finished chan bool, dataChan chan []byte) (err error) {
|
||||
// remove previous progress
|
||||
var fProgress *os.File
|
||||
var progressErr error
|
||||
if resumeFile {
|
||||
fProgress, progressErr = os.OpenFile(progressFile, os.O_APPEND|os.O_WRONLY, 0644)
|
||||
bytesWritten = len(blocks) * blockSize
|
||||
} else {
|
||||
os.Remove(progressFile)
|
||||
fProgress, progressErr = os.Create(progressFile)
|
||||
}
|
||||
if progressErr != nil {
|
||||
panic(progressErr)
|
||||
}
|
||||
defer fProgress.Close()
|
||||
|
||||
blocksWritten := 0.0
|
||||
blocksToWrite := float64(cr.FileInfo.Size)
|
||||
if useWebsockets {
|
||||
blocksToWrite = blocksToWrite/float64(models.WEBSOCKET_BUFFER_SIZE/8) - float64(len(blocks))
|
||||
} else {
|
||||
blocksToWrite = blocksToWrite/float64(models.TCP_BUFFER_SIZE/2) - float64(len(blocks))
|
||||
}
|
||||
for {
|
||||
message := <-dataChan
|
||||
// do decryption
|
||||
var enc crypt.Encryption
|
||||
err = json.Unmarshal(message, &enc)
|
||||
if err != nil {
|
||||
// log.Errorf("%s: [%s] [%+v] (%d/%d) %+v", err.Error(), message, message, len(message), numBytes, bs)
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
decrypted, err := enc.Decrypt(sessionKey, !cr.FileInfo.IsEncrypted)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// get location if TCP
|
||||
var locationToWrite int
|
||||
if !useWebsockets {
|
||||
pieces := bytes.SplitN(decrypted, []byte("-"), 2)
|
||||
decrypted = pieces[1]
|
||||
locationToWrite, _ = strconv.Atoi(string(pieces[0]))
|
||||
}
|
||||
|
||||
// do decompression
|
||||
if cr.FileInfo.IsCompressed && !cr.FileInfo.IsDir {
|
||||
decrypted = compress.Decompress(decrypted)
|
||||
}
|
||||
|
||||
var n int
|
||||
if !useWebsockets {
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
n, err = f.WriteAt(decrypted, int64(locationToWrite))
|
||||
fProgress.WriteString(fmt.Sprintf("%d\n", locationToWrite))
|
||||
log.Debugf("wrote %d bytes to location %d (%2.0f/%2.0f)", n, locationToWrite, blocksWritten, blocksToWrite)
|
||||
} else {
|
||||
// write to file
|
||||
n, err = f.Write(decrypted)
|
||||
log.Debugf("wrote %d bytes to location %d (%2.0f/%2.0f)", n, bytesWritten, blocksWritten, blocksToWrite)
|
||||
fProgress.WriteString(fmt.Sprintf("%d\n", bytesWritten))
|
||||
}
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// update the bytes written
|
||||
bytesWritten += n
|
||||
blocksWritten += 1.0
|
||||
// update the progress bar
|
||||
cr.Bar.Add(n)
|
||||
if int64(bytesWritten) == cr.FileInfo.Size || blocksWritten >= blocksToWrite {
|
||||
log.Debug("finished", int64(bytesWritten), cr.FileInfo.Size, blocksWritten, blocksToWrite)
|
||||
break
|
||||
}
|
||||
}
|
||||
finished <- true
|
||||
return
|
||||
}(finished, dataChan)
|
||||
|
||||
log.Debug("telling sender i'm ready")
|
||||
c.WriteMessage(websocket.BinaryMessage, []byte("ready"))
|
||||
|
||||
startTime := time.Now()
|
||||
if useWebsockets {
|
||||
for {
|
||||
// read from websockets
|
||||
websocketMessageData := <-websocketMessages
|
||||
if bytes.HasPrefix(websocketMessageData.message, []byte("error")) {
|
||||
return fmt.Errorf("%s", websocketMessageData.message)
|
||||
}
|
||||
if websocketMessageData.messageType != websocket.BinaryMessage {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
if bytes.Equal(websocketMessageData.message, []byte("magic")) {
|
||||
log.Debug("got magic")
|
||||
break
|
||||
}
|
||||
dataChan <- websocketMessageData.message
|
||||
}
|
||||
} else {
|
||||
log.Debugf("starting listening with tcp with %d connections", len(tcpConnections))
|
||||
|
||||
// check to see if any messages are sent
|
||||
stopMessageSignal := make(chan bool, 1)
|
||||
errorsDuringTransfer := make(chan error, 24)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case sig := <-stopMessageSignal:
|
||||
errorsDuringTransfer <- nil
|
||||
log.Debugf("got message signal: %+v", sig)
|
||||
return
|
||||
case wsMessage := <-websocketMessages:
|
||||
log.Debugf("got message: %s", wsMessage.message)
|
||||
if bytes.HasPrefix(wsMessage.message, []byte("error")) {
|
||||
log.Debug("stopping transfer")
|
||||
for i := 0; i < len(tcpConnections)+1; i++ {
|
||||
errorsDuringTransfer <- fmt.Errorf("%s", wsMessage.message)
|
||||
}
|
||||
return
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// using TCP
|
||||
go func() {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(tcpConnections))
|
||||
for i := range tcpConnections {
|
||||
defer func(i int) {
|
||||
log.Debugf("closing connection %d", i)
|
||||
tcpConnections[i].Close()
|
||||
}(i)
|
||||
go func(wg *sync.WaitGroup, j int) {
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case _ = <-errorsDuringTransfer:
|
||||
log.Debugf("%d got stop", i)
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
log.Debugf("waiting to read on %d", j)
|
||||
// read from TCP connection
|
||||
message, _, _, err := tcpConnections[j].Read()
|
||||
// log.Debugf("message: %s", message)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if bytes.Equal(message, []byte("magic")) {
|
||||
log.Debugf("%d got magic, leaving", j)
|
||||
return
|
||||
}
|
||||
dataChan <- message
|
||||
}
|
||||
}(&wg, i)
|
||||
}
|
||||
log.Debug("waiting for tcp goroutines")
|
||||
wg.Wait()
|
||||
errorsDuringTransfer <- nil
|
||||
}()
|
||||
|
||||
// block until this is done
|
||||
|
||||
log.Debug("waiting for error")
|
||||
errorDuringTransfer := <-errorsDuringTransfer
|
||||
log.Debug("sending stop message signal")
|
||||
stopMessageSignal <- true
|
||||
if errorDuringTransfer != nil {
|
||||
log.Debugf("got error during transfer: %s", errorDuringTransfer.Error())
|
||||
return errorDuringTransfer
|
||||
}
|
||||
}
|
||||
|
||||
_ = <-finished
|
||||
log.Debug("telling sender i'm done")
|
||||
c.WriteMessage(websocket.BinaryMessage, []byte("done"))
|
||||
// we are finished
|
||||
transferTime = time.Since(startTime)
|
||||
|
||||
// close file
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// finish bar
|
||||
cr.Bar.Finish()
|
||||
|
||||
// check hash
|
||||
hash256, err = utils.HashFile(cr.FileInfo.SentName)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
// tell the sender the hash so they can quit
|
||||
c.WriteMessage(websocket.BinaryMessage, append([]byte("hash:"), hash256...))
|
||||
case 4:
|
||||
// receive the hash from the sender so we can check it and quit
|
||||
log.Debugf("got hash: %x", message)
|
||||
if bytes.Equal(hash256, message) {
|
||||
// open directory
|
||||
if cr.FileInfo.IsDir {
|
||||
err = zipper.UnzipFile(cr.FileInfo.SentName, ".")
|
||||
if DebugLevel != "debug" {
|
||||
os.Remove(cr.FileInfo.SentName)
|
||||
}
|
||||
} else {
|
||||
err = nil
|
||||
}
|
||||
if err == nil {
|
||||
if useStdout && !cr.FileInfo.IsDir {
|
||||
var bFile []byte
|
||||
bFile, err = ioutil.ReadFile(cr.FileInfo.SentName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
os.Stdout.Write(bFile)
|
||||
os.Remove(cr.FileInfo.SentName)
|
||||
}
|
||||
transferRate := float64(cr.FileInfo.Size) / 1000000.0 / transferTime.Seconds()
|
||||
transferType := "MB/s"
|
||||
if transferRate < 1 {
|
||||
transferRate = float64(cr.FileInfo.Size) / 1000.0 / transferTime.Seconds()
|
||||
transferType = "kB/s"
|
||||
}
|
||||
folderOrFile := "file"
|
||||
if cr.FileInfo.IsDir {
|
||||
folderOrFile = "folder"
|
||||
}
|
||||
if useStdout {
|
||||
cr.FileInfo.Name = "stdout"
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\nReceived %s written to %s (%2.1f %s)", folderOrFile, cr.FileInfo.Name, transferRate, transferType)
|
||||
os.Remove(progressFile)
|
||||
cr.StateString = fmt.Sprintf("Received %s written to %s (%2.1f %s)", folderOrFile, cr.FileInfo.Name, transferRate, transferType)
|
||||
}
|
||||
return err
|
||||
} else {
|
||||
if DebugLevel != "debug" {
|
||||
log.Debug("removing corrupted file")
|
||||
os.Remove(cr.FileInfo.SentName)
|
||||
}
|
||||
return errors.New("file corrupted")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown step")
|
||||
}
|
||||
step++
|
||||
}
|
||||
}
|
|
@ -1,570 +0,0 @@
|
|||
package croc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/cihub/seelog"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/schollz/croc/src/comm"
|
||||
"github.com/schollz/croc/src/compress"
|
||||
"github.com/schollz/croc/src/crypt"
|
||||
"github.com/schollz/croc/src/logger"
|
||||
"github.com/schollz/croc/src/models"
|
||||
"github.com/schollz/croc/src/utils"
|
||||
"github.com/schollz/croc/src/zipper"
|
||||
"github.com/schollz/pake"
|
||||
progressbar "github.com/schollz/progressbar/v2"
|
||||
"github.com/schollz/spinner"
|
||||
)
|
||||
|
||||
// Send is the async call to send data
|
||||
func (cr *Croc) startSender(forceSend int, serverAddress string, tcpPorts []string, isLocal bool, done chan error, c *websocket.Conn, fname string, codephrase string, useCompression bool, useEncryption bool) {
|
||||
logger.SetLogLevel(DebugLevel)
|
||||
log.Debugf("sending %s", fname)
|
||||
err := cr.send(forceSend, serverAddress, tcpPorts, isLocal, c, fname, codephrase, useCompression, useEncryption)
|
||||
if err != nil && strings.HasPrefix(err.Error(), "websocket: close 100") {
|
||||
err = nil
|
||||
}
|
||||
done <- err
|
||||
}
|
||||
|
||||
func (cr *Croc) send(forceSend int, serverAddress string, tcpPorts []string, isLocal bool, c *websocket.Conn, fname string, codephrase string, useCompression bool, useEncryption bool) (err error) {
|
||||
var f *os.File
|
||||
defer f.Close() // ignore the error if it wasn't opened :(
|
||||
var fileHash []byte
|
||||
var startTransfer time.Time
|
||||
var tcpConnections []comm.Comm
|
||||
blocksToSkip := make(map[int64]struct{})
|
||||
isConnectedIfUsingTCP := make(chan bool)
|
||||
|
||||
type DataChan struct {
|
||||
b []byte
|
||||
currentPostition int64
|
||||
bytesRead int
|
||||
err error
|
||||
}
|
||||
dataChan := make(chan DataChan, 1024*1024)
|
||||
defer close(dataChan)
|
||||
|
||||
useWebsockets := true
|
||||
switch forceSend {
|
||||
case 0:
|
||||
if !isLocal {
|
||||
useWebsockets = false
|
||||
}
|
||||
case 1:
|
||||
useWebsockets = true
|
||||
case 2:
|
||||
useWebsockets = false
|
||||
}
|
||||
|
||||
fileReady := make(chan error)
|
||||
|
||||
// normalize the file name
|
||||
fname, err = filepath.Abs(fname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, filename := filepath.Split(fname)
|
||||
|
||||
// get ready to generate session key
|
||||
var sessionKey []byte
|
||||
|
||||
// start a spinner
|
||||
spin := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
|
||||
spin.Writer = os.Stderr
|
||||
defer spin.Stop()
|
||||
|
||||
// both parties should have a weak key
|
||||
pw := []byte(codephrase)
|
||||
// initialize sender P ("0" indicates sender)
|
||||
P, err := pake.InitCurve(pw, 0, cr.CurveType, 1*time.Millisecond)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// start the reader
|
||||
websocketMessages := make(chan WebSocketMessage, 1024)
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Debugf("recovered from %s", r)
|
||||
}
|
||||
}()
|
||||
for {
|
||||
messageType, message, err := c.ReadMessage()
|
||||
websocketMessages <- WebSocketMessage{messageType, message, err}
|
||||
}
|
||||
}()
|
||||
|
||||
step := 0
|
||||
for {
|
||||
websocketMessage := <-websocketMessages
|
||||
messageType := websocketMessage.messageType
|
||||
message := websocketMessage.message
|
||||
errRead := websocketMessage.err
|
||||
if errRead != nil {
|
||||
return errRead
|
||||
}
|
||||
if messageType == websocket.PongMessage || messageType == websocket.PingMessage {
|
||||
continue
|
||||
}
|
||||
if messageType == websocket.TextMessage && bytes.HasPrefix(message, []byte("interrupt")) {
|
||||
return errors.New("\rinterrupted by other party")
|
||||
}
|
||||
if messageType == websocket.TextMessage && bytes.HasPrefix(message, []byte("err")) {
|
||||
return errors.New("\r" + string(message))
|
||||
}
|
||||
log.Debugf("got %d: %s", messageType, message)
|
||||
switch step {
|
||||
case 0:
|
||||
// sender initiates communication
|
||||
ip := ""
|
||||
if isLocal {
|
||||
ip = utils.LocalIP()
|
||||
} else {
|
||||
ip, _ = utils.PublicIP()
|
||||
}
|
||||
|
||||
initialData := models.Initial{
|
||||
CurveType: cr.CurveType,
|
||||
IPAddress: ip,
|
||||
VersionString: cr.Version, // version should match
|
||||
}
|
||||
bInitialData, _ := json.Marshal(initialData)
|
||||
// send the initial data
|
||||
c.WriteMessage(websocket.BinaryMessage, bInitialData)
|
||||
case 1:
|
||||
// first receive the initial data from the recipient
|
||||
var initialData models.Initial
|
||||
err = json.Unmarshal(message, &initialData)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "incompatible versions of croc")
|
||||
return
|
||||
}
|
||||
cr.OtherIP = initialData.IPAddress
|
||||
log.Debugf("recipient IP: %s", cr.OtherIP)
|
||||
|
||||
go func() {
|
||||
// recipient might want file! start gathering information about file
|
||||
fstat, err := os.Stat(fname)
|
||||
if err != nil {
|
||||
fileReady <- err
|
||||
return
|
||||
}
|
||||
cr.FileInfo = models.FileStats{
|
||||
Name: filename,
|
||||
Size: fstat.Size(),
|
||||
ModTime: fstat.ModTime(),
|
||||
IsDir: fstat.IsDir(),
|
||||
SentName: fstat.Name(),
|
||||
IsCompressed: useCompression,
|
||||
IsEncrypted: useEncryption,
|
||||
}
|
||||
if cr.FileInfo.IsDir {
|
||||
// zip the directory
|
||||
cr.FileInfo.SentName, err = zipper.ZipFile(fname, true)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
fileReady <- err
|
||||
return
|
||||
}
|
||||
fname = cr.FileInfo.SentName
|
||||
|
||||
fstat, err := os.Stat(fname)
|
||||
if err != nil {
|
||||
fileReady <- err
|
||||
return
|
||||
}
|
||||
// get new size
|
||||
cr.FileInfo.Size = fstat.Size()
|
||||
}
|
||||
|
||||
// open the file
|
||||
f, err = os.Open(fname)
|
||||
if err != nil {
|
||||
fileReady <- err
|
||||
return
|
||||
}
|
||||
fileReady <- nil
|
||||
|
||||
}()
|
||||
|
||||
// send pake data
|
||||
log.Debugf("[%d] first, P sends u to Q", step)
|
||||
c.WriteMessage(websocket.BinaryMessage, P.Bytes())
|
||||
// start PAKE spinnner
|
||||
spin.Suffix = " performing PAKE..."
|
||||
cr.StateString = "Performing PAKE..."
|
||||
spin.Start()
|
||||
case 2:
|
||||
// P recieves H(k),v from Q
|
||||
log.Debugf("[%d] P computes k, H(k), sends H(k) to Q", step)
|
||||
err := P.Update(message)
|
||||
c.WriteMessage(websocket.BinaryMessage, P.Bytes())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Recipient is using wrong code phrase.")
|
||||
}
|
||||
|
||||
sessionKey, _ = P.SessionKey()
|
||||
// check(err)
|
||||
log.Debugf("%x\n", sessionKey)
|
||||
|
||||
// wait for readiness
|
||||
spin.Stop()
|
||||
spin.Suffix = " waiting for recipient ok..."
|
||||
cr.StateString = "Waiting for recipient ok...."
|
||||
spin.Start()
|
||||
case 3:
|
||||
log.Debugf("[%d] recipient declares readiness for file info", step)
|
||||
if !bytes.HasPrefix(message, []byte("ready")) {
|
||||
return errors.New("Recipient refused file")
|
||||
}
|
||||
|
||||
err = <-fileReady // block until file is ready
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fstatsBytes, err := json.Marshal(cr.FileInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// encrypt the file meta data
|
||||
enc := crypt.Encrypt(fstatsBytes, sessionKey)
|
||||
// send the file meta data
|
||||
c.WriteMessage(websocket.BinaryMessage, enc.Bytes())
|
||||
case 4:
|
||||
log.Debugf("[%d] recipient gives blocks", step)
|
||||
// recipient sends blocks, and sender does not send anything back
|
||||
// determine if any blocks were sent to skip
|
||||
enc, err := crypt.FromBytes(message)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
decrypted, err := enc.Decrypt(sessionKey)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "could not decrypt blocks with session key")
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
var blocks []string
|
||||
errBlocks := json.Unmarshal(decrypted, &blocks)
|
||||
if errBlocks == nil {
|
||||
for _, block := range blocks {
|
||||
blockInt64, errBlock := strconv.Atoi(block)
|
||||
if errBlock == nil {
|
||||
blocksToSkip[int64(blockInt64)] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Debugf("found blocks: %+v", blocksToSkip)
|
||||
|
||||
// connect to TCP in background
|
||||
tcpConnections = make([]comm.Comm, len(tcpPorts))
|
||||
go func() {
|
||||
if !useWebsockets {
|
||||
log.Debugf("connecting to server")
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(tcpPorts))
|
||||
for i, tcpPort := range tcpPorts {
|
||||
go func(i int, tcpPort string) {
|
||||
defer wg.Done()
|
||||
log.Debugf("connecting to %s on connection %d", tcpPort, i)
|
||||
var message string
|
||||
tcpConnections[i], message, err = connectToTCPServer(utils.SHA256(fmt.Sprintf("%d%x", i, sessionKey)), serverAddress+":"+tcpPort)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
if message != "sender" {
|
||||
log.Errorf("got wrong message: %s", message)
|
||||
}
|
||||
}(i, tcpPort)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
isConnectedIfUsingTCP <- true
|
||||
}()
|
||||
|
||||
// start loading the file into memory
|
||||
// start streaming encryption/compression
|
||||
if cr.FileInfo.IsDir {
|
||||
// remove file if zipped
|
||||
defer os.Remove(cr.FileInfo.SentName)
|
||||
}
|
||||
go func(dataChan chan DataChan) {
|
||||
var buffer []byte
|
||||
if useWebsockets {
|
||||
buffer = make([]byte, models.WEBSOCKET_BUFFER_SIZE/8)
|
||||
} else {
|
||||
buffer = make([]byte, models.TCP_BUFFER_SIZE/2)
|
||||
}
|
||||
|
||||
currentPostition := int64(0)
|
||||
for {
|
||||
bytesread, err := f.Read(buffer)
|
||||
if bytesread > 0 {
|
||||
if _, ok := blocksToSkip[currentPostition]; ok {
|
||||
log.Debugf("skipping the sending of block %d", currentPostition)
|
||||
currentPostition += int64(bytesread)
|
||||
continue
|
||||
}
|
||||
|
||||
// do compression
|
||||
var compressedBytes []byte
|
||||
if useCompression && !cr.FileInfo.IsDir {
|
||||
compressedBytes = compress.Compress(buffer[:bytesread])
|
||||
} else {
|
||||
compressedBytes = buffer[:bytesread]
|
||||
}
|
||||
|
||||
// if using TCP, prepend the location to write the data to in the resulting file
|
||||
if !useWebsockets {
|
||||
compressedBytes = append([]byte(fmt.Sprintf("%d-", currentPostition)), compressedBytes...)
|
||||
}
|
||||
|
||||
// do encryption
|
||||
enc := crypt.Encrypt(compressedBytes, sessionKey, !useEncryption)
|
||||
encBytes, err := json.Marshal(enc)
|
||||
if err != nil {
|
||||
dataChan <- DataChan{
|
||||
b: nil,
|
||||
bytesRead: 0,
|
||||
err: err,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
dataChan <- DataChan{
|
||||
b: encBytes,
|
||||
bytesRead: bytesread,
|
||||
err: nil,
|
||||
}
|
||||
currentPostition += int64(bytesread)
|
||||
}
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
log.Error(err)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
// finish
|
||||
log.Debug("sending magic")
|
||||
dataChan <- DataChan{
|
||||
b: []byte("magic"),
|
||||
bytesRead: 0,
|
||||
err: nil,
|
||||
}
|
||||
if !useWebsockets {
|
||||
log.Debug("sending extra magic to %d others", len(tcpPorts)-1)
|
||||
for i := 0; i < len(tcpPorts)-1; i++ {
|
||||
log.Debug("sending magic")
|
||||
dataChan <- DataChan{
|
||||
b: []byte("magic"),
|
||||
bytesRead: 0,
|
||||
err: nil,
|
||||
}
|
||||
}
|
||||
}
|
||||
}(dataChan)
|
||||
|
||||
case 5:
|
||||
spin.Stop()
|
||||
|
||||
log.Debugf("[%d] recipient declares readiness for file data", step)
|
||||
if !bytes.HasPrefix(message, []byte("ready")) {
|
||||
return errors.New("Recipient refused file")
|
||||
}
|
||||
cr.StateString = "Transfer in progress..."
|
||||
fmt.Fprintf(os.Stderr, "\rSending (->%s)...\n", cr.OtherIP)
|
||||
// send file, compure hash simultaneously
|
||||
startTransfer = time.Now()
|
||||
|
||||
blockSize := 0
|
||||
if useWebsockets {
|
||||
blockSize = models.WEBSOCKET_BUFFER_SIZE / 8
|
||||
} else {
|
||||
blockSize = models.TCP_BUFFER_SIZE / 2
|
||||
}
|
||||
cr.Bar = progressbar.NewOptions(
|
||||
int(cr.FileInfo.Size),
|
||||
progressbar.OptionSetRenderBlankState(true),
|
||||
progressbar.OptionSetBytes(int(cr.FileInfo.Size)),
|
||||
progressbar.OptionSetWriter(os.Stderr),
|
||||
progressbar.OptionThrottle(1/60*time.Second),
|
||||
)
|
||||
cr.Bar.Add(blockSize * len(blocksToSkip))
|
||||
|
||||
if useWebsockets {
|
||||
for {
|
||||
data := <-dataChan
|
||||
if data.err != nil {
|
||||
return data.err
|
||||
}
|
||||
cr.Bar.Add(data.bytesRead)
|
||||
|
||||
// write data to websockets
|
||||
err = c.WriteMessage(websocket.BinaryMessage, data.b)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "problem writing message")
|
||||
return err
|
||||
}
|
||||
if bytes.Equal(data.b, []byte("magic")) {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_ = <-isConnectedIfUsingTCP
|
||||
log.Debug("connected and ready to send on tcp")
|
||||
|
||||
// check to see if any messages are sent
|
||||
stopMessageSignal := make(chan bool, 1)
|
||||
errorsDuringTransfer := make(chan error, 24)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case sig := <-stopMessageSignal:
|
||||
errorsDuringTransfer <- nil
|
||||
log.Debugf("got message signal: %+v", sig)
|
||||
return
|
||||
case wsMessage := <-websocketMessages:
|
||||
log.Debugf("got message: %s", wsMessage.message)
|
||||
if bytes.HasPrefix(wsMessage.message, []byte("error")) {
|
||||
log.Debug("stopping transfer")
|
||||
for i := 0; i < len(tcpConnections)+1; i++ {
|
||||
errorsDuringTransfer <- fmt.Errorf("%s", wsMessage.message)
|
||||
}
|
||||
return
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(tcpConnections))
|
||||
for i := range tcpConnections {
|
||||
defer func(i int) {
|
||||
log.Debugf("closing connection %d", i)
|
||||
tcpConnections[i].Close()
|
||||
}(i)
|
||||
go func(i int, wg *sync.WaitGroup, dataChan <-chan DataChan) {
|
||||
defer wg.Done()
|
||||
for data := range dataChan {
|
||||
select {
|
||||
case _ = <-errorsDuringTransfer:
|
||||
log.Debugf("%d got stop", i)
|
||||
return
|
||||
default:
|
||||
}
|
||||
if data.err != nil {
|
||||
log.Error(data.err)
|
||||
return
|
||||
}
|
||||
cr.Bar.Add(data.bytesRead)
|
||||
// write data to tcp connection
|
||||
_, errTcp := tcpConnections[i].Write(data.b)
|
||||
if errTcp != nil {
|
||||
errTcp = errors.Wrap(errTcp, "problem writing message")
|
||||
log.Debug(errTcp)
|
||||
errorsDuringTransfer <- errTcp
|
||||
return
|
||||
}
|
||||
if bytes.Equal(data.b, []byte("magic")) {
|
||||
log.Debugf("%d got magic", i)
|
||||
return
|
||||
}
|
||||
}
|
||||
}(i, &wg, dataChan)
|
||||
}
|
||||
|
||||
// block until this is done
|
||||
log.Debug("waiting for tcp goroutines")
|
||||
wg.Wait()
|
||||
log.Debug("sending stop message signal")
|
||||
stopMessageSignal <- true
|
||||
log.Debug("waiting for error")
|
||||
errorDuringTransfer := <-errorsDuringTransfer
|
||||
if errorDuringTransfer != nil {
|
||||
log.Debugf("got error during transfer: %s", errorDuringTransfer.Error())
|
||||
return errorDuringTransfer
|
||||
}
|
||||
}
|
||||
|
||||
cr.Bar.Finish()
|
||||
log.Debug("send hash to finish file")
|
||||
fileHash, err = utils.HashFile(fname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case 6:
|
||||
// recevied something, maybe the file hash
|
||||
transferTime := time.Since(startTransfer)
|
||||
if !bytes.HasPrefix(message, []byte("hash:")) {
|
||||
log.Debugf("%s", message)
|
||||
continue
|
||||
}
|
||||
c.WriteMessage(websocket.BinaryMessage, fileHash)
|
||||
message = bytes.TrimPrefix(message, []byte("hash:"))
|
||||
log.Debugf("[%d] determing whether it went ok", step)
|
||||
if bytes.Equal(message, fileHash) {
|
||||
log.Debug("file transfered successfully")
|
||||
transferRate := float64(cr.FileInfo.Size) / 1000000.0 / transferTime.Seconds()
|
||||
transferType := "MB/s"
|
||||
if transferRate < 1 {
|
||||
transferRate = float64(cr.FileInfo.Size) / 1000.0 / transferTime.Seconds()
|
||||
transferType = "kB/s"
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\nTransfer complete (%2.1f %s)", transferRate, transferType)
|
||||
cr.StateString = fmt.Sprintf("Transfer complete (%2.1f %s)", transferRate, transferType)
|
||||
return nil
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "\nTransfer corrupted")
|
||||
return errors.New("file not transfered succesfully")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown step")
|
||||
}
|
||||
step++
|
||||
}
|
||||
}
|
||||
|
||||
func connectToTCPServer(room string, address string) (com comm.Comm, message string, err error) {
|
||||
connection, err := net.DialTimeout("tcp", address, 3*time.Hour)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
connection.SetReadDeadline(time.Now().Add(3 * time.Hour))
|
||||
connection.SetDeadline(time.Now().Add(3 * time.Hour))
|
||||
connection.SetWriteDeadline(time.Now().Add(3 * time.Hour))
|
||||
|
||||
com = comm.New(connection)
|
||||
ok, err := com.Receive()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.Debugf("server says: %s", ok)
|
||||
|
||||
err = com.Send(room)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
message, err = com.Receive()
|
||||
log.Debugf("server says: %s", message)
|
||||
return
|
||||
}
|
|
@ -1,217 +0,0 @@
|
|||
package croc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/cihub/seelog"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/schollz/croc/src/relay"
|
||||
"github.com/schollz/croc/src/utils"
|
||||
"github.com/schollz/peerdiscovery"
|
||||
)
|
||||
|
||||
// Send the file
|
||||
func (c *Croc) Send(fname, codephrase string) (err error) {
|
||||
defer log.Flush()
|
||||
log.Debugf("sending %s", fname)
|
||||
errChan := make(chan error)
|
||||
|
||||
// normally attempt two connections
|
||||
waitingFor := 2
|
||||
|
||||
// use public relay
|
||||
if !c.LocalOnly {
|
||||
go func() {
|
||||
// atttempt to connect to public relay
|
||||
errChan <- c.sendReceive(c.Address, c.AddressWebsocketPort, c.AddressTCPPorts, fname, codephrase, true, false)
|
||||
}()
|
||||
} else {
|
||||
waitingFor = 1
|
||||
}
|
||||
|
||||
// use local relay
|
||||
if !c.NoLocal {
|
||||
defer func() {
|
||||
log.Debug("sending relay stop signal")
|
||||
relay.Stop()
|
||||
}()
|
||||
go func() {
|
||||
// start own relay and connect to it
|
||||
go relay.Run(c.RelayWebsocketPort, c.RelayTCPPorts)
|
||||
time.Sleep(250 * time.Millisecond) // race condition here, but this should work most of the time :(
|
||||
|
||||
// broadcast for peer discovery
|
||||
go func() {
|
||||
log.Debug("starting local discovery...")
|
||||
discovered, err := peerdiscovery.Discover(peerdiscovery.Settings{
|
||||
Limit: 1,
|
||||
TimeLimit: 600 * time.Second,
|
||||
Delay: 50 * time.Millisecond,
|
||||
Payload: []byte(c.RelayWebsocketPort + "- " + strings.Join(c.RelayTCPPorts, ",")),
|
||||
MulticastAddress: fmt.Sprintf("239.255.255.%d", 230+int64(time.Now().Minute()/5)),
|
||||
})
|
||||
log.Debug(discovered, err)
|
||||
}()
|
||||
|
||||
// connect to own relay
|
||||
errChan <- c.sendReceive("localhost", c.RelayWebsocketPort, c.RelayTCPPorts, fname, codephrase, true, true)
|
||||
}()
|
||||
} else {
|
||||
waitingFor = 1
|
||||
}
|
||||
|
||||
err = <-errChan
|
||||
if err == nil || waitingFor == 1 {
|
||||
log.Debug("returning")
|
||||
return
|
||||
}
|
||||
log.Debug(err)
|
||||
return <-errChan
|
||||
}
|
||||
|
||||
// Receive the file
|
||||
func (c *Croc) Receive(codephrase string) (err error) {
|
||||
defer log.Flush()
|
||||
log.Debug("receiving")
|
||||
|
||||
// use local relay first
|
||||
if !c.NoLocal {
|
||||
log.Debug("trying to discover")
|
||||
// try to discovery codephrase and server through peer network
|
||||
discovered, errDiscover := peerdiscovery.Discover(peerdiscovery.Settings{
|
||||
Limit: 1,
|
||||
TimeLimit: 300 * time.Millisecond,
|
||||
Delay: 50 * time.Millisecond,
|
||||
Payload: []byte("checking"),
|
||||
AllowSelf: true,
|
||||
DisableBroadcast: true,
|
||||
MulticastAddress: fmt.Sprintf("239.255.255.%d", 230+int64(time.Now().Minute()/5)),
|
||||
})
|
||||
log.Debug("finished")
|
||||
log.Debug(discovered)
|
||||
if errDiscover != nil {
|
||||
log.Debug(errDiscover)
|
||||
}
|
||||
if len(discovered) > 0 {
|
||||
if discovered[0].Address == utils.LocalIP() {
|
||||
discovered[0].Address = "localhost"
|
||||
}
|
||||
log.Debugf("discovered %s:%s", discovered[0].Address, discovered[0].Payload)
|
||||
// see if we can actually connect to it
|
||||
timeout := time.Duration(200 * time.Millisecond)
|
||||
client := http.Client{
|
||||
Timeout: timeout,
|
||||
}
|
||||
ports := strings.Split(string(discovered[0].Payload), "-")
|
||||
if len(ports) != 2 {
|
||||
return errors.New("bad payload")
|
||||
}
|
||||
resp, err := client.Get(fmt.Sprintf("http://%s:%s/", discovered[0].Address, ports[0]))
|
||||
if err == nil {
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
// we connected, so use this
|
||||
return c.sendReceive(discovered[0].Address, strings.TrimSpace(ports[0]), strings.Split(strings.TrimSpace(ports[1]), ","), "", codephrase, false, true)
|
||||
}
|
||||
} else {
|
||||
log.Debugf("could not connect: %s", err.Error())
|
||||
}
|
||||
} else {
|
||||
log.Debug("discovered no peers")
|
||||
}
|
||||
}
|
||||
|
||||
// use public relay
|
||||
if !c.LocalOnly {
|
||||
log.Debug("using public relay")
|
||||
return c.sendReceive(c.Address, c.AddressWebsocketPort, c.AddressTCPPorts, "", codephrase, false, false)
|
||||
}
|
||||
|
||||
return errors.New("must use local or public relay")
|
||||
}
|
||||
|
||||
func (c *Croc) sendReceive(address, websocketPort string, tcpPorts []string, fname string, codephrase string, isSender bool, isLocal bool) (err error) {
|
||||
defer log.Flush()
|
||||
if len(codephrase) < 4 {
|
||||
return fmt.Errorf("codephrase is too short")
|
||||
}
|
||||
|
||||
// allow interrupts from Ctl+C
|
||||
interrupt := make(chan os.Signal, 1)
|
||||
signal.Notify(interrupt, os.Interrupt)
|
||||
|
||||
done := make(chan error)
|
||||
// connect to server
|
||||
websocketAddress := ""
|
||||
if len(websocketPort) > 0 {
|
||||
websocketAddress = fmt.Sprintf("ws://%s:%s/ws?room=%s", address, websocketPort, codephrase[:3])
|
||||
} else {
|
||||
websocketAddress = fmt.Sprintf("ws://%s/ws?room=%s", address, codephrase[:3])
|
||||
}
|
||||
log.Debugf("connecting to %s", websocketAddress)
|
||||
sock, _, err := websocket.DefaultDialer.Dial(websocketAddress, nil)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
defer sock.Close()
|
||||
|
||||
// tell the websockets we are connected
|
||||
err = sock.WriteMessage(websocket.BinaryMessage, []byte("connected"))
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if isSender {
|
||||
go c.startSender(c.ForceSend, address, tcpPorts, isLocal, done, sock, fname, codephrase, c.UseCompression, c.UseEncryption)
|
||||
} else {
|
||||
go c.startRecipient(c.ForceSend, address, tcpPorts, isLocal, done, sock, codephrase, c.NoRecipientPrompt, c.Stdout)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case doneError := <-done:
|
||||
log.Debug("received done signal")
|
||||
if doneError != nil {
|
||||
c.StateString = doneError.Error()
|
||||
sock.WriteMessage(websocket.TextMessage, []byte("error: "+doneError.Error()))
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
return doneError
|
||||
case <-interrupt:
|
||||
if !c.Debug {
|
||||
SetDebugLevel("critical")
|
||||
}
|
||||
log.Debug("interrupt")
|
||||
err = sock.WriteMessage(websocket.TextMessage, []byte("error: interrupted by other party"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
// Cleanly close the connection by sending a close message and then
|
||||
// waiting (with timeout) for the server to close the connection.
|
||||
err := sock.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
|
||||
if err != nil {
|
||||
log.Debug("write close:", err)
|
||||
return nil
|
||||
}
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Relay will start a relay on the specified port
|
||||
func (c *Croc) Relay() (err error) {
|
||||
return relay.Run(c.RelayWebsocketPort, c.RelayTCPPorts)
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
log "github.com/cihub/seelog"
|
||||
)
|
||||
|
||||
// SetLogLevel determines the log level
|
||||
func SetLogLevel(level string) (err error) {
|
||||
|
||||
// https://en.wikipedia.org/wiki/ANSI_escape_code#3/4_bit
|
||||
// https://github.com/cihub/seelog/wiki/Log-levels
|
||||
appConfig := `
|
||||
<seelog minlevel="` + level + `">
|
||||
<outputs formatid="stdout">
|
||||
<filter levels="debug,trace">
|
||||
<console formatid="debug"/>
|
||||
</filter>
|
||||
<filter levels="info">
|
||||
<console formatid="info"/>
|
||||
</filter>
|
||||
<filter levels="critical,error">
|
||||
<console formatid="error"/>
|
||||
</filter>
|
||||
<filter levels="warn">
|
||||
<console formatid="warn"/>
|
||||
</filter>
|
||||
</outputs>
|
||||
<formats>
|
||||
<format id="stdout" format="%Date %Time [%LEVEL] %File %FuncShort:%Line %Msg %n" />
|
||||
<format id="debug" format="%Date %Time %EscM(37)[%LEVEL]%EscM(0) %File %FuncShort:%Line %Msg %n" />
|
||||
<format id="info" format="%Date %Time %EscM(36)[%LEVEL]%EscM(0) %File %FuncShort:%Line %Msg %n" />
|
||||
<format id="warn" format="%Date %Time %EscM(33)[%LEVEL]%EscM(0) %File %FuncShort:%Line %Msg %n" />
|
||||
<format id="error" format="%Date %Time %EscM(31)[%LEVEL]%EscM(0) %File %FuncShort:%Line %Msg %n" />
|
||||
</formats>
|
||||
</seelog>
|
||||
`
|
||||
logger, err := log.LoggerFromConfigAsBytes([]byte(appConfig))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.ReplaceLogger(logger)
|
||||
return
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
package models
|
||||
|
||||
const WEBSOCKET_BUFFER_SIZE = 1024 * 1024 * 32
|
||||
const TCP_BUFFER_SIZE = 1024 * 64
|
|
@ -1,14 +0,0 @@
|
|||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// FileStats are the file stats transfered to the other
|
||||
type FileStats struct {
|
||||
Name string
|
||||
Size int64
|
||||
ModTime time.Time
|
||||
IsDir bool
|
||||
SentName string
|
||||
IsCompressed bool
|
||||
IsEncrypted bool
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package models
|
||||
|
||||
type Initial struct {
|
||||
CurveType string
|
||||
IPAddress string
|
||||
VersionString string
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
package relay
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
log "github.com/cihub/seelog"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/schollz/croc/src/models"
|
||||
)
|
||||
|
||||
const (
|
||||
// Time allowed to write a message to the peer.
|
||||
writeWait = 6000 * time.Second
|
||||
|
||||
// Time allowed to read the next pong message from the peer.
|
||||
pongWait = 6000 * time.Second
|
||||
|
||||
// Send pings to peer with this period. Must be less than pongWait.
|
||||
pingPeriod = (pongWait * 9) / 10
|
||||
|
||||
// Maximum message size allowed from peer.
|
||||
maxMessageSize = models.WEBSOCKET_BUFFER_SIZE / 2
|
||||
)
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: models.WEBSOCKET_BUFFER_SIZE,
|
||||
WriteBufferSize: models.WEBSOCKET_BUFFER_SIZE,
|
||||
}
|
||||
|
||||
// connection is an middleman between the websocket connection and the hub.
|
||||
type connection struct {
|
||||
// The websocket connection.
|
||||
ws *websocket.Conn
|
||||
|
||||
// Buffered channel of outbound messages.
|
||||
send chan messageChannel
|
||||
}
|
||||
|
||||
type messageChannel struct {
|
||||
data []byte
|
||||
messageType int
|
||||
}
|
||||
|
||||
// readPump pumps messages from the websocket connection to the hub.
|
||||
func (s subscription) readPump() {
|
||||
c := s.conn
|
||||
defer func() {
|
||||
h.unregister <- s
|
||||
c.ws.Close()
|
||||
}()
|
||||
c.ws.SetReadLimit(maxMessageSize)
|
||||
c.ws.SetReadDeadline(time.Now().Add(pongWait))
|
||||
c.ws.SetPongHandler(func(string) error { c.ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
|
||||
for {
|
||||
messageType, msg, err := c.ws.ReadMessage()
|
||||
if err != nil {
|
||||
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
|
||||
log.Debugf("unexpected close: %v", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
h.broadcast <- message{messageChannel{msg, messageType}, s.room, c.ws.RemoteAddr().String()}
|
||||
}
|
||||
}
|
||||
|
||||
// write writes a message with the given message type and payload.
|
||||
func (c *connection) write(mt int, payload []byte) error {
|
||||
c.ws.SetWriteDeadline(time.Now().Add(writeWait))
|
||||
return c.ws.WriteMessage(mt, payload)
|
||||
}
|
||||
|
||||
// writePump pumps messages from the hub to the websocket connection.
|
||||
func (s *subscription) writePump() {
|
||||
c := s.conn
|
||||
ticker := time.NewTicker(pingPeriod)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
c.ws.Close()
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case message, ok := <-c.send:
|
||||
if !ok {
|
||||
err := c.write(websocket.CloseMessage, []byte{})
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err := c.write(message.messageType, message.data); err != nil {
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
case <-ticker.C:
|
||||
if err := c.write(websocket.PingMessage, []byte{}); err != nil {
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// serveWs handles websocket requests from the peer.
|
||||
func serveWs(w http.ResponseWriter, r *http.Request) {
|
||||
ws, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
vals := r.URL.Query()
|
||||
room := "default"
|
||||
rooms, ok := vals["room"]
|
||||
if ok {
|
||||
room = rooms[0]
|
||||
}
|
||||
|
||||
c := &connection{send: make(chan messageChannel, 256), ws: ws}
|
||||
s := subscription{c, room}
|
||||
h.register <- s
|
||||
go s.writePump()
|
||||
s.readPump()
|
||||
}
|
105
src/relay/hub.go
105
src/relay/hub.go
|
@ -1,105 +0,0 @@
|
|||
package relay
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
log "github.com/cihub/seelog"
|
||||
)
|
||||
|
||||
type message struct {
|
||||
msg messageChannel
|
||||
room string
|
||||
remoteOrigin string
|
||||
}
|
||||
|
||||
type subscription struct {
|
||||
conn *connection
|
||||
room string
|
||||
}
|
||||
|
||||
// hub maintains the set of active connections and broadcasts messages to the
|
||||
// connections.
|
||||
type hub struct {
|
||||
// Registered connections.
|
||||
rooms roomMap
|
||||
|
||||
// Inbound messages from the connections.
|
||||
broadcast chan message
|
||||
|
||||
// Register requests from the connections.
|
||||
register chan subscription
|
||||
|
||||
// Unregister requests from connections.
|
||||
unregister chan subscription
|
||||
}
|
||||
|
||||
type roomMap struct {
|
||||
rooms map[string]map[*connection]bool
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
var h = hub{
|
||||
broadcast: make(chan message),
|
||||
register: make(chan subscription),
|
||||
unregister: make(chan subscription),
|
||||
rooms: roomMap{rooms: make(map[string]map[*connection]bool)},
|
||||
}
|
||||
|
||||
func (h *hub) run() {
|
||||
for {
|
||||
if stop {
|
||||
log.Debug("stopping hub")
|
||||
return
|
||||
}
|
||||
select {
|
||||
case s := <-h.register:
|
||||
log.Debugf("adding connection to %s", s.room)
|
||||
h.rooms.Lock()
|
||||
connections := h.rooms.rooms[s.room]
|
||||
if connections == nil {
|
||||
connections = make(map[*connection]bool)
|
||||
h.rooms.rooms[s.room] = connections
|
||||
}
|
||||
h.rooms.rooms[s.room][s.conn] = true
|
||||
if len(h.rooms.rooms) > 2 {
|
||||
// if more than three, close all of them
|
||||
for connection := range h.rooms.rooms[s.room] {
|
||||
close(connection.send)
|
||||
}
|
||||
log.Debugf("deleting room %s", s.room)
|
||||
delete(h.rooms.rooms, s.room)
|
||||
}
|
||||
h.rooms.Unlock()
|
||||
case s := <-h.unregister:
|
||||
// if one leaves, close all of them
|
||||
h.rooms.Lock()
|
||||
if _, ok := h.rooms.rooms[s.room]; ok {
|
||||
for connection := range h.rooms.rooms[s.room] {
|
||||
close(connection.send)
|
||||
}
|
||||
log.Debugf("deleting room %s", s.room)
|
||||
delete(h.rooms.rooms, s.room)
|
||||
}
|
||||
h.rooms.Unlock()
|
||||
case m := <-h.broadcast:
|
||||
h.rooms.Lock()
|
||||
connections := h.rooms.rooms[m.room]
|
||||
for c := range connections {
|
||||
if c.ws.RemoteAddr().String() == m.remoteOrigin {
|
||||
continue
|
||||
}
|
||||
select {
|
||||
case c.send <- m.msg:
|
||||
default:
|
||||
close(c.send)
|
||||
delete(connections, c)
|
||||
if len(connections) == 0 {
|
||||
log.Debugf("deleting room %s", m.room)
|
||||
delete(h.rooms.rooms, m.room)
|
||||
}
|
||||
}
|
||||
}
|
||||
h.rooms.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
package relay
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
log "github.com/cihub/seelog"
|
||||
"github.com/schollz/croc/src/logger"
|
||||
"github.com/schollz/croc/src/tcp"
|
||||
)
|
||||
|
||||
var DebugLevel string
|
||||
var stop bool
|
||||
|
||||
func Stop() {
|
||||
log.Debug("got stop signal")
|
||||
stop = true
|
||||
}
|
||||
|
||||
// Run is the async operation for running a server
|
||||
func Run(port string, tcpPorts []string) (err error) {
|
||||
logger.SetLogLevel(DebugLevel)
|
||||
|
||||
if len(tcpPorts) > 0 {
|
||||
for _, tcpPort := range tcpPorts {
|
||||
go tcp.Run(DebugLevel, tcpPort)
|
||||
}
|
||||
}
|
||||
|
||||
go h.run()
|
||||
log.Debug("running relay on " + port)
|
||||
m := http.NewServeMux()
|
||||
s := http.Server{Addr: ":" + port, Handler: m}
|
||||
m.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
|
||||
serveWs(w, r)
|
||||
})
|
||||
m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "ok")
|
||||
})
|
||||
go func() {
|
||||
for {
|
||||
if stop {
|
||||
s.Shutdown(context.Background())
|
||||
log.Debug("stopping http server")
|
||||
return
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
s.ListenAndServe()
|
||||
log.Debug("finished")
|
||||
return
|
||||
}
|
188
src/tcp/tcp.go
188
src/tcp/tcp.go
|
@ -1,188 +0,0 @@
|
|||
package tcp
|
||||
|
||||
import (
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/cihub/seelog"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/schollz/croc/src/comm"
|
||||
"github.com/schollz/croc/src/logger"
|
||||
"github.com/schollz/croc/src/models"
|
||||
)
|
||||
|
||||
type roomInfo struct {
|
||||
receiver comm.Comm
|
||||
opened time.Time
|
||||
}
|
||||
|
||||
type roomMap struct {
|
||||
rooms map[string]roomInfo
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
var rooms roomMap
|
||||
|
||||
// Run starts a tcp listener, run async
|
||||
func Run(debugLevel, port string) {
|
||||
logger.SetLogLevel(debugLevel)
|
||||
rooms.Lock()
|
||||
rooms.rooms = make(map[string]roomInfo)
|
||||
rooms.Unlock()
|
||||
|
||||
// delete old rooms
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(10 * time.Minute)
|
||||
rooms.Lock()
|
||||
for room := range rooms.rooms {
|
||||
if time.Since(rooms.rooms[room].opened) > 3*time.Hour {
|
||||
delete(rooms.rooms, room)
|
||||
}
|
||||
}
|
||||
rooms.Unlock()
|
||||
}
|
||||
}()
|
||||
|
||||
err := run(port)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func run(port string) (err error) {
|
||||
log.Debugf("starting TCP server on " + port)
|
||||
server, err := net.Listen("tcp", "0.0.0.0:"+port)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error listening on :"+port)
|
||||
}
|
||||
defer server.Close()
|
||||
// spawn a new goroutine whenever a client connects
|
||||
for {
|
||||
connection, err := server.Accept()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "problem accepting connection")
|
||||
}
|
||||
log.Debugf("client %s connected", connection.RemoteAddr().String())
|
||||
go func(port string, connection net.Conn) {
|
||||
errCommunication := clientCommuncation(port, comm.New(connection))
|
||||
if errCommunication != nil {
|
||||
log.Warnf("relay-%s: %s", connection.RemoteAddr().String(), errCommunication.Error())
|
||||
}
|
||||
}(port, connection)
|
||||
}
|
||||
}
|
||||
|
||||
func clientCommuncation(port string, c comm.Comm) (err error) {
|
||||
// send ok to tell client they are connected
|
||||
log.Debug("sending ok")
|
||||
err = c.Send("ok")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// wait for client to tell me which room they want
|
||||
log.Debug("waiting for answer")
|
||||
room, err := c.Receive()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rooms.Lock()
|
||||
// first connection is always the receiver
|
||||
if _, ok := rooms.rooms[room]; !ok {
|
||||
rooms.rooms[room] = roomInfo{
|
||||
receiver: c,
|
||||
opened: time.Now(),
|
||||
}
|
||||
rooms.Unlock()
|
||||
// tell the client that they got the room
|
||||
err = c.Send("recipient")
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
log.Debug("recipient connected")
|
||||
return nil
|
||||
}
|
||||
log.Debug("sender connected")
|
||||
receiver := rooms.rooms[room].receiver
|
||||
rooms.Unlock()
|
||||
|
||||
// second connection is the sender, time to staple connections
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
// start piping
|
||||
go func(com1, com2 comm.Comm, wg *sync.WaitGroup) {
|
||||
log.Debug("starting pipes")
|
||||
pipe(com1.Connection(), com2.Connection())
|
||||
wg.Done()
|
||||
log.Debug("done piping")
|
||||
}(c, receiver, &wg)
|
||||
|
||||
// tell the sender everything is ready
|
||||
err = c.Send("sender")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// delete room
|
||||
rooms.Lock()
|
||||
log.Debugf("deleting room: %s", room)
|
||||
delete(rooms.rooms, room)
|
||||
rooms.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// chanFromConn creates a channel from a Conn object, and sends everything it
|
||||
// Read()s from the socket to the channel.
|
||||
func chanFromConn(conn net.Conn) chan []byte {
|
||||
c := make(chan []byte)
|
||||
|
||||
go func() {
|
||||
b := make([]byte, models.TCP_BUFFER_SIZE)
|
||||
|
||||
for {
|
||||
n, err := conn.Read(b)
|
||||
if n > 0 {
|
||||
res := make([]byte, n)
|
||||
// Copy the buffer so it doesn't get changed while read by the recipient.
|
||||
copy(res, b[:n])
|
||||
c <- res
|
||||
}
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
c <- nil
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// pipe creates a full-duplex pipe between the two sockets and
|
||||
// transfers data from one to the other.
|
||||
func pipe(conn1 net.Conn, conn2 net.Conn) {
|
||||
chan1 := chanFromConn(conn1)
|
||||
chan2 := chanFromConn(conn2)
|
||||
|
||||
for {
|
||||
select {
|
||||
case b1 := <-chan1:
|
||||
if b1 == nil {
|
||||
return
|
||||
}
|
||||
conn2.Write(b1)
|
||||
|
||||
case b2 := <-chan2:
|
||||
if b2 == nil {
|
||||
return
|
||||
}
|
||||
conn1.Write(b2)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
VERSION=$(shell git describe --tags --abbrev=0)
|
||||
LDFLAGS=-ldflags "-X main.Version=${VERSION}"
|
||||
|
||||
.PHONY: linux
|
||||
linux:
|
||||
GO111MODULE=off qtdeploy ${LDFLAGS} --tags='wincroc' --debug build desktop
|
||||
|
||||
.PHONY: fast
|
||||
fast:
|
||||
GO111MODULE=off qtdeploy ${LDFLAGS} --fast --tags='wincroc' --debug build desktop
|
||||
|
||||
windows:
|
||||
GO111MODULE=off qtdeploy ${LDFLAGS} --tags='wincroc' --debug --docker build windows_64_static
|
||||
|
||||
release: linux windows
|
||||
mv deploy/linux/win croc
|
||||
tar -czvf croc_${VERSION}_Linux-64bit_GUI.tar.gz croc
|
||||
mv deploy/windows/win.exe croc.exe
|
||||
zip croc_${VERSION}_Windows-64bit_GUI.zip croc.exe
|
||||
rm -rf dist
|
||||
mkdir dist
|
||||
mv *zip dist/
|
||||
mv *tar.gz dist/
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
REM getting the output of an executed command in a batch file is not trivial, we use a temp file for it
|
||||
git describe --tags --abbrev=0 > temp.txt
|
||||
set /P VERSION=< temp.txt
|
||||
echo %VERSION%
|
||||
del temp.txt
|
||||
|
||||
REM build a 32 bit Windows application, this way it will run on both 32 but and 64 bit Windows machines
|
||||
set GOARCH=386
|
||||
|
||||
REM -s and -w strip the program of debugging information, making it smaller
|
||||
REM -H=windowsgui makes the program not have a console window on start-up
|
||||
go build -ldflags="-s -w -H=windowsgui -X main.Version=%VERSION%" -o croc.exe
|
||||
|
||||
if errorlevel 1 pause
|
3028
src/win/icon.go
3028
src/win/icon.go
File diff suppressed because it is too large
Load diff
BIN
src/win/icon.ico
BIN
src/win/icon.ico
Binary file not shown.
Before Width: | Height: | Size: 35 KiB |
|
@ -1 +0,0 @@
|
|||
IDI_ICON1 ICON DISCARDABLE "icon.ico"
|
Binary file not shown.
Binary file not shown.
230
src/win/main.go
230
src/win/main.go
|
@ -1,230 +0,0 @@
|
|||
// +build wincroc
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/schollz/croc/src/cli"
|
||||
"github.com/schollz/croc/src/croc"
|
||||
"github.com/schollz/croc/src/utils"
|
||||
"github.com/skratchdot/open-golang/open"
|
||||
"github.com/therecipe/qt/core"
|
||||
"github.com/therecipe/qt/widgets"
|
||||
)
|
||||
|
||||
type CustomLabel struct {
|
||||
widgets.QLabel
|
||||
|
||||
_ func(string) `signal:"updateTextFromGoroutine,auto(this.QLabel.setText)"` //TODO: support this.setText as well
|
||||
}
|
||||
|
||||
var Version string
|
||||
|
||||
func main() {
|
||||
if len(os.Args) > 1 {
|
||||
cli.Run()
|
||||
return
|
||||
}
|
||||
|
||||
var isWorking bool
|
||||
app := widgets.NewQApplication(len(os.Args), os.Args)
|
||||
|
||||
window := widgets.NewQMainWindow(nil, 0)
|
||||
if runtime.GOOS == "windows" {
|
||||
window.SetFixedSize2(300, 150)
|
||||
window.SetWindowTitle("croc " + Version)
|
||||
} else {
|
||||
window.SetFixedSize2(400, 150)
|
||||
window.SetWindowTitle("🐊📦 croc " + Version)
|
||||
}
|
||||
|
||||
widget := widgets.NewQWidget(nil, 0)
|
||||
widget.SetLayout(widgets.NewQVBoxLayout())
|
||||
window.SetCentralWidget(widget)
|
||||
|
||||
labels := make([]*CustomLabel, 3)
|
||||
for i := range labels {
|
||||
label := NewCustomLabel(nil, 0)
|
||||
label.SetAlignment(core.Qt__AlignCenter)
|
||||
widget.Layout().AddWidget(label)
|
||||
labels[i] = label
|
||||
}
|
||||
labels[0].SetText("secure data transfer")
|
||||
labels[1].SetText("Click 'Send' or 'Receive' to start")
|
||||
|
||||
button := widgets.NewQPushButton2("Send", nil)
|
||||
button.ConnectClicked(func(bool) {
|
||||
if isWorking {
|
||||
dialog("Can only do one send or receive at a time")
|
||||
return
|
||||
}
|
||||
isWorking = true
|
||||
|
||||
var fileDialog = widgets.NewQFileDialog2(window, "Open file to send...", "", "")
|
||||
fileDialog.SetAcceptMode(widgets.QFileDialog__AcceptOpen)
|
||||
fileDialog.SetFileMode(widgets.QFileDialog__AnyFile)
|
||||
if fileDialog.Exec() != int(widgets.QDialog__Accepted) {
|
||||
isWorking = false
|
||||
return
|
||||
}
|
||||
var fn = fileDialog.SelectedFiles()[0]
|
||||
if len(fn) == 0 {
|
||||
dialog(fmt.Sprintf("No file selected"))
|
||||
isWorking = false
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
cr := croc.Init(false)
|
||||
done := make(chan bool)
|
||||
codePhrase := utils.GetRandomName()
|
||||
_, fname := filepath.Split(fn)
|
||||
labels[0].UpdateTextFromGoroutine(fmt.Sprintf("Sending '%s'", fname))
|
||||
labels[1].UpdateTextFromGoroutine(fmt.Sprintf("Code phrase: %s", codePhrase))
|
||||
|
||||
go func(done chan bool) {
|
||||
for {
|
||||
if cr.OtherIP != "" && cr.FileInfo.SentName != "" {
|
||||
bytesString := humanize.Bytes(uint64(cr.FileInfo.Size))
|
||||
fileOrFolder := "file"
|
||||
if cr.FileInfo.IsDir {
|
||||
fileOrFolder = "folder"
|
||||
}
|
||||
labels[0].UpdateTextFromGoroutine(fmt.Sprintf("Sending %s %s '%s' to %s", bytesString, fileOrFolder, cr.FileInfo.SentName, cr.OtherIP))
|
||||
}
|
||||
if cr.Bar != nil {
|
||||
barState := cr.Bar.State()
|
||||
labels[1].UpdateTextFromGoroutine(fmt.Sprintf("%2.1f%% [%2.0fs:%2.0fs]", barState.CurrentPercent*100, barState.SecondsSince, barState.SecondsLeft))
|
||||
}
|
||||
labels[2].UpdateTextFromGoroutine(cr.StateString)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
select {
|
||||
case _ = <-done:
|
||||
labels[2].UpdateTextFromGoroutine(cr.StateString)
|
||||
return
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}(done)
|
||||
|
||||
cr.Send(fn, codePhrase)
|
||||
done <- true
|
||||
isWorking = false
|
||||
}()
|
||||
})
|
||||
widget.Layout().AddWidget(button)
|
||||
|
||||
receiveButton := widgets.NewQPushButton2("Receive", nil)
|
||||
receiveButton.ConnectClicked(func(bool) {
|
||||
if isWorking {
|
||||
dialog("Can only do one send or receive at a time")
|
||||
return
|
||||
}
|
||||
labels[1].SetText("")
|
||||
labels[2].SetText("please wait...")
|
||||
isWorking = true
|
||||
defer func() {
|
||||
isWorking = false
|
||||
}()
|
||||
|
||||
// determine the folder to save the file
|
||||
var folderDialog = widgets.NewQFileDialog2(window, "Open folder to receive file...", "", "")
|
||||
folderDialog.SetAcceptMode(widgets.QFileDialog__AcceptOpen)
|
||||
folderDialog.SetFileMode(widgets.QFileDialog__DirectoryOnly)
|
||||
if folderDialog.Exec() != int(widgets.QDialog__Accepted) {
|
||||
return
|
||||
}
|
||||
var fn = folderDialog.SelectedFiles()[0]
|
||||
if len(fn) == 0 {
|
||||
labels[2].SetText(fmt.Sprintf("No folder selected"))
|
||||
return
|
||||
}
|
||||
|
||||
var codePhrase = widgets.QInputDialog_GetText(window, "croc", "Enter code phrase:",
|
||||
widgets.QLineEdit__Normal, "", true, core.Qt__Dialog, core.Qt__ImhNone)
|
||||
if len(codePhrase) < 3 {
|
||||
labels[2].SetText(fmt.Sprintf("Invalid codephrase: '%s'", codePhrase))
|
||||
return
|
||||
}
|
||||
|
||||
cr := croc.Init(false)
|
||||
cr.WindowRecipientPrompt = true
|
||||
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
// change into the receiving directory
|
||||
cwd, _ := os.Getwd()
|
||||
defer os.Chdir(cwd)
|
||||
os.Chdir(fn)
|
||||
err := cr.Receive(codePhrase)
|
||||
if err == nil {
|
||||
open.Run(fn)
|
||||
}
|
||||
done <- true
|
||||
done <- true
|
||||
isWorking = false
|
||||
}()
|
||||
go func() {
|
||||
for {
|
||||
if cr.Bar != nil {
|
||||
barState := cr.Bar.State()
|
||||
labels[1].UpdateTextFromGoroutine(fmt.Sprintf("%2.1f%% [%2.0fs:%2.0fs]", barState.CurrentPercent*100, barState.SecondsSince, barState.SecondsLeft))
|
||||
}
|
||||
if cr.StateString != "" {
|
||||
labels[2].UpdateTextFromGoroutine(cr.StateString)
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
select {
|
||||
case _ = <-done:
|
||||
labels[2].UpdateTextFromGoroutine(cr.StateString)
|
||||
return
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
if cr.WindowReceivingString != "" {
|
||||
var question = widgets.QMessageBox_Question(window, "croc", fmt.Sprintf("%s?", cr.WindowReceivingString), widgets.QMessageBox__Yes|widgets.QMessageBox__No, 0)
|
||||
if question == widgets.QMessageBox__Yes {
|
||||
cr.WindowRecipientAccept = true
|
||||
labels[0].SetText(cr.WindowReceivingString)
|
||||
} else {
|
||||
cr.WindowRecipientAccept = false
|
||||
labels[2].SetText("canceled")
|
||||
}
|
||||
cr.WindowRecipientPrompt = false
|
||||
cr.WindowReceivingString = ""
|
||||
break
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
select {
|
||||
case _ = <-done:
|
||||
labels[2].SetText(cr.StateString)
|
||||
return
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
widget.Layout().AddWidget(receiveButton)
|
||||
|
||||
window.Show()
|
||||
app.Exec()
|
||||
}
|
||||
|
||||
func dialog(s string) {
|
||||
var info = widgets.NewQMessageBox(nil)
|
||||
info.SetWindowTitle("Info")
|
||||
info.SetText(s)
|
||||
info.Exec()
|
||||
}
|
|
@ -1,250 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/gonutz/w32"
|
||||
"github.com/gonutz/wui"
|
||||
"github.com/schollz/croc/src/cli"
|
||||
"github.com/schollz/croc/src/croc"
|
||||
"github.com/schollz/croc/src/utils"
|
||||
"github.com/skratchdot/open-golang/open"
|
||||
)
|
||||
|
||||
var Version string
|
||||
|
||||
func main() {
|
||||
if len(os.Args) > 1 {
|
||||
cli.Run()
|
||||
return
|
||||
}
|
||||
|
||||
var isWorking bool
|
||||
|
||||
font, _ := wui.NewFont(wui.FontDesc{
|
||||
Name: "Tahoma",
|
||||
Height: -11,
|
||||
})
|
||||
|
||||
window := wui.NewWindow()
|
||||
window.SetFont(font)
|
||||
window.SetIconFromMem(icon)
|
||||
window.SetStyle(w32.WS_OVERLAPPED | w32.WS_CAPTION | w32.WS_SYSMENU | w32.WS_MINIMIZEBOX)
|
||||
if runtime.GOOS == "windows" {
|
||||
window.SetClientSize(300, 150)
|
||||
window.SetTitle("croc " + Version)
|
||||
} else {
|
||||
window.SetClientSize(400, 150)
|
||||
window.SetTitle("🐊📦 croc " + Version)
|
||||
}
|
||||
|
||||
labels := make([]*wui.Label, 3)
|
||||
for i := range labels {
|
||||
label := wui.NewLabel()
|
||||
label.SetCenterAlign()
|
||||
label.SetBounds(0, 10+i*20, window.ClientWidth(), 20)
|
||||
window.Add(label)
|
||||
labels[i] = label
|
||||
}
|
||||
labels[0].SetText("secure data transfer")
|
||||
labels[1].SetText("Click 'Send' or 'Receive' to start")
|
||||
|
||||
button := wui.NewButton()
|
||||
button.SetText("Send")
|
||||
window.Add(button)
|
||||
button.SetBounds(10, window.ClientHeight()-70, window.ClientWidth()-20, 25)
|
||||
button.SetOnClick(func() {
|
||||
if isWorking {
|
||||
wui.MessageBoxError(window, "Error", "Can only do one send or receive at a time")
|
||||
return
|
||||
}
|
||||
isWorking = true
|
||||
|
||||
fileDialog := wui.NewFileOpenDialog()
|
||||
fileDialog.SetTitle("Open file to send...")
|
||||
accepted, fn := fileDialog.ExecuteSingleSelection(window)
|
||||
if !accepted {
|
||||
isWorking = false
|
||||
return
|
||||
}
|
||||
if fn == "" {
|
||||
wui.MessageBoxError(window, "Error", "No file selected")
|
||||
isWorking = false
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
cr := croc.Init(false)
|
||||
done := make(chan bool)
|
||||
codePhrase := utils.GetRandomName()
|
||||
_, fname := filepath.Split(fn)
|
||||
labels[0].SetText(fmt.Sprintf("Sending '%s'", fname))
|
||||
labels[1].SetText(fmt.Sprintf("Code phrase: %s", codePhrase))
|
||||
|
||||
go func(done chan bool) {
|
||||
for {
|
||||
if cr.OtherIP != "" && cr.FileInfo.SentName != "" {
|
||||
bytesString := humanize.Bytes(uint64(cr.FileInfo.Size))
|
||||
fileOrFolder := "file"
|
||||
if cr.FileInfo.IsDir {
|
||||
fileOrFolder = "folder"
|
||||
}
|
||||
labels[0].SetText(fmt.Sprintf("Sending %s %s '%s' to %s", bytesString, fileOrFolder, cr.FileInfo.SentName, cr.OtherIP))
|
||||
}
|
||||
if cr.Bar != nil {
|
||||
barState := cr.Bar.State()
|
||||
labels[1].SetText(fmt.Sprintf("%2.1f%% [%2.0fs:%2.0fs]", barState.CurrentPercent*100, barState.SecondsSince, barState.SecondsLeft))
|
||||
}
|
||||
labels[2].SetText(cr.StateString)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
select {
|
||||
case _ = <-done:
|
||||
labels[2].SetText(cr.StateString)
|
||||
return
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}(done)
|
||||
|
||||
cr.Send(fn, codePhrase)
|
||||
done <- true
|
||||
isWorking = false
|
||||
}()
|
||||
})
|
||||
|
||||
receiveButton := wui.NewButton()
|
||||
receiveButton.SetText("Receive")
|
||||
window.Add(receiveButton)
|
||||
receiveButton.SetBounds(10, window.ClientHeight()-35, window.ClientWidth()-20, 25)
|
||||
receiveButton.SetOnClick(func() {
|
||||
if isWorking {
|
||||
wui.MessageBoxError(window, "Error", "Can only do one send or receive at a time")
|
||||
return
|
||||
}
|
||||
labels[1].SetText("")
|
||||
labels[2].SetText("please wait...")
|
||||
isWorking = true
|
||||
defer func() {
|
||||
isWorking = false
|
||||
}()
|
||||
|
||||
// determine the folder to save the file
|
||||
folderDialog := wui.NewFolderSelectDialog()
|
||||
folderDialog.SetTitle("Open folder to receive file...")
|
||||
accepted, fn := folderDialog.Execute(window)
|
||||
if !accepted {
|
||||
return
|
||||
}
|
||||
if len(fn) == 0 {
|
||||
labels[2].SetText(fmt.Sprintf("No folder selected"))
|
||||
return
|
||||
}
|
||||
|
||||
passDlg := wui.NewDialogWindow()
|
||||
passDlg.SetTitle("Enter code phrase")
|
||||
passDlg.SetClientSize(window.ClientWidth()-20, 40)
|
||||
pass := wui.NewEditLine()
|
||||
passDlg.Add(pass)
|
||||
pass.SetPassword(true)
|
||||
pass.SetBounds(10, 10, passDlg.ClientWidth()-20, 20)
|
||||
var passAccepted bool
|
||||
passDlg.SetShortcut(wui.ShortcutKeys{Key: w32.VK_RETURN}, func() {
|
||||
passAccepted = true
|
||||
passDlg.Close()
|
||||
})
|
||||
passDlg.SetShortcut(wui.ShortcutKeys{Key: w32.VK_ESCAPE}, func() {
|
||||
passDlg.Close()
|
||||
})
|
||||
var codePhrase string
|
||||
passDlg.SetOnShow(func() {
|
||||
passDlg.SetX(window.X() + (window.Width()-passDlg.Width())/2)
|
||||
passDlg.SetY(window.Y() + (window.Height()-passDlg.Height())/2)
|
||||
pass.Focus()
|
||||
})
|
||||
passDlg.SetOnClose(func() {
|
||||
if passAccepted {
|
||||
codePhrase = pass.Text()
|
||||
}
|
||||
})
|
||||
passDlg.ShowModal(window)
|
||||
passDlg.Destroy()
|
||||
if len(codePhrase) < 3 {
|
||||
labels[2].SetText(fmt.Sprintf("Invalid codephrase: '%s'", codePhrase))
|
||||
return
|
||||
}
|
||||
|
||||
cr := croc.Init(false)
|
||||
cr.WindowRecipientPrompt = true
|
||||
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
// change into the receiving directory
|
||||
cwd, _ := os.Getwd()
|
||||
defer os.Chdir(cwd)
|
||||
os.Chdir(fn)
|
||||
err := cr.Receive(codePhrase)
|
||||
if err == nil {
|
||||
open.Run(fn)
|
||||
}
|
||||
done <- true
|
||||
done <- true
|
||||
isWorking = false
|
||||
}()
|
||||
go func() {
|
||||
for {
|
||||
if cr.Bar != nil {
|
||||
barState := cr.Bar.State()
|
||||
labels[1].SetText(fmt.Sprintf("%2.1f%% [%2.0fs:%2.0fs]", barState.CurrentPercent*100, barState.SecondsSince, barState.SecondsLeft))
|
||||
}
|
||||
if cr.StateString != "" {
|
||||
labels[2].SetText(cr.StateString)
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
select {
|
||||
case _ = <-done:
|
||||
labels[2].SetText(cr.StateString)
|
||||
return
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
if cr.WindowReceivingString != "" {
|
||||
question := wui.MessageBoxYesNo(
|
||||
window,
|
||||
"croc",
|
||||
fmt.Sprintf("%s?", cr.WindowReceivingString),
|
||||
)
|
||||
if question {
|
||||
cr.WindowRecipientAccept = true
|
||||
labels[0].SetText(cr.WindowReceivingString)
|
||||
} else {
|
||||
cr.WindowRecipientAccept = false
|
||||
labels[2].SetText("canceled")
|
||||
}
|
||||
cr.WindowRecipientPrompt = false
|
||||
cr.WindowReceivingString = ""
|
||||
break
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
select {
|
||||
case _ = <-done:
|
||||
labels[2].SetText(cr.StateString)
|
||||
return
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
window.Show()
|
||||
}
|
|
@ -1,230 +0,0 @@
|
|||
package zipper
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"compress/flate"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
log "github.com/cihub/seelog"
|
||||
"github.com/schollz/croc/src/logger"
|
||||
)
|
||||
|
||||
var DebugLevel string
|
||||
|
||||
func init() {
|
||||
DebugLevel = "info"
|
||||
}
|
||||
|
||||
// UnzipFile will unzip the src directory into the dest
|
||||
func UnzipFile(src, dest string) (err error) {
|
||||
logger.SetLogLevel(DebugLevel)
|
||||
|
||||
r, err := zip.OpenReader(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
for _, f := range r.File {
|
||||
var rc io.ReadCloser
|
||||
rc, err = f.Open()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
// Store filename/path for returning and using later on
|
||||
fpath := filepath.Join(dest, f.Name)
|
||||
log.Debugf("unzipping %s", fpath)
|
||||
fpath = filepath.FromSlash(fpath)
|
||||
|
||||
if f.FileInfo().IsDir() {
|
||||
|
||||
// Make Folder
|
||||
os.MkdirAll(fpath, os.ModePerm)
|
||||
|
||||
} else {
|
||||
|
||||
// Make File
|
||||
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var outFile *os.File
|
||||
outFile, err = os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = io.Copy(outFile, rc)
|
||||
|
||||
// Close the file without defer to close before next iteration of loop
|
||||
outFile.Close()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
log.Debugf("unzipped %s to %s", src, dest)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ZipFiles will zip all the files and the folders as if they were in the same directory
|
||||
func ZipFiles(fnames []string, compress bool) (writtenFilename string, err error) {
|
||||
logger.SetLogLevel(DebugLevel)
|
||||
if len(fnames) == 0 {
|
||||
err = fmt.Errorf("must provide files to zip")
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("zipping %s with compression? %v", fnames, compress)
|
||||
writtenFilename = fmt.Sprintf("%d_files.croc.zip", len(fnames))
|
||||
err = makeZip(writtenFilename, fnames, compress)
|
||||
return
|
||||
}
|
||||
|
||||
// ZipFile will zip the folder
|
||||
func ZipFile(fname string, compress bool) (writtenFilename string, err error) {
|
||||
logger.SetLogLevel(DebugLevel)
|
||||
|
||||
// get path to file and the filename
|
||||
_, filename := filepath.Split(fname)
|
||||
writtenFilename = filename + ".croc.zip"
|
||||
err = makeZip(writtenFilename, []string{fname}, compress)
|
||||
return
|
||||
}
|
||||
|
||||
func makeZip(writtenFilename string, fnames []string, compress bool) (err error) {
|
||||
log.Debugf("creating file: %s", writtenFilename)
|
||||
f, err := os.Create(writtenFilename)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
zipWriter := zip.NewWriter(f)
|
||||
zipWriter.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {
|
||||
if compress {
|
||||
return flate.NewWriter(out, flate.BestSpeed)
|
||||
} else {
|
||||
return flate.NewWriter(out, flate.NoCompression)
|
||||
}
|
||||
})
|
||||
defer zipWriter.Close()
|
||||
|
||||
err = zipFiles(fnames, compress, zipWriter)
|
||||
if err == nil {
|
||||
log.Debugf("wrote zip file to %s", writtenFilename)
|
||||
} else {
|
||||
log.Error(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func zipFiles(fnames []string, compress bool, zipWriter *zip.Writer) (err error) {
|
||||
for _, fname := range fnames {
|
||||
// get absolute filename
|
||||
absPath, err := filepath.Abs(filepath.Clean(fname))
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
absPath = filepath.ToSlash(absPath)
|
||||
|
||||
// get path to file and the filename
|
||||
fpath, fname := filepath.Split(absPath)
|
||||
|
||||
// Get the file information for the target
|
||||
log.Debugf("checking %s", absPath)
|
||||
ftarget, err := os.Open(absPath)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
defer ftarget.Close()
|
||||
info, err := ftarget.Stat()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// write header informaiton
|
||||
header, err := zip.FileInfoHeader(info)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
var writer io.Writer
|
||||
if info.IsDir() {
|
||||
baseDir := filepath.Clean(path.Join(fpath, fname))
|
||||
log.Debugf("walking base dir: %s", baseDir)
|
||||
filepath.Walk(baseDir, func(curpath string, info os.FileInfo, err error) error {
|
||||
curpath = filepath.Clean(curpath)
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
header, err := zip.FileInfoHeader(info)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if baseDir != "" {
|
||||
header.Name = path.Join(fname, strings.TrimPrefix(curpath, baseDir))
|
||||
}
|
||||
header.Name = filepath.ToSlash(filepath.Clean(header.Name))
|
||||
log.Debug(header.Name)
|
||||
|
||||
if info.IsDir() {
|
||||
header.Name += "/"
|
||||
} else {
|
||||
header.Method = zip.Deflate
|
||||
}
|
||||
|
||||
writer, err = zipWriter.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
file, err := os.Open(curpath)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
_, err = io.Copy(writer, file)
|
||||
return err
|
||||
})
|
||||
} else {
|
||||
writer, err = zipWriter.CreateHeader(header)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(writer, ftarget)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
package zipper
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
log "github.com/cihub/seelog"
|
||||
"github.com/schollz/croc/src/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestZip(t *testing.T) {
|
||||
defer log.Flush()
|
||||
DebugLevel = "debug"
|
||||
writtenFilename1, err := ZipFile("../croc", true)
|
||||
assert.Nil(t, err)
|
||||
err = UnzipFile(writtenFilename1, ".")
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, utils.Exists("croc"))
|
||||
|
||||
writtenFilename2, err := ZipFile("../../README.md", false)
|
||||
assert.Nil(t, err)
|
||||
err = UnzipFile(writtenFilename2, ".")
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, utils.Exists("README.md"))
|
||||
|
||||
os.Remove("README.md")
|
||||
os.RemoveAll("croc")
|
||||
os.Remove(writtenFilename1)
|
||||
os.Remove(writtenFilename2)
|
||||
}
|
||||
|
||||
func TestZipFiles(t *testing.T) {
|
||||
defer log.Flush()
|
||||
DebugLevel = "debug"
|
||||
writtenFilename, err := ZipFiles([]string{"../../LICENSE", "../win/Makefile", "../utils"}, true)
|
||||
assert.Nil(t, err)
|
||||
err = UnzipFile(writtenFilename, "zipfilestest")
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, utils.Exists("zipfilestest/LICENSE"))
|
||||
assert.True(t, utils.Exists("zipfilestest/Makefile"))
|
||||
assert.True(t, utils.Exists("zipfilestest/utils/exists.go"))
|
||||
os.RemoveAll("zipfilestest")
|
||||
err = os.Remove(path.Join(".", writtenFilename))
|
||||
assert.Nil(t, err)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue