diff --git a/main.go b/main.go index ab4ca91..5b88e04 100644 --- a/main.go +++ b/main.go @@ -79,18 +79,16 @@ func main() { fmt.Fprintf(c.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n") } app.Action = func(c *cli.Context) error { - fmt.Printf("made it!\n") - return nil + return cr.Receive(c.Args().First()) } app.Before = func(c *cli.Context) error { cr = croc.Init() cr.AllowLocalDiscovery = true cr.WebsocketAddress = c.GlobalString("relay") - cr.Debug = c.GlobalBool("debug") + cr.SetDebug(c.GlobalBool("debug")) cr.Yes = c.GlobalBool("yes") cr.Stdout = c.GlobalBool("stdout") cr.LocalOnly = c.GlobalBool("local") - cr.CodePhrase = c.GlobalString("code") return nil } @@ -106,14 +104,11 @@ func send(c *cli.Context) error { } cr.UseCompression = c.GlobalBoolT("compress") cr.UseEncryption = c.GlobalBoolT("encrypt") - - fmt.Println("sending") - return nil + return cr.Send(c.Args().First(), c.GlobalString("code")) } func receive(c *cli.Context) error { - fmt.Println("receive") - return nil + return cr.Receive(c.GlobalString("code")) } func relay(c *cli.Context) error { diff --git a/src/api.go b/src/api.go index 40b4e8c..e4eaaf2 100644 --- a/src/api.go +++ b/src/api.go @@ -15,12 +15,10 @@ func (c *Croc) Relay() error { // Send will take an existing file or folder and send it through the croc relay func (c *Croc) Send(fname string, codephrase string) (err error) { - err = c.client(0, codephrase, fname) - return + return c.client(0, codephrase, fname) } // Receive will receive something through the croc relay func (c *Croc) Receive(codephrase string) (err error) { - err = c.client(1, codephrase) - return + return c.client(1, codephrase) } diff --git a/src/cleanup.go b/src/cleanup.go index 6dae134..9c3dd87 100644 --- a/src/cleanup.go +++ b/src/cleanup.go @@ -1,6 +1,26 @@ package croc +import ( + "os" + "strconv" +) + func (c *Croc) cleanup() { - // TODO // erase all the croc files and their possible numbers + for i := 0; i < 100; i++ { + fname := c.crocFile + "." + strconv.Itoa(i) + if !exists(fname) { + break + } + os.Remove(fname) + } + for i := 0; i < 100; i++ { + fname := c.crocFileEncrypted + "." + strconv.Itoa(i) + if !exists(fname) { + break + } + os.Remove(fname) + } + os.Remove(c.crocFile) + os.Remove(c.crocFileEncrypted) } diff --git a/src/client.go b/src/client.go index 290ddfe..a3cb09a 100644 --- a/src/client.go +++ b/src/client.go @@ -2,6 +2,7 @@ package croc import ( "encoding/json" + "fmt" "io" "net" "net/url" @@ -16,10 +17,48 @@ import ( "github.com/gorilla/websocket" "github.com/pkg/errors" "github.com/schollz/croc/src/pake" + "github.com/schollz/progressbar" ) func (c *Croc) client(role int, codePhrase string, fname ...string) (err error) { defer log.Flush() + defer c.cleanup() + // initialize the channel data for this client + c.cs.Lock() + c.cs.channel.codePhrase = codePhrase + if len(codePhrase) > 0 { + if len(codePhrase) < 4 { + err = errors.New("code phrase must be more than 4 characters") + c.cs.Unlock() + return + } + c.cs.channel.Channel = codePhrase[:3] + c.cs.channel.passPhrase = codePhrase[3:] + } else { + // TODO + if role == 0 { + // generate code phrase + codePhrase = getRandomName() + dash := strings.Index(codePhrase, "-") + c.cs.channel.Channel = codePhrase[:dash] + c.cs.channel.passPhrase = codePhrase[dash:] + } else { + codePhrase = promptCodePhrase() + if len(codePhrase) < 4 { + err = errors.New("code phrase must be more than 4 characters") + c.cs.Unlock() + return + } + c.cs.channel.Channel = codePhrase[:3] + c.cs.channel.passPhrase = codePhrase[3:] + } + c.cs.channel.codePhrase = codePhrase + } + log.Debugf("codephrase: '%s'", codePhrase) + log.Debugf("channel: '%s'", c.cs.channel.Channel) + log.Debugf("passPhrase: '%s'", c.cs.channel.passPhrase) + channel := c.cs.channel.Channel + c.cs.Unlock() if role == 0 { if len(fname) == 0 { @@ -32,26 +71,6 @@ func (c *Croc) client(role int, codePhrase string, fname ...string) (err error) } } - // initialize the channel data for this client - c.cs.Lock() - - c.cs.channel.codePhrase = codePhrase - if len(codePhrase) > 0 { - if len(codePhrase) < 4 { - err = errors.New("code phrase must be more than 4 characters") - return - } - c.cs.channel.Channel = codePhrase[:3] - c.cs.channel.passPhrase = codePhrase[3:] - } else { - // TODO - // generate code phrase - c.cs.channel.Channel = "chou" - c.cs.channel.passPhrase = codePhrase[3:] - } - channel := c.cs.channel.Channel - c.cs.Unlock() - interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt) @@ -83,7 +102,7 @@ func (c *Croc) client(role int, codePhrase string, fname ...string) (err error) log.Debugf("sender read error:", err) return } - //log.Debugf("recv: %s", cd.String2()) + log.Debugf("recv: %s", cd.String2()) err = c.processState(cd) if err != nil { log.Warn(err) @@ -150,6 +169,17 @@ func (c *Croc) client(role int, codePhrase string, fname ...string) (err error) c.cs.Lock() if c.cs.channel.finishedHappy { log.Info("file recieved!") + if c.cs.channel.Role == 0 { + fmt.Fprintf(os.Stderr, "\nTransfer complete.\n") + } else { + fmt.Fprintf(os.Stderr, "\nReceived file written to %s", c.cs.channel.fileMetaData.Name) + } + } else { + if c.cs.channel.Error != "" { + err = errors.New(c.cs.channel.Error) + } else { + err = errors.New("one party canceled, file not transfered") + } } c.cs.Unlock() return @@ -242,8 +272,6 @@ func (c *Croc) processState(cd channelData) (err error) { // process the client state if c.cs.channel.Pake.IsVerified() && !c.cs.channel.isReady && c.cs.channel.EncryptedFileMetaData.Encrypted != nil { - // TODO: - // check if the user still wants to recieve file // decrypt the meta data log.Debugf("encrypted meta data: %+v", c.cs.channel.EncryptedFileMetaData) @@ -265,6 +293,18 @@ func (c *Croc) processState(cd channelData) (err error) { } log.Debugf("meta data: %+v", c.cs.channel.fileMetaData) + // check if the user still wants to receive the file + if c.cs.channel.Role == 1 { + if !c.Yes { + if !promptOkayToRecieve(c.cs.channel.fileMetaData) { + log.Debug("sending close signal") + c.cs.channel.Close = true + c.cs.channel.Error = "refusing file" + c.cs.channel.ws.WriteJSON(c.cs.channel) + } + } + } + // spawn TCP connections c.cs.channel.isReady = true go c.spawnConnections(c.cs.channel.Role) @@ -344,9 +384,17 @@ func (c *Croc) dialUp() (err error) { } time.Sleep(10 * time.Millisecond) } - c.cs.RLock() + if i == 0 { + c.cs.Lock() + c.bar = progressbar.NewOptions(c.cs.channel.fileMetaData.Size, progressbar.OptionSetWriter(os.Stderr)) + c.cs.Unlock() + if role == 0 { + fmt.Fprintf(os.Stderr, "\nSending...\n") + } else { + fmt.Fprintf(os.Stderr, "\nReceiving...\n") + } + } - c.cs.RUnlock() if role == 0 { log.Debug("send file") for { @@ -360,7 +408,7 @@ func (c *Croc) dialUp() (err error) { } log.Debug("sending file") filename := c.crocFileEncrypted + "." + strconv.Itoa(i) - err = sendFile(filename, i, connection) + err = c.sendFile(filename, i, connection) } else { go func() { time.Sleep(10 * time.Millisecond) @@ -378,8 +426,9 @@ func (c *Croc) dialUp() (err error) { }() receiveFileName := c.crocFileEncrypted + "." + strconv.Itoa(i) log.Debugf("receiving file into %s", receiveFileName) - err = receiveFile(receiveFileName, i, connection) + err = c.receiveFile(receiveFileName, i, connection) } + c.bar.Finish() errorChan <- err }(channel, uuid, port, i, errorChan) } @@ -398,7 +447,7 @@ func (c *Croc) dialUp() (err error) { return } -func receiveFile(filename string, id int, connection net.Conn) error { +func (c *Croc) receiveFile(filename string, id int, connection net.Conn) error { log.Debug("waiting for chunk size from sender") fileSizeBuffer := make([]byte, 10) connection.Read(fileSizeBuffer) @@ -435,16 +484,19 @@ func receiveFile(filename string, id int, connection net.Conn) error { } written, _ := io.CopyN(newFile, connection, bufferSize) receivedBytes += written + c.bar.Add(int(written)) + if !receivedFirstBytes { receivedFirstBytes = true - log.Debug(id, "Receieved first bytes!") + log.Debug(id, "Received first bytes!") } } log.Debug(id, "received file") return nil } -func sendFile(filename string, id int, connection net.Conn) error { +func (c *Croc) sendFile(filename string, id int, connection net.Conn) error { + // open encrypted file chunk, if it exists log.Debug("opening encrypted file chunk: " + filename) file, err := os.Open(filename) @@ -481,6 +533,7 @@ func sendFile(filename string, id int, connection net.Conn) error { _, err := file.Read(sendBuffer) written, _ := connection.Write(sendBuffer) totalBytesSent += written + c.bar.Add(written) // if errWrite != nil { // errWrite = errors.Wrap(errWrite, "problem writing to connection") // return errWrite diff --git a/src/files.go b/src/files.go index 648d1e8..b1fce9d 100644 --- a/src/files.go +++ b/src/files.go @@ -79,6 +79,7 @@ func (c *Croc) processFile(src string) (err error) { c.cs.Lock() defer c.cs.Unlock() c.cs.channel.fileMetaData = fd + go showIntro(c.cs.channel.codePhrase, fd) return } diff --git a/src/models.go b/src/models.go index 8bb9d1f..3ee78b6 100644 --- a/src/models.go +++ b/src/models.go @@ -8,6 +8,7 @@ import ( "github.com/gorilla/websocket" "github.com/schollz/croc/src/pake" + "github.com/schollz/progressbar" ) const ( @@ -41,7 +42,8 @@ type Croc struct { rs relayState // cs keeps the client state - cs clientState + cs clientState + bar *progressbar.ProgressBar // crocFile is the name of the file that is prepared to sent crocFile string @@ -64,6 +66,7 @@ func Init() (c *Croc) { c.rs.channel = make(map[string]*channelData) c.cs.channel = new(channelData) c.rs.Unlock() + return } diff --git a/src/prompts.go b/src/prompts.go new file mode 100644 index 0000000..99d4b44 --- /dev/null +++ b/src/prompts.go @@ -0,0 +1,55 @@ +package croc + +import ( + "bufio" + "fmt" + "os" + "strings" + + humanize "github.com/dustin/go-humanize" +) + +func promptCodePhrase() string { + return getInput("Enter receive code: ") +} + +func promptOkayToRecieve(f FileMetaData) (ok bool) { + fileOrFolder := "file" + if f.IsDir { + fileOrFolder = "folder" + } + return "y" == getInput(fmt.Sprintf( + `Receiving %s (%s) into: %s +ok? (y/N): `, + fileOrFolder, + humanize.Bytes(uint64(f.Size)), + f.Name, + )) +} + +func showIntro(code string, f FileMetaData) { + fileOrFolder := "file" + if f.IsDir { + fileOrFolder = "folder" + } + fmt.Fprintf(os.Stderr, + `Sending %s %s named '%s' +Code is: %s +On the other computer, please run: + +croc %s +`, + humanize.Bytes(uint64(f.Size)), + fileOrFolder, + f.Name, + code, + code, + ) +} + +func getInput(prompt string) string { + reader := bufio.NewReader(os.Stdin) + fmt.Fprintf(os.Stderr, "%s", prompt) + text, _ := reader.ReadString('\n') + return strings.TrimSpace(text) +}