1
1
Fork 0
mirror of https://github.com/schollz/croc.git synced 2025-10-11 13:21:00 +02:00

remove bits

This commit is contained in:
Zack Scholl 2019-04-03 21:21:35 -06:00
parent 7c731a90dc
commit 00e52848b5
30 changed files with 538 additions and 6527 deletions

2
go.mod
View file

@ -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
View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -1,7 +0,0 @@
package croc
type WebSocketMessage struct {
messageType int
message []byte
err error
}

View file

@ -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++
}
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -1,4 +0,0 @@
package models
const WEBSOCKET_BUFFER_SIZE = 1024 * 1024 * 32
const TCP_BUFFER_SIZE = 1024 * 64

View file

@ -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
}

View file

@ -1,7 +0,0 @@
package models
type Initial struct {
CurveType string
IPAddress string
VersionString string
}

View file

@ -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()
}

View file

@ -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()
}
}
}

View file

@ -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
}

View file

@ -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)
}
}
}

View file

@ -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/

View file

@ -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

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

View file

@ -1 +0,0 @@
IDI_ICON1 ICON DISCARDABLE "icon.ico"

Binary file not shown.

Binary file not shown.

View file

@ -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()
}

View file

@ -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()
}

View file

@ -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
}

View file

@ -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)
}