mirror of https://github.com/schollz/croc.git
commit
f2e420cd2c
Binary file not shown.
Before Width: | Height: | Size: 30 KiB |
Binary file not shown.
Before Width: | Height: | Size: 25 KiB |
Binary file not shown.
Before Width: | Height: | Size: 203 KiB |
Binary file not shown.
Before Width: | Height: | Size: 351 KiB |
|
@ -1,17 +0,0 @@
|
|||
## Expected Behavior
|
||||
|
||||
|
||||
## Actual Behavior
|
||||
|
||||
|
||||
## Steps to Reproduce the Problem
|
||||
|
||||
1.
|
||||
1.
|
||||
1.
|
||||
|
||||
## Specifications
|
||||
|
||||
- Version (`croc -version` or let me know latest commit in git log if using dev):
|
||||
- Platform:
|
||||
- Subsystem:
|
14
.travis.yml
14
.travis.yml
|
@ -10,12 +10,10 @@ install: true
|
|||
|
||||
script:
|
||||
- env GO111MODULE=on go build -v
|
||||
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/src/compress
|
||||
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/src/croc
|
||||
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/src/crypt
|
||||
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/src/models
|
||||
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/src/relay
|
||||
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/src/tcp
|
||||
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/src/utils
|
||||
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/src/zipper
|
||||
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/v6/src/compress
|
||||
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/v6/src/croc
|
||||
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/v6/src/crypt
|
||||
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/v6/src/tcp
|
||||
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/v6/src/utils
|
||||
- env GO111MODULE=on go test -v -cover github.com/schollz/croc/v6/src/comm
|
||||
|
||||
|
|
118
README.md
118
README.md
|
@ -4,8 +4,7 @@
|
|||
src="https://user-images.githubusercontent.com/6550035/46709024-9b23ad00-cbf6-11e8-9fb2-ca8b20b7dbec.jpg"
|
||||
width="408px" border="0" alt="croc">
|
||||
<br>
|
||||
<a href="https://github.com/schollz/croc/releases/latest"><img src="https://img.shields.io/badge/version-4.1.5-brightgreen.svg?style=flat-square" alt="Version"></a>
|
||||
<img src="https://img.shields.io/badge/coverage-77%25-brightgreen.svg?style=flat-square" alt="Code coverage">
|
||||
<a href="https://github.com/schollz/croc/releases/latest"><img src="https://img.shields.io/badge/version-v6.0.0-brightgreen.svg?style=flat-square" alt="Version"></a>
|
||||
<a href="https://travis-ci.org/schollz/croc"><img
|
||||
src="https://img.shields.io/travis/schollz/croc.svg?style=flat-square" alt="Build
|
||||
Status"></a>
|
||||
|
@ -15,140 +14,94 @@ Status"></a>
|
|||
|
||||
<p align="center"><code>curl https://getcroc.schollz.com | bash</code></p>
|
||||
|
||||
*croc* is a tool that allows any two computers to simply and securely transfer files and folders. There are many tools that can do this but AFAIK *croc* is the only tool that is easily installed and used on any platform, *and* has secure peer-to-peer transferring, *and* has the capability to resume broken transfers. _Note:_ A new WebRTC version is in progress.
|
||||
`croc` is a tool that allows any two computers to simply and securely transfer files and folders. AFAIK, *croc* is the only CLI file-transfer tool does **all** of the following:
|
||||
|
||||
## Overview
|
||||
- allows **any two computers** to transfer data (using a relay)
|
||||
- provides **end-to-end encryption** (using PAKE)
|
||||
- enables easy **cross-platform** transfers (Windows, Linux, Mac)
|
||||
- allows **multiple file** transfers
|
||||
- allows **resuming transfers** that are interrupted
|
||||
- does *not* require a server or port-forwarding
|
||||
|
||||
**Transmit encrypted data with a code phrase**
|
||||
For more information about `croc`, see [my blog post](https://schollz.com/software/croc6).
|
||||
|
||||
*croc* securely transfers data using *code phrases* - a combination of three random words (mnemonicoded 4 bytes). The code phrase is shared between the sender and the recipient for password authenticated key exchange ([PAKE](https://github.com/schollz/pake)), a cryptographic method to use a shared weak key (the "code phrase") to generate a strong key for secure end-to-end encryption. By default, a code phrase can only be used once between two parties so an attacker would have a chance of less than 1 in *4 billion* to guess the code phrase correctly to steal the data. An attacker with the wrong code phrase will fail the PAKE and the sender will be notified without any data transfering. Only two people with the right code phrase will be able to computers transfer encrypted data through a relay.
|
||||
|
||||
**Fast data transfer through TCP**
|
||||
|
||||
The actual data transfer is accomplished using a relay, either using raw TCP sockets or websockets. If both computers are on the LAN network then *croc* will use a local relay, otherwise a public relay is used. All the data going through the relay is encrypted using the PAKE-generated session key, so the relay can't spy on information passing through it. The data is transferred in blocks, where each block is compressed and encrypted, and the recipient keeps track of blocks received so that it can resume the transfer if interrupted.
|
||||
|
||||
**Relay allows any two computers to connect**
|
||||
|
||||
*croc* differs from a utility like *scp* because it doesn't require any two computers to have enabled port-forwarding. Instead, *croc* will uses a relay - a temporary server setup locally (if both computers are on lan) or publicly (default is at croc4.schollz.com). Any two computers can connect to the relay, and after securing their channel with PAKE, they can transfer encrypted metadata and data through the relay. The relay works by first having the computers communicate the PAKE protocol via websockets, and then exchanging encrypted metadata, and then stapling the TCP connections directly so that they can transfer directly.
|
||||
|
||||
**Why another data transfer utility?**
|
||||
|
||||
My motivation to write *croc*, as stupid as it sounds, is because I wanted to create a program that made it easy to send a 3GB+ PBS documentary to my friend in a different country. My friend has a Windows computer and is not comfortable using a terminal. So I wanted to write a program that, while secure, is simple to receive a file. *croc* accomplishes this, and now I find myself using it almost everyday at work. To receive a file you can just download the executable and double click on it. The name is inspired by the [fable of the frog and the crocodile](https://web.archive.org/web/20180926035731/http://allaboutfrogs.org/stories/crocodile.html).
|
||||
|
||||
## Examples
|
||||
|
||||
The first example shows the basic transfer of some file or folder from computer 1 to computer 2. _These two gifs should run in sync if you force-reload (Ctl+F5)_
|
||||
|
||||
![send](.github/1.gif)
|
||||
|
||||
![receive](.github/2.gif)
|
||||
|
||||
The second example shows how you can restart a broken transfer. Here, computer 2 presses Ctl+C during a transfer to abruptly break the connection, and then resumes by having computer 1 re-send the file. _These two gifs should run in sync if you force-reload (Ctl+F5)_
|
||||
|
||||
![send](.github/3.gif)
|
||||
|
||||
![receive](.github/4.gif)
|
||||
|
||||
In version 4, there is now a GUI available for [Windows](https://github.com/schollz/croc/releases/latest) and [Linux](https://github.com/schollz/croc/releases/latest):
|
||||
|
||||
<div style="text-align:center">
|
||||
<img src="https://user-images.githubusercontent.com/6550035/47256575-8a193e00-d437-11e8-96fa-42c9d072a8f1.PNG">
|
||||
</div>
|
||||
|
||||
## Install
|
||||
|
||||
[Download the latest release for your system](https://github.com/schollz/croc/releases/latest).
|
||||
|
||||
Or, you can [install Go](https://golang.org/dl/) and build from source with `go get github.com/schollz/croc`. Since *croc* uses [Go modules](https://golang.org/doc/go1.11#modules) it requires Go version 1.11+.
|
||||
|
||||
Or, you can quickly install a release from the command-line:
|
||||
Download [the latest release for your system](https://github.com/schollz/croc/releases/latest), or install a release from the command-line:
|
||||
|
||||
```
|
||||
$ curl https://getcroc.schollz.com | bash
|
||||
```
|
||||
|
||||
|
||||
Or, you can [install Go](https://golang.org/dl/) and build from source (requires Go 1.11+):
|
||||
|
||||
```
|
||||
$ wget -qO- https://getcroc.schollz.com | bash
|
||||
go get github.com/schollz/croc
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
The basic usage is to just do
|
||||
To send a file, simply do:
|
||||
|
||||
```
|
||||
$ croc send FILE
|
||||
Sending 'FILE' (X MB)
|
||||
Code is: code-phrase
|
||||
```
|
||||
|
||||
to send and then on the other computer you can just do
|
||||
Them to receive the file on another computer, you can just do
|
||||
|
||||
```
|
||||
$ croc [code phrase]
|
||||
$ croc code-phrase
|
||||
```
|
||||
|
||||
to receive (you'll be prompted to enter the code phrase). Note, by default, you don't need any arguments for receiving, instead you will be prompted to enter the code phrase. This makes it possible for you to just double click the executable to run (nice for those of us that aren't computer wizards).
|
||||
The code phrase is used to establish password-authenticated key agreement ([PAKE](https://en.wikipedia.org/wiki/Password-authenticated_key_agreement)) which generates a secret key for the sender and recipient to use for end-to-end encryption.
|
||||
|
||||
### Custom code phrase
|
||||
|
||||
You can send with your own code phrase (must be more than 4 characters).
|
||||
|
||||
```
|
||||
$ croc send --code [code phrase] [filename]
|
||||
$ croc send --code [code-phrase] [filename]
|
||||
```
|
||||
|
||||
### Use locally
|
||||
|
||||
*croc* automatically will attempt to start a local connection on your LAN to transfer the file much faster. It uses [peer discovery](https://github.com/schollz/peerdiscovery), basically broadcasting a message on the local subnet to see if another *croc* user wants to receive the file. *croc* will utilize the first incoming connection from either the local network or the public relay and follow through with PAKE.
|
||||
### Use pipes - stdin and stdout
|
||||
|
||||
You can change this behavior by forcing *croc* to use only local connections (`--local`) or force to use the public relay only (`--no-local`):
|
||||
|
||||
```
|
||||
$ croc --local/--no-local send [filename]
|
||||
```
|
||||
|
||||
### Using pipes - stdin and stdout
|
||||
|
||||
You can easily use *croc* in pipes when you need to send data through stdin or get data from stdout. To send you can just use pipes:
|
||||
You can pipe to `croc`:
|
||||
|
||||
```
|
||||
$ cat [filename] | croc send
|
||||
```
|
||||
|
||||
In this case *croc* will automatically use the stdin data and send and assign a filename like "croc-stdin-123456789". To receive to stdout at you can always just use the `-yes` and `-stdout` flags which will automatically approve the transfer and pipe it out to stdout.
|
||||
In this case `croc` will automatically use the stdin data and send and assign a filename like "croc-stdin-123456789". To receive to `stdout` at you can always just use the `--yes` and `--stdout` flags which will automatically approve the transfer and pipe it out to `stdout`.
|
||||
|
||||
```
|
||||
$ croc -yes -stdout [code phrase] > out
|
||||
$ croc --yes --stdout [code-phrase] > out
|
||||
```
|
||||
|
||||
All of the other text printed to the console is going to `stderr` so it will not interfere with the message going to stdout.
|
||||
All of the other text printed to the console is going to `stderr` so it will not interfere with the message going to `stdout`.
|
||||
|
||||
### Self-host relay
|
||||
|
||||
The relay is needed to staple the parallel incoming and outgoing connections. The relay temporarily stores connection information and the encrypted meta information. The default uses a public relay at, `croc4.schollz.com`. You can also run your own relay, it is very easy, just run:
|
||||
The relay is needed to staple the parallel incoming and outgoing connections. By default, `croc` uses a public relay but you can also run your own relay:
|
||||
|
||||
```
|
||||
$ croc relay
|
||||
```
|
||||
|
||||
Make sure to open up TCP ports (see `croc relay --help` for which ports to open). Relays can also be customized to which elliptic curve they will use (default is siec).
|
||||
Make sure to open up TCP ports (see `croc relay --help` for which ports to open).
|
||||
|
||||
You can send files using your relay by entering `-addr` to change the relay that you are using if you want to custom host your own.
|
||||
You can send files using your relay by entering `--relay` to change the relay that you are using if you want to custom host your own.
|
||||
|
||||
```
|
||||
$ croc -addr "myrelay.example.com" send [filename]
|
||||
$ croc --relay "myrelay.example.com:9009" send [filename]
|
||||
```
|
||||
|
||||
### Configuration file
|
||||
|
||||
You can also make some paramters static by using a configuration file. To get started with the config file just do
|
||||
|
||||
```
|
||||
$ croc config
|
||||
```
|
||||
|
||||
which will generate the file that you can edit.
|
||||
Any changes you make to the configuration file will be applied *before* the command-line flags, if any.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
|
@ -156,11 +109,6 @@ MIT
|
|||
|
||||
## Acknowledgements
|
||||
|
||||
*croc* has been through many iterations, and I am awed by all the great contributions! If you feel like contributing, in any way, by all means you can send an Issue, a PR, ask a question, or tweet me ([@yakczar](http://ctt.ec/Rq054)).
|
||||
`croc` has been through many iterations, and I am awed by all the great contributions! If you feel like contributing, in any way, by all means you can send an Issue, a PR, ask a question, or tweet me ([@yakczar](http://ctt.ec/Rq054)).
|
||||
|
||||
Thanks...
|
||||
|
||||
- ...[@warner](https://github.com/warner) for the [idea](https://github.com/warner/magic-wormhole).
|
||||
- ...[@tscholl2](https://github.com/tscholl2) for the [encryption gists](https://gist.github.com/tscholl2/dc7dc15dc132ea70a98e8542fefffa28).
|
||||
- ...[@skorokithakis](https://github.com/skorokithakis) for [code on proxying two connections](https://www.stavros.io/posts/proxying-two-connections-go/).
|
||||
- ...for making pull requests [@Girbons](https://github.com/Girbons), [@techtide](https://github.com/techtide), [@heymatthew](https://github.com/heymatthew), [@Lunsford94](https://github.com/Lunsford94), [@lummie](https://github.com/lummie), [@jesuiscamille](https://github.com/jesuiscamille), [@threefjord](https://github.com/threefjord), [@marcossegovia](https://github.com/marcossegovia), [@csleong98](https://github.com/csleong98), [@afotescu](https://github.com/afotescu), [@callmefever](https://github.com/callmefever), [@El-JojA](https://github.com/El-JojA), [@anatolyyyyyy](https://github.com/anatolyyyyyy), [@goggle](https://github.com/goggle), [@smileboywtu](https://github.com/smileboywtu), [@nicolashardy](https://github.com/nicolashardy)!
|
||||
Thanks [@warner](https://github.com/warner) for the [idea](https://github.com/warner/magic-wormhole), [@tscholl2](https://github.com/tscholl2) for the [encryption gists](https://gist.github.com/tscholl2/dc7dc15dc132ea70a98e8542fefffa28), [@skorokithakis](https://github.com/skorokithakis) for [code on proxying two connections](https://www.stavros.io/posts/proxying-two-connections-go/). Finally thanks for making pull requests [@Girbons](https://github.com/Girbons), [@techtide](https://github.com/techtide), [@heymatthew](https://github.com/heymatthew), [@Lunsford94](https://github.com/Lunsford94), [@lummie](https://github.com/lummie), [@jesuiscamille](https://github.com/jesuiscamille), [@threefjord](https://github.com/threefjord), [@marcossegovia](https://github.com/marcossegovia), [@csleong98](https://github.com/csleong98), [@afotescu](https://github.com/afotescu), [@callmefever](https://github.com/callmefever), [@El-JojA](https://github.com/El-JojA), [@anatolyyyyyy](https://github.com/anatolyyyyyy), [@goggle](https://github.com/goggle), [@smileboywtu](https://github.com/smileboywtu), [@nicolashardy](https://github.com/nicolashardy)!
|
||||
|
|
19
go.mod
19
go.mod
|
@ -1,27 +1,20 @@
|
|||
module github.com/schollz/croc
|
||||
module github.com/schollz/croc/v6
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/denisbrodbeck/machineid v1.0.1
|
||||
github.com/fatih/color v1.7.0 // indirect
|
||||
github.com/gonutz/w32 v1.0.0
|
||||
github.com/gonutz/wui v1.0.0
|
||||
github.com/gorilla/websocket v1.4.0
|
||||
github.com/mattn/go-colorable v0.1.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.7 // indirect
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/schollz/mnemonicode v1.0.1
|
||||
github.com/schollz/pake v1.1.0
|
||||
github.com/schollz/peerdiscovery v1.4.0
|
||||
github.com/schollz/progressbar/v2 v2.10.0
|
||||
github.com/schollz/progressbar/v2 v2.12.1
|
||||
github.com/schollz/spinner v0.0.0-20180925172146-6bbc5f7804f9
|
||||
github.com/skratchdot/open-golang v0.0.0-20190104022628-a2dfa6d0dab6
|
||||
github.com/stretchr/testify v1.3.0
|
||||
github.com/urfave/cli v1.20.0
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a // indirect
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313 // indirect
|
||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734
|
||||
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872 // indirect
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
)
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 h1:kHaBemcxl8o/pQ5VM1c8PVE1PubbNx3mjUr09OqWGCs=
|
||||
github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575/go.mod h1:9d6lWj8KzO/fd/NrVaLscBKmPigpZpn5YawRPw+e3Yo=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
|
||||
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mitchellh/colorstring v0.0.0-20150917214807-8631ce90f286/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/schollz/mnemonicode v1.0.1 h1:LiH5hwADZwjwnfXsaD4xgnMyTAtaKHN+e5AyjRU6WSU=
|
||||
github.com/schollz/mnemonicode v1.0.1/go.mod h1:cl4UAOhUV0mkdjMj/QYaUZbZZdF8BnOqoz8rHMzwboY=
|
||||
github.com/schollz/pake v1.1.0 h1:+tYqsPVkuirFpmeRePjYTUhIHHKLufdmd7QfuspaXCk=
|
||||
github.com/schollz/pake v1.1.0/go.mod h1:pL7Z08gnQ4OQ3G27s5e5T6TEzp6cFc5GzCwLm0f75Io=
|
||||
github.com/schollz/peerdiscovery v1.4.0 h1:wJWiJUBSMY2io9eIG1+gauXm8WD6sJVN5M+pLd4fYZQ=
|
||||
github.com/schollz/peerdiscovery v1.4.0/go.mod h1:DXj/7VvxAkUuSZNabx3q8t524uWbrhMPxeX151kvvHs=
|
||||
github.com/schollz/progressbar/v2 v2.9.1/go.mod h1:l6tn6yU6ZdQoF8lwX/VoAUQ3FjhCbrcZDnl9xeWZzYw=
|
||||
github.com/schollz/progressbar/v2 v2.12.1 h1:0Ce7IBClG+s3lxXN1Noqwh7aToKGL5a3mnMfPJqDlv4=
|
||||
github.com/schollz/progressbar/v2 v2.12.1/go.mod h1:fBI3onORwtNtwCWJHsrXtjE3QnJOtqIZrvr3rDaF7L0=
|
||||
github.com/schollz/spinner v0.0.0-20180925172146-6bbc5f7804f9 h1:y08o5oQ/slxXE/F0uh5dd8mdVvb+w4NLcNSDSq4c2F0=
|
||||
github.com/schollz/spinner v0.0.0-20180925172146-6bbc5f7804f9/go.mod h1:kCMoQsqzx4MzGJWaALr6tKyCnlrY0kILGLkA1FOiLF4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/tscholl2/siec v0.0.0-20180721101609-21667da05937 h1:lhssCpSe3TjKcbvUoPzFMuv9oUyZDgI3Cmgolfw2C90=
|
||||
github.com/tscholl2/siec v0.0.0-20180721101609-21667da05937/go.mod h1:KL9+ubr1JZdaKjgAaHr+tCytEncXBa1pR6FjbTsOJnw=
|
||||
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo=
|
||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872 h1:cGjJzUd8RgBw428LXP65YXni0aiGNA4Bl+ls8SmLOm8=
|
||||
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
12
main.go
12
main.go
|
@ -1,12 +1,14 @@
|
|||
package main
|
||||
//go:generate go run src/install/updateversion.go
|
||||
|
||||
import (
|
||||
"github.com/schollz/croc/src/cli"
|
||||
"fmt"
|
||||
|
||||
"github.com/schollz/croc/v6/src/cli"
|
||||
)
|
||||
|
||||
var Version string
|
||||
|
||||
func main() {
|
||||
cli.Version = Version
|
||||
cli.Run()
|
||||
if err := cli.Run(); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
|
255
src/cli/cli.go
255
src/cli/cli.go
|
@ -1,6 +1,7 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
@ -11,25 +12,23 @@ import (
|
|||
"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/schollz/croc/v6/src/croc"
|
||||
"github.com/schollz/croc/v6/src/tcp"
|
||||
"github.com/schollz/croc/v6/src/utils"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var Version string
|
||||
var cr *croc.Croc
|
||||
|
||||
func Run() {
|
||||
func Run() (err error) {
|
||||
// use all of the processors
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
|
||||
app := cli.NewApp()
|
||||
app.Name = "croc"
|
||||
if Version == "" {
|
||||
Version = "dev"
|
||||
Version = "v6.0.0"
|
||||
}
|
||||
|
||||
app.Version = Version
|
||||
app.Compiled = time.Now()
|
||||
app.Usage = "easily and securely transfer stuff from one computer to another"
|
||||
|
@ -42,9 +41,8 @@ func Run() {
|
|||
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"},
|
||||
cli.StringFlag{Name: "ports", Value: "9009,9010,9011,9012,9013", Usage: "ports of the local relay (optional)"},
|
||||
},
|
||||
HelpName: "croc send",
|
||||
Action: func(c *cli.Context) error {
|
||||
|
@ -53,39 +51,21 @@ func Run() {
|
|||
},
|
||||
{
|
||||
Name: "relay",
|
||||
Usage: "start a croc relay",
|
||||
Description: "the croc relay will handle websocket and TCP connections",
|
||||
Flags: []cli.Flag{},
|
||||
Description: "start relay",
|
||||
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)
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{Name: "ports", Value: "9009,9010,9011,9012,9013", Usage: "ports of the relay"},
|
||||
},
|
||||
},
|
||||
}
|
||||
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: "relay", Value: "198.199.67.130:9009", Usage: "address of the relay"},
|
||||
cli.StringFlag{Name: "out", Value: ".", Usage: "specify an output folder to receive the file"},
|
||||
}
|
||||
app.EnableBashCompletion = true
|
||||
|
@ -105,44 +85,18 @@ func Run() {
|
|||
}
|
||||
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")
|
||||
return app.Run(os.Args)
|
||||
}
|
||||
|
||||
func saveDefaultConfig(c *cli.Context) error {
|
||||
return croc.SaveDefaultConfig()
|
||||
}
|
||||
// func saveDefaultConfig(c *cli.Context) error {
|
||||
// return croc.SaveDefaultConfig()
|
||||
// }
|
||||
|
||||
func send(c *cli.Context) error {
|
||||
func send(c *cli.Context) (err error) {
|
||||
|
||||
var fnames []string
|
||||
stat, _ := os.Stdin.Stat()
|
||||
var fname string
|
||||
if (stat.Mode() & os.ModeCharDevice) == 0 {
|
||||
f, err := ioutil.TempFile(".", "croc-stdin-")
|
||||
if err != nil {
|
||||
|
@ -156,102 +110,135 @@ func send(c *cli.Context) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fname = f.Name()
|
||||
fnames = []string{f.Name()}
|
||||
defer func() {
|
||||
err = os.Remove(fname)
|
||||
err = os.Remove(fnames[0])
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
fname = c.Args().First()
|
||||
fnames = append([]string{c.Args().First()}, c.Args().Tail()...)
|
||||
}
|
||||
if fname == "" {
|
||||
if len(fnames) == 0 {
|
||||
return errors.New("must specify file: croc send [filename]")
|
||||
}
|
||||
cr.UseCompression = !c.Bool("no-compress")
|
||||
cr.UseEncryption = !c.Bool("no-encrypt")
|
||||
|
||||
var sharedSecret string
|
||||
if c.String("code") != "" {
|
||||
cr.Codephrase = c.String("code")
|
||||
sharedSecret = c.String("code")
|
||||
}
|
||||
cr.LoadConfig()
|
||||
if len(cr.Codephrase) == 0 {
|
||||
// cr.LoadConfig()
|
||||
if len(sharedSecret) == 0 {
|
||||
// generate code phrase
|
||||
cr.Codephrase = utils.GetRandomName()
|
||||
sharedSecret = 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)
|
||||
haveFolder := false
|
||||
paths := []string{}
|
||||
for _, fname := range fnames {
|
||||
stat, err := os.Stat(fname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stat.IsDir() {
|
||||
haveFolder = true
|
||||
err = filepath.Walk(fname,
|
||||
func(pathName string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir() {
|
||||
paths = append(paths, filepath.ToSlash(pathName))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
paths = append(paths, filepath.ToSlash(fname))
|
||||
}
|
||||
}
|
||||
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")
|
||||
cr, err := croc.New(croc.Options{
|
||||
SharedSecret: sharedSecret,
|
||||
IsSender: true,
|
||||
Debug: c.GlobalBool("debug"),
|
||||
NoPrompt: c.GlobalBool("yes"),
|
||||
RelayAddress: c.GlobalString("relay"),
|
||||
Stdout: c.GlobalBool("stdout"),
|
||||
RelayPorts: strings.Split(c.String("ports"), ","),
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return cr.Send(fname, cr.Codephrase)
|
||||
|
||||
err = cr.Send(croc.TransferOptions{
|
||||
PathToFiles: paths,
|
||||
KeepPathInRemote: haveFolder,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func receive(c *cli.Context) error {
|
||||
func receive(c *cli.Context) (err error) {
|
||||
var sharedSecret string
|
||||
if c.GlobalString("code") != "" {
|
||||
cr.Codephrase = c.GlobalString("code")
|
||||
sharedSecret = c.GlobalString("code")
|
||||
}
|
||||
if c.Args().First() != "" {
|
||||
cr.Codephrase = c.Args().First()
|
||||
sharedSecret = c.Args().First()
|
||||
}
|
||||
if sharedSecret == "" {
|
||||
sharedSecret = utils.GetInput("Enter receive code: ")
|
||||
}
|
||||
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
|
||||
cr, err := croc.New(croc.Options{
|
||||
SharedSecret: sharedSecret,
|
||||
IsSender: false,
|
||||
Debug: c.GlobalBool("debug"),
|
||||
NoPrompt: c.GlobalBool("yes"),
|
||||
RelayAddress: c.GlobalString("relay"),
|
||||
Stdout: c.GlobalBool("stdout"),
|
||||
})
|
||||
return size, err
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = cr.Receive()
|
||||
return
|
||||
}
|
||||
|
||||
func relay(c *cli.Context) (err error) {
|
||||
debugString := "info"
|
||||
if c.GlobalBool("debug") {
|
||||
debugString = "debug"
|
||||
}
|
||||
ports := strings.Split(c.String("ports"), ",")
|
||||
tcpPorts := strings.Join(ports[1:], ",")
|
||||
for i, port := range ports {
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
go func(portStr string) {
|
||||
err = tcp.Run(debugString, portStr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}(port)
|
||||
}
|
||||
return tcp.Run(debugString, ports[0], tcpPorts)
|
||||
}
|
||||
|
||||
// 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
|
||||
// }
|
||||
|
|
124
src/comm/comm.go
124
src/comm/comm.go
|
@ -2,10 +2,9 @@ package comm
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
@ -16,29 +15,43 @@ type Comm struct {
|
|||
connection net.Conn
|
||||
}
|
||||
|
||||
// NewConnection gets a new comm to a tcp address
|
||||
func NewConnection(address string) (c *Comm, err error) {
|
||||
connection, err := net.DialTimeout("tcp", address, 30*time.Second)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c = New(connection)
|
||||
return
|
||||
}
|
||||
|
||||
// New returns a new comm
|
||||
func New(c net.Conn) 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}
|
||||
comm := new(Comm)
|
||||
comm.connection = c
|
||||
return comm
|
||||
}
|
||||
|
||||
// Connection returns the net.Conn connection
|
||||
func (c Comm) Connection() net.Conn {
|
||||
func (c *Comm) Connection() net.Conn {
|
||||
return c.connection
|
||||
}
|
||||
|
||||
// Close closes the connection
|
||||
func (c Comm) Close() {
|
||||
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)
|
||||
func (c *Comm) Write(b []byte) (int, error) {
|
||||
header := new(bytes.Buffer)
|
||||
err := binary.Write(header, binary.LittleEndian, uint32(len(b)))
|
||||
if err != nil {
|
||||
fmt.Println("binary.Write failed:", err)
|
||||
}
|
||||
tmpCopy := append(header.Bytes(), b...)
|
||||
n, err := c.connection.Write(tmpCopy)
|
||||
if n != len(tmpCopy) {
|
||||
if err != nil {
|
||||
|
@ -51,70 +64,55 @@ func (c Comm) Write(b []byte) (int, error) {
|
|||
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)
|
||||
func (c *Comm) Read() (buf []byte, numBytes int, bs []byte, err error) {
|
||||
// read until we get 4 bytes for the header
|
||||
var header []byte
|
||||
numBytes = 4
|
||||
for {
|
||||
// see if we have enough bytes
|
||||
bs = bytes.Trim(bs, "\x00")
|
||||
if len(bs) == 5 {
|
||||
break
|
||||
tmp := make([]byte, numBytes-len(header))
|
||||
n, errRead := c.connection.Read(tmp)
|
||||
if errRead != nil {
|
||||
err = errRead
|
||||
return
|
||||
}
|
||||
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 {
|
||||
header = append(header, tmp[:n]...)
|
||||
if numBytes == len(header) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var numBytesUint32 uint32
|
||||
rbuf := bytes.NewReader(header)
|
||||
err = binary.Read(rbuf, binary.LittleEndian, &numBytesUint32)
|
||||
if err != nil {
|
||||
fmt.Println("binary.Read failed:", err)
|
||||
}
|
||||
numBytes = int(numBytesUint32)
|
||||
buf = make([]byte, 0)
|
||||
for {
|
||||
// log.Debugf("bytes: %d/%d",len(buf),numBytes)
|
||||
tmp := make([]byte, numBytes-len(buf))
|
||||
n, errRead := c.connection.Read(tmp)
|
||||
if errRead != nil {
|
||||
err = errRead
|
||||
return
|
||||
}
|
||||
buf = append(buf, tmp[:n]...)
|
||||
if numBytes == len(buf) {
|
||||
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))
|
||||
func (c *Comm) Send(message []byte) (err error) {
|
||||
_, err = c.Write(message)
|
||||
return
|
||||
}
|
||||
|
||||
// Receive a message
|
||||
func (c Comm) Receive() (s string, err error) {
|
||||
b, _, _, err := c.Read()
|
||||
s = string(b)
|
||||
func (c *Comm) Receive() (b []byte, err error) {
|
||||
b, _, _, err = c.Read()
|
||||
return
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
package comm
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
log "github.com/cihub/seelog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestComm(t *testing.T) {
|
||||
defer log.Flush()
|
||||
token := make([]byte, 40000000)
|
||||
rand.Read(token)
|
||||
|
||||
port := "8001"
|
||||
go func() {
|
||||
log.Debugf("starting TCP server on " + port)
|
||||
server, err := net.Listen("tcp", "0.0.0.0:"+port)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
defer server.Close()
|
||||
// spawn a new goroutine whenever a client connects
|
||||
for {
|
||||
connection, err := server.Accept()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
log.Debugf("client %s connected", connection.RemoteAddr().String())
|
||||
go func(port string, connection net.Conn) {
|
||||
c := New(connection)
|
||||
err = c.Send([]byte("hello, world"))
|
||||
assert.Nil(t, err)
|
||||
data, err := c.Receive()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []byte("hello, computer"), data)
|
||||
data, err = c.Receive()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []byte{'\x00'}, data)
|
||||
data, err = c.Receive()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, token, data)
|
||||
}(port, connection)
|
||||
}
|
||||
}()
|
||||
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
a, err := NewConnection("localhost:" + port)
|
||||
assert.Nil(t, err)
|
||||
data, err := a.Receive()
|
||||
assert.Equal(t, []byte("hello, world"), data)
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, a.Send([]byte("hello, computer")))
|
||||
assert.Nil(t, a.Send([]byte{'\x00'}))
|
||||
|
||||
assert.Nil(t, a.Send(token))
|
||||
|
||||
}
|
|
@ -75,4 +75,16 @@ func TestCompress(t *testing.T) {
|
|||
dataRateSavings = 100 * (1.0 - float64(len(compressedB))/float64(len(fable)))
|
||||
fmt.Printf("Level -2: %2.0f%% percent space savings\n", dataRateSavings)
|
||||
assert.True(t, len(compressedB) < len(fable))
|
||||
|
||||
data := make([]byte, 4096)
|
||||
rand.Read(data)
|
||||
compressedB = CompressWithOption(data, -2)
|
||||
dataRateSavings = 100 * (1.0 - float64(len(compressedB))/float64(len(data)))
|
||||
fmt.Printf("random, Level -2: %2.0f%% percent space savings\n", dataRateSavings)
|
||||
|
||||
rand.Read(data)
|
||||
compressedB = CompressWithOption(data, 9)
|
||||
dataRateSavings = 100 * (1.0 - float64(len(compressedB))/float64(len(data)))
|
||||
fmt.Printf("random, Level 9: %2.0f%% percent space savings\n", dataRateSavings)
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
904
src/croc/croc.go
904
src/croc/croc.go
|
@ -1,96 +1,842 @@
|
|||
package croc
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"bytes"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"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"
|
||||
log "github.com/cihub/seelog"
|
||||
"github.com/denisbrodbeck/machineid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/schollz/croc/v6/src/comm"
|
||||
"github.com/schollz/croc/v6/src/compress"
|
||||
"github.com/schollz/croc/v6/src/crypt"
|
||||
"github.com/schollz/croc/v6/src/logger"
|
||||
"github.com/schollz/croc/v6/src/message"
|
||||
"github.com/schollz/croc/v6/src/models"
|
||||
"github.com/schollz/croc/v6/src/tcp"
|
||||
"github.com/schollz/croc/v6/src/utils"
|
||||
"github.com/schollz/pake"
|
||||
"github.com/schollz/peerdiscovery"
|
||||
"github.com/schollz/progressbar/v2"
|
||||
"github.com/schollz/spinner"
|
||||
)
|
||||
|
||||
func init() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
logger.SetLogLevel("debug")
|
||||
}
|
||||
|
||||
// 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"
|
||||
func Debug(debug bool) {
|
||||
if debug {
|
||||
debugLevel = "debug"
|
||||
c.Debug = true
|
||||
logger.SetLogLevel("debug")
|
||||
} else {
|
||||
logger.SetLogLevel("warn")
|
||||
}
|
||||
SetDebugLevel(debugLevel)
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
IsSender bool
|
||||
SharedSecret string
|
||||
Debug bool
|
||||
RelayAddress string
|
||||
RelayPorts []string
|
||||
Stdout bool
|
||||
NoPrompt bool
|
||||
DisableLocal bool
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
Options Options
|
||||
Pake *pake.Pake
|
||||
Key crypt.Encryption
|
||||
ExternalIP, ExternalIPConnected string
|
||||
|
||||
// steps involved in forming relationship
|
||||
Step1ChannelSecured bool
|
||||
Step2FileInfoTransfered bool
|
||||
Step3RecipientRequestFile bool
|
||||
Step4FileTransfer bool
|
||||
Step5CloseChannels bool
|
||||
|
||||
// send / receive information of all files
|
||||
FilesToTransfer []FileInfo
|
||||
FilesToTransferCurrentNum int
|
||||
|
||||
// send / receive information of current file
|
||||
CurrentFile *os.File
|
||||
CurrentFileChunks []int64
|
||||
TotalSent int64
|
||||
TotalChunksTransfered int
|
||||
chunkMap map[uint64]struct{}
|
||||
|
||||
// tcp connections
|
||||
conn []*comm.Comm
|
||||
|
||||
bar *progressbar.ProgressBar
|
||||
spinner *spinner.Spinner
|
||||
machineID string
|
||||
firstSend bool
|
||||
|
||||
mutex *sync.Mutex
|
||||
quit chan bool
|
||||
}
|
||||
|
||||
type Chunk struct {
|
||||
Bytes []byte `json:"b,omitempty"`
|
||||
Location int64 `json:"l,omitempty"`
|
||||
}
|
||||
|
||||
type FileInfo struct {
|
||||
Name string `json:"n,omitempty"`
|
||||
FolderRemote string `json:"fr,omitempty"`
|
||||
FolderSource string `json:"fs,omitempty"`
|
||||
Hash []byte `json:"h,omitempty"`
|
||||
Size int64 `json:"s,omitempty"`
|
||||
ModTime time.Time `json:"m,omitempty"`
|
||||
IsCompressed bool `json:"c,omitempty"`
|
||||
IsEncrypted bool `json:"e,omitempty"`
|
||||
}
|
||||
|
||||
type RemoteFileRequest struct {
|
||||
CurrentFileChunks []int64
|
||||
FilesToTransferCurrentNum int
|
||||
}
|
||||
|
||||
type SenderInfo struct {
|
||||
MachineID string
|
||||
FilesToTransfer []FileInfo
|
||||
}
|
||||
|
||||
// New establishes a new connection for transfering files between two instances.
|
||||
func New(ops Options) (c *Client, err error) {
|
||||
c = new(Client)
|
||||
|
||||
// setup basic info
|
||||
c.Options = ops
|
||||
Debug(c.Options.Debug)
|
||||
log.Debugf("options: %+v", c.Options)
|
||||
|
||||
c.conn = make([]*comm.Comm, 16)
|
||||
|
||||
// use default key (no encryption, until PAKE succeeds)
|
||||
c.Key, err = crypt.New(nil, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// initialize pake
|
||||
if c.Options.IsSender {
|
||||
c.Pake, err = pake.Init([]byte(c.Options.SharedSecret), 1, elliptic.P521(), 1*time.Microsecond)
|
||||
} else {
|
||||
c.Pake, err = pake.Init([]byte(c.Options.SharedSecret), 0, elliptic.P521(), 1*time.Microsecond)
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.mutex = &sync.Mutex{}
|
||||
return
|
||||
}
|
||||
|
||||
func SetDebugLevel(debugLevel string) {
|
||||
logger.SetLogLevel(debugLevel)
|
||||
relay.DebugLevel = debugLevel
|
||||
zipper.DebugLevel = debugLevel
|
||||
// TransferOptions for sending
|
||||
type TransferOptions struct {
|
||||
PathToFiles []string
|
||||
KeepPathInRemote bool
|
||||
}
|
||||
|
||||
// Send will send the specified file
|
||||
func (c *Client) Send(options TransferOptions) (err error) {
|
||||
c.FilesToTransfer = make([]FileInfo, len(options.PathToFiles))
|
||||
totalFilesSize := int64(0)
|
||||
for i, pathToFile := range options.PathToFiles {
|
||||
var fstats os.FileInfo
|
||||
var fullPath string
|
||||
fullPath, err = filepath.Abs(pathToFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fullPath = filepath.Clean(fullPath)
|
||||
var folderName string
|
||||
folderName, _ = filepath.Split(fullPath)
|
||||
|
||||
fstats, err = os.Stat(fullPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.FilesToTransfer[i] = FileInfo{
|
||||
Name: fstats.Name(),
|
||||
FolderRemote: ".",
|
||||
FolderSource: folderName,
|
||||
Size: fstats.Size(),
|
||||
ModTime: fstats.ModTime(),
|
||||
}
|
||||
c.FilesToTransfer[i].Hash, err = utils.HashFile(fullPath)
|
||||
totalFilesSize += fstats.Size()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if options.KeepPathInRemote {
|
||||
var curFolder string
|
||||
curFolder, err = os.Getwd()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
curFolder, err = filepath.Abs(curFolder)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !strings.HasPrefix(folderName, curFolder) {
|
||||
err = fmt.Errorf("remote directory must be relative to current")
|
||||
return
|
||||
}
|
||||
c.FilesToTransfer[i].FolderRemote = strings.TrimPrefix(folderName, curFolder)
|
||||
c.FilesToTransfer[i].FolderRemote = filepath.ToSlash(c.FilesToTransfer[i].FolderRemote)
|
||||
c.FilesToTransfer[i].FolderRemote = strings.TrimPrefix(c.FilesToTransfer[i].FolderRemote, "/")
|
||||
if c.FilesToTransfer[i].FolderRemote == "" {
|
||||
c.FilesToTransfer[i].FolderRemote = "."
|
||||
}
|
||||
}
|
||||
log.Debugf("file %d info: %+v", i, c.FilesToTransfer[i])
|
||||
}
|
||||
fname := fmt.Sprintf("%d files", len(c.FilesToTransfer))
|
||||
if len(c.FilesToTransfer) == 1 {
|
||||
fname = fmt.Sprintf("'%s'", c.FilesToTransfer[0].Name)
|
||||
}
|
||||
machID, macIDerr := machineid.ID()
|
||||
if macIDerr != nil {
|
||||
log.Error(macIDerr)
|
||||
return
|
||||
}
|
||||
if len(machID) > 6 {
|
||||
machID = machID[:6]
|
||||
}
|
||||
c.machineID = machID
|
||||
fmt.Fprintf(os.Stderr, "Sending %s (%s)\n", fname, utils.ByteCountDecimal(totalFilesSize))
|
||||
fmt.Fprintf(os.Stderr, "Code is: %s\nOn the other computer run\n\ncroc %s\n", c.Options.SharedSecret, c.Options.SharedSecret)
|
||||
// // c.spinner.Suffix = " waiting for recipient..."
|
||||
// c.spinner.Start()
|
||||
// create channel for quitting
|
||||
// connect to the relay for messaging
|
||||
errchan := make(chan error, 1)
|
||||
|
||||
if !c.Options.DisableLocal {
|
||||
// setup the relay locally
|
||||
for _, port := range c.Options.RelayPorts {
|
||||
|
||||
go func(portStr string) {
|
||||
debugString := "warn"
|
||||
if c.Options.Debug {
|
||||
debugString = "debug"
|
||||
}
|
||||
err = tcp.Run(debugString, portStr, strings.Join(c.Options.RelayPorts[1:], ","))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}(port)
|
||||
}
|
||||
|
||||
// look for peers first
|
||||
go func() {
|
||||
discoveries, err := peerdiscovery.Discover(peerdiscovery.Settings{
|
||||
Limit: -1,
|
||||
Payload: []byte(c.Options.RelayPorts[0]),
|
||||
Delay: 10 * time.Millisecond,
|
||||
TimeLimit: 30 * time.Second,
|
||||
})
|
||||
log.Debugf("discoveries: %+v", discoveries)
|
||||
|
||||
if err == nil && len(discoveries) > 0 {
|
||||
log.Debug("using local server")
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
log.Debug("establishing connection")
|
||||
var banner string
|
||||
conn, banner, ipaddr, err := tcp.ConnectToTCPServer("localhost:"+c.Options.RelayPorts[0], c.Options.SharedSecret)
|
||||
log.Debugf("banner: %s", banner)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("could not connect to localhost:%s", c.Options.RelayPorts[0]))
|
||||
return
|
||||
}
|
||||
log.Debugf("connection established: %+v", conn)
|
||||
for {
|
||||
data, _ := conn.Receive()
|
||||
if bytes.Equal(data, []byte("handshake")) {
|
||||
break
|
||||
}
|
||||
}
|
||||
c.conn[0] = conn
|
||||
log.Debug("exchanged header message")
|
||||
c.Options.RelayAddress = "localhost"
|
||||
c.Options.RelayPorts = strings.Split(banner, ",")
|
||||
c.ExternalIP = ipaddr
|
||||
errchan <- c.transfer(options)
|
||||
}()
|
||||
}
|
||||
|
||||
go func() {
|
||||
log.Debug("establishing connection to %s", c.Options.RelayAddress)
|
||||
var banner string
|
||||
conn, banner, ipaddr, err := tcp.ConnectToTCPServer(c.Options.RelayAddress, c.Options.SharedSecret)
|
||||
log.Debugf("banner: %s", banner)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("could not connect to %s", c.Options.RelayAddress))
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
log.Debugf("connection established: %+v", conn)
|
||||
for {
|
||||
data, _ := conn.Receive()
|
||||
if bytes.Equal(data, []byte("handshake")) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
c.conn[0] = conn
|
||||
c.Options.RelayPorts = strings.Split(banner, ",")
|
||||
c.ExternalIP = ipaddr
|
||||
log.Debug("exchanged header message")
|
||||
errchan <- c.transfer(options)
|
||||
}()
|
||||
|
||||
return <-errchan
|
||||
}
|
||||
|
||||
// Receive will receive a file
|
||||
func (c *Client) Receive() (err error) {
|
||||
fmt.Fprintf(os.Stderr, "connecting...")
|
||||
// recipient will look for peers first
|
||||
// and continue if it doesn't find any within 100 ms
|
||||
if !c.Options.DisableLocal {
|
||||
log.Debug("attempt to discover peers")
|
||||
discoveries, err := peerdiscovery.Discover(peerdiscovery.Settings{
|
||||
Limit: 1,
|
||||
Payload: []byte("ok"),
|
||||
Delay: 10 * time.Millisecond,
|
||||
TimeLimit: 100 * time.Millisecond,
|
||||
})
|
||||
if err == nil && len(discoveries) > 0 {
|
||||
log.Debug("switching to local")
|
||||
c.Options.RelayAddress = fmt.Sprintf("%s:%s", discoveries[0].Address, discoveries[0].Payload)
|
||||
}
|
||||
log.Debugf("discoveries: %+v", discoveries)
|
||||
log.Debug("establishing connection")
|
||||
}
|
||||
var banner string
|
||||
c.conn[0], banner, c.ExternalIP, err = tcp.ConnectToTCPServer(c.Options.RelayAddress, c.Options.SharedSecret)
|
||||
log.Debugf("banner: %s", banner)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("could not connect to %s", c.Options.RelayAddress))
|
||||
return
|
||||
}
|
||||
log.Debugf("connection established: %+v", c.conn[0])
|
||||
c.conn[0].Send([]byte("handshake"))
|
||||
c.Options.RelayPorts = strings.Split(banner, ",")
|
||||
log.Debug("exchanged header message")
|
||||
fmt.Fprintf(os.Stderr, "\rsecuring channel...")
|
||||
return c.transfer(TransferOptions{})
|
||||
}
|
||||
|
||||
func (c *Client) transfer(options TransferOptions) (err error) {
|
||||
// connect to the server
|
||||
|
||||
// quit with c.quit <- true
|
||||
c.quit = make(chan bool)
|
||||
|
||||
// if recipient, initialize with sending pake information
|
||||
log.Debug("ready")
|
||||
if !c.Options.IsSender && !c.Step1ChannelSecured {
|
||||
err = message.Send(c.conn[0], c.Key, message.Message{
|
||||
Type: "pake",
|
||||
Bytes: c.Pake.Bytes(),
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// listen for incoming messages and process them
|
||||
for {
|
||||
var data []byte
|
||||
var done bool
|
||||
data, err = c.conn[0].Receive()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
done, err = c.processMessage(data)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if done {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if c.Options.Stdout && !c.Options.IsSender {
|
||||
pathToFile := path.Join(
|
||||
c.FilesToTransfer[c.FilesToTransferCurrentNum].FolderRemote,
|
||||
c.FilesToTransfer[c.FilesToTransferCurrentNum].Name,
|
||||
)
|
||||
os.Remove(pathToFile)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) processMessage(payload []byte) (done bool, err error) {
|
||||
m, err := message.Decode(c.Key, payload)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch m.Type {
|
||||
case "finished":
|
||||
err = message.Send(c.conn[0], c.Key, message.Message{
|
||||
Type: "finished",
|
||||
})
|
||||
done = true
|
||||
return
|
||||
case "pake":
|
||||
log.Debug("received pake payload")
|
||||
// if // c.spinner.Suffix != " performing PAKE..." {
|
||||
// // c.spinner.Stop()
|
||||
// // c.spinner.Suffix = " performing PAKE..."
|
||||
// // c.spinner.Start()
|
||||
// }
|
||||
notVerified := !c.Pake.IsVerified()
|
||||
err = c.Pake.Update(m.Bytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if (notVerified && c.Pake.IsVerified() && !c.Options.IsSender) || !c.Pake.IsVerified() {
|
||||
err = message.Send(c.conn[0], c.Key, message.Message{
|
||||
Type: "pake",
|
||||
Bytes: c.Pake.Bytes(),
|
||||
})
|
||||
}
|
||||
if c.Pake.IsVerified() {
|
||||
if c.Options.IsSender {
|
||||
log.Debug("generating salt")
|
||||
salt := make([]byte, 8)
|
||||
rand.Read(salt)
|
||||
err = message.Send(c.conn[0], c.Key, message.Message{
|
||||
Type: "salt",
|
||||
Bytes: salt,
|
||||
Message: c.ExternalIP,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// connects to the other ports of the server for transfer
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(c.Options.RelayPorts))
|
||||
for i := 0; i < len(c.Options.RelayPorts); i++ {
|
||||
log.Debugf("port: [%s]", c.Options.RelayPorts[i])
|
||||
go func(j int) {
|
||||
defer wg.Done()
|
||||
server := fmt.Sprintf("%s:%s", strings.Split(c.Options.RelayAddress, ":")[0], c.Options.RelayPorts[j])
|
||||
log.Debugf("connecting to %s", server)
|
||||
c.conn[j+1], _, _, err = tcp.ConnectToTCPServer(
|
||||
server,
|
||||
fmt.Sprintf("%s-%d", utils.SHA256(c.Options.SharedSecret)[:7], j),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
log.Debugf("connected to %s", server)
|
||||
if !c.Options.IsSender {
|
||||
go c.receiveData(j)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
case "salt":
|
||||
log.Debug("received salt")
|
||||
if !c.Options.IsSender {
|
||||
log.Debug("sending salt back")
|
||||
err = message.Send(c.conn[0], c.Key, message.Message{
|
||||
Type: "salt",
|
||||
Bytes: m.Bytes,
|
||||
Message: c.ExternalIP,
|
||||
})
|
||||
}
|
||||
log.Debugf("session key is verified, generating encryption with salt: %x", m.Bytes)
|
||||
key, err := c.Pake.SessionKey()
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
c.Key, err = crypt.New(key, m.Bytes)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
c.ExternalIPConnected = m.Message
|
||||
log.Debugf("connected as %s -> %s", c.ExternalIP, c.ExternalIPConnected)
|
||||
c.Step1ChannelSecured = true
|
||||
case "error":
|
||||
// c.spinner.Stop()
|
||||
fmt.Print("\r")
|
||||
err = fmt.Errorf("peer error: %s", m.Message)
|
||||
return true, err
|
||||
case "fileinfo":
|
||||
var senderInfo SenderInfo
|
||||
err = json.Unmarshal(m.Bytes, &senderInfo)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
c.FilesToTransfer = senderInfo.FilesToTransfer
|
||||
fname := fmt.Sprintf("%d files", len(c.FilesToTransfer))
|
||||
if len(c.FilesToTransfer) == 1 {
|
||||
fname = fmt.Sprintf("'%s'", c.FilesToTransfer[0].Name)
|
||||
}
|
||||
totalSize := int64(0)
|
||||
for _, fi := range c.FilesToTransfer {
|
||||
totalSize += fi.Size
|
||||
}
|
||||
// c.spinner.Stop()
|
||||
if !c.Options.NoPrompt {
|
||||
fmt.Fprintf(os.Stderr, "\rAccept %s (%s)? (y/n) ", fname, utils.ByteCountDecimal(totalSize))
|
||||
if strings.ToLower(strings.TrimSpace(utils.GetInput(""))) != "y" {
|
||||
err = message.Send(c.conn[0], c.Key, message.Message{
|
||||
Type: "error",
|
||||
Message: "refusing files",
|
||||
})
|
||||
return true, fmt.Errorf("refused files")
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "\rReceiving %s (%s) \n", fname, utils.ByteCountDecimal(totalSize))
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\nReceiving (<-%s)\n", c.ExternalIPConnected)
|
||||
|
||||
log.Debug(c.FilesToTransfer)
|
||||
c.Step2FileInfoTransfered = true
|
||||
case "recipientready":
|
||||
var remoteFile RemoteFileRequest
|
||||
err = json.Unmarshal(m.Bytes, &remoteFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.FilesToTransferCurrentNum = remoteFile.FilesToTransferCurrentNum
|
||||
c.CurrentFileChunks = remoteFile.CurrentFileChunks
|
||||
log.Debugf("current file chunks: %+v", c.CurrentFileChunks)
|
||||
c.chunkMap = make(map[uint64]struct{})
|
||||
for _, chunk := range c.CurrentFileChunks {
|
||||
c.chunkMap[uint64(chunk)] = struct{}{}
|
||||
}
|
||||
c.Step3RecipientRequestFile = true
|
||||
case "close-sender":
|
||||
c.bar.Finish()
|
||||
log.Debug("close-sender received...")
|
||||
c.Step4FileTransfer = false
|
||||
c.Step3RecipientRequestFile = false
|
||||
log.Debug("sending close-recipient")
|
||||
err = message.Send(c.conn[0], c.Key, message.Message{
|
||||
Type: "close-recipient",
|
||||
})
|
||||
case "close-recipient":
|
||||
c.Step4FileTransfer = false
|
||||
c.Step3RecipientRequestFile = false
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = c.updateState()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) updateState() (err error) {
|
||||
if c.Options.IsSender && c.Step1ChannelSecured && !c.Step2FileInfoTransfered {
|
||||
var b []byte
|
||||
b, err = json.Marshal(SenderInfo{
|
||||
MachineID: c.machineID,
|
||||
FilesToTransfer: c.FilesToTransfer,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
err = message.Send(c.conn[0], c.Key, message.Message{
|
||||
Type: "fileinfo",
|
||||
Bytes: b,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.Step2FileInfoTransfered = true
|
||||
}
|
||||
if !c.Options.IsSender && c.Step2FileInfoTransfered && !c.Step3RecipientRequestFile {
|
||||
// find the next file to transfer and send that number
|
||||
// if the files are the same size, then look for missing chunks
|
||||
finished := true
|
||||
for i, fileInfo := range c.FilesToTransfer {
|
||||
if i < c.FilesToTransferCurrentNum {
|
||||
continue
|
||||
}
|
||||
fileHash, errHash := utils.HashFile(path.Join(fileInfo.FolderRemote, fileInfo.Name))
|
||||
if errHash != nil || !bytes.Equal(fileHash, fileInfo.Hash) {
|
||||
if !bytes.Equal(fileHash, fileInfo.Hash) {
|
||||
log.Debugf("hashes are not equal %x != %x", fileHash, fileInfo.Hash)
|
||||
}
|
||||
finished = false
|
||||
c.FilesToTransferCurrentNum = i
|
||||
break
|
||||
}
|
||||
// TODO: print out something about this file already existing
|
||||
}
|
||||
if finished {
|
||||
// TODO: do the last finishing stuff
|
||||
log.Debug("finished")
|
||||
err = message.Send(c.conn[0], c.Key, message.Message{
|
||||
Type: "finished",
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// start initiating the process to receive a new file
|
||||
log.Debugf("working on file %d", c.FilesToTransferCurrentNum)
|
||||
|
||||
// recipient sets the file
|
||||
pathToFile := path.Join(
|
||||
c.FilesToTransfer[c.FilesToTransferCurrentNum].FolderRemote,
|
||||
c.FilesToTransfer[c.FilesToTransferCurrentNum].Name,
|
||||
)
|
||||
folderForFile, _ := filepath.Split(pathToFile)
|
||||
os.MkdirAll(folderForFile, os.ModePerm)
|
||||
var errOpen error
|
||||
c.CurrentFile, errOpen = os.OpenFile(
|
||||
pathToFile,
|
||||
os.O_WRONLY, 0666)
|
||||
truncate := false
|
||||
c.CurrentFileChunks = []int64{}
|
||||
if errOpen == nil {
|
||||
stat, _ := c.CurrentFile.Stat()
|
||||
truncate = stat.Size() != c.FilesToTransfer[c.FilesToTransferCurrentNum].Size
|
||||
if truncate == false {
|
||||
// recipient requests the file and chunks (if empty, then should receive all chunks)
|
||||
// TODO: determine the missing chunks
|
||||
c.CurrentFileChunks = utils.MissingChunks(
|
||||
pathToFile,
|
||||
c.FilesToTransfer[c.FilesToTransferCurrentNum].Size,
|
||||
models.TCP_BUFFER_SIZE/2,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
c.CurrentFile, errOpen = os.Create(pathToFile)
|
||||
if errOpen != nil {
|
||||
errOpen = errors.Wrap(errOpen, "could not create "+pathToFile)
|
||||
log.Error(errOpen)
|
||||
return errOpen
|
||||
}
|
||||
truncate = true
|
||||
}
|
||||
if truncate {
|
||||
err := c.CurrentFile.Truncate(c.FilesToTransfer[c.FilesToTransferCurrentNum].Size)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "could not truncate "+pathToFile)
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// setup the progressbar
|
||||
c.setBar()
|
||||
c.TotalSent = 0
|
||||
bRequest, _ := json.Marshal(RemoteFileRequest{
|
||||
CurrentFileChunks: c.CurrentFileChunks,
|
||||
FilesToTransferCurrentNum: c.FilesToTransferCurrentNum,
|
||||
})
|
||||
err = message.Send(c.conn[0], c.Key, message.Message{
|
||||
Type: "recipientready",
|
||||
Bytes: bRequest,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.Step3RecipientRequestFile = true
|
||||
}
|
||||
if c.Options.IsSender && c.Step3RecipientRequestFile && !c.Step4FileTransfer {
|
||||
log.Debug("start sending data!")
|
||||
if !c.firstSend {
|
||||
fmt.Fprintf(os.Stderr, "\nSending (->%s)\n", c.ExternalIPConnected)
|
||||
c.firstSend = true
|
||||
}
|
||||
c.Step4FileTransfer = true
|
||||
// setup the progressbar
|
||||
c.setBar()
|
||||
c.TotalSent = 0
|
||||
for i := 0; i < len(c.Options.RelayPorts); i++ {
|
||||
go c.sendData(i)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) setBar() {
|
||||
description := fmt.Sprintf("%-28s", c.FilesToTransfer[c.FilesToTransferCurrentNum].Name)
|
||||
if len(c.FilesToTransfer) == 1 {
|
||||
description = c.FilesToTransfer[c.FilesToTransferCurrentNum].Name
|
||||
}
|
||||
c.bar = progressbar.NewOptions64(
|
||||
c.FilesToTransfer[c.FilesToTransferCurrentNum].Size,
|
||||
progressbar.OptionOnCompletion(func() {
|
||||
fmt.Fprintf(os.Stderr, " ✔️\n")
|
||||
}),
|
||||
progressbar.OptionSetWidth(20),
|
||||
progressbar.OptionSetDescription(description),
|
||||
progressbar.OptionSetRenderBlankState(true),
|
||||
progressbar.OptionSetBytes64(c.FilesToTransfer[c.FilesToTransferCurrentNum].Size),
|
||||
progressbar.OptionSetWriter(os.Stderr),
|
||||
progressbar.OptionThrottle(100*time.Millisecond),
|
||||
)
|
||||
c.bar.Add(len(c.CurrentFileChunks) * models.TCP_BUFFER_SIZE / 2)
|
||||
}
|
||||
|
||||
func (c *Client) receiveData(i int) {
|
||||
for {
|
||||
log.Debug("waiting for data")
|
||||
data, err := c.conn[i+1].Receive()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
data, err = c.Key.Decrypt(data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
data = compress.Decompress(data)
|
||||
|
||||
// get position
|
||||
var position uint64
|
||||
rbuf := bytes.NewReader(data[:8])
|
||||
err = binary.Read(rbuf, binary.LittleEndian, &position)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
positionInt64 := int64(position)
|
||||
|
||||
c.mutex.Lock()
|
||||
_, err = c.CurrentFile.WriteAt(data[8:], positionInt64)
|
||||
c.mutex.Unlock()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
c.bar.Add(len(data[8:]))
|
||||
c.TotalSent += int64(len(data[8:]))
|
||||
c.TotalChunksTransfered++
|
||||
log.Debugf("block: %+v", positionInt64)
|
||||
if c.TotalChunksTransfered == len(c.CurrentFileChunks) || c.TotalSent == c.FilesToTransfer[c.FilesToTransferCurrentNum].Size {
|
||||
log.Debug("finished receiving!")
|
||||
c.CurrentFile.Close()
|
||||
if c.Options.Stdout {
|
||||
pathToFile := path.Join(
|
||||
c.FilesToTransfer[c.FilesToTransferCurrentNum].FolderRemote,
|
||||
c.FilesToTransfer[c.FilesToTransferCurrentNum].Name,
|
||||
)
|
||||
b, _ := ioutil.ReadFile(pathToFile)
|
||||
fmt.Print(string(b))
|
||||
}
|
||||
log.Debug("sending close-sender")
|
||||
err = message.Send(c.conn[0], c.Key, message.Message{
|
||||
Type: "close-sender",
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) sendData(i int) {
|
||||
defer func() {
|
||||
log.Debugf("finished with %d", i)
|
||||
}()
|
||||
pathToFile := path.Join(
|
||||
c.FilesToTransfer[c.FilesToTransferCurrentNum].FolderSource,
|
||||
c.FilesToTransfer[c.FilesToTransferCurrentNum].Name,
|
||||
)
|
||||
log.Debugf("opening %s to read", pathToFile)
|
||||
f, err := os.Open(pathToFile)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
pos := uint64(0)
|
||||
curi := float64(0)
|
||||
for {
|
||||
// Read file
|
||||
data := make([]byte, models.TCP_BUFFER_SIZE/2)
|
||||
n, err := f.Read(data)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if math.Mod(curi, float64(len(c.Options.RelayPorts))) == float64(i) {
|
||||
// check to see if this is a chunk that the recipient wants
|
||||
usableChunk := true
|
||||
c.mutex.Lock()
|
||||
if len(c.chunkMap) != 0 {
|
||||
if _, ok := c.chunkMap[pos]; !ok {
|
||||
usableChunk = false
|
||||
} else {
|
||||
delete(c.chunkMap, pos)
|
||||
}
|
||||
}
|
||||
c.mutex.Unlock()
|
||||
if usableChunk {
|
||||
// log.Debugf("sending chunk %d", pos)
|
||||
posByte := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(posByte, pos)
|
||||
|
||||
dataToSend, err := c.Key.Encrypt(
|
||||
compress.Compress(
|
||||
append(posByte, data[:n]...),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = c.conn[i+1].Send(dataToSend)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
c.bar.Add(n)
|
||||
c.TotalSent += int64(n)
|
||||
// time.Sleep(100 * time.Millisecond)
|
||||
} else {
|
||||
// log.Debugf("skipping chunk %d", pos)
|
||||
}
|
||||
}
|
||||
|
||||
curi++
|
||||
pos += uint64(n)
|
||||
}
|
||||
|
||||
time.Sleep(10 * time.Second)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,81 +1,67 @@
|
|||
package croc
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/schollz/croc/src/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
log "github.com/cihub/seelog"
|
||||
"github.com/schollz/croc/v6/src/tcp"
|
||||
)
|
||||
|
||||
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
|
||||
func TestCroc(t *testing.T) {
|
||||
defer log.Flush()
|
||||
|
||||
go tcp.Run("debug", "8081", "8082,8083,8084,8085")
|
||||
go tcp.Run("debug", "8082")
|
||||
go tcp.Run("debug", "8083")
|
||||
go tcp.Run("debug", "8084")
|
||||
go tcp.Run("debug", "8085")
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
|
||||
log.Flush()
|
||||
log.Debug("setting up sender")
|
||||
log.Flush()
|
||||
sender, err := New(Options{
|
||||
IsSender: true,
|
||||
SharedSecret: "test",
|
||||
Debug: true,
|
||||
RelayAddress: "localhost:8081",
|
||||
Stdout: false,
|
||||
NoPrompt: true,
|
||||
DisableLocal: true,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fname := generateRandomFile(megabytes)
|
||||
|
||||
log.Debug("setting up receiver")
|
||||
receiver, err := New(Options{
|
||||
IsSender: false,
|
||||
SharedSecret: "test",
|
||||
Debug: true,
|
||||
RelayAddress: "localhost:8081",
|
||||
Stdout: false,
|
||||
NoPrompt: true,
|
||||
DisableLocal: true,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
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))
|
||||
sender.Send(TransferOptions{
|
||||
PathToFiles: []string{"../../README.md"},
|
||||
})
|
||||
wg.Done()
|
||||
}()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
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))
|
||||
receiver.Receive()
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
os.Chdir("..")
|
||||
os.RemoveAll("test")
|
||||
os.Remove(fname)
|
||||
fmt.Printf("\n-----\n%2.1f MB/s\n----\n", durationPerMegabyte)
|
||||
}
|
||||
|
||||
func TestSendReceivePubWebsockets(t *testing.T) {
|
||||
sendAndReceive(t, 1, false)
|
||||
}
|
||||
|
||||
func TestSendReceivePubTCP(t *testing.T) {
|
||||
sendAndReceive(t, 2, false)
|
||||
}
|
||||
|
||||
func TestSendReceiveLocalWebsockets(t *testing.T) {
|
||||
sendAndReceive(t, 1, true)
|
||||
}
|
||||
|
||||
// func TestSendReceiveLocalTCP(t *testing.T) {
|
||||
// sendAndReceive(t, 2, true)
|
||||
// }
|
||||
|
||||
func generateRandomFile(megabytes int) (fname string) {
|
||||
// generate a random file
|
||||
bigBuff := make([]byte, 1024*1024*megabytes)
|
||||
rand.Read(bigBuff)
|
||||
fname = fmt.Sprintf("%dmb.file", megabytes)
|
||||
ioutil.WriteFile(fname, bigBuff, 0666)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
package croc
|
||||
|
||||
type WebSocketMessage struct {
|
||||
messageType int
|
||||
message []byte
|
||||
err error
|
||||
}
|
|
@ -1,624 +0,0 @@
|
|||
package croc
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/cihub/seelog"
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/schollz/croc/src/comm"
|
||||
"github.com/schollz/croc/src/compress"
|
||||
"github.com/schollz/croc/src/crypt"
|
||||
"github.com/schollz/croc/src/logger"
|
||||
"github.com/schollz/croc/src/models"
|
||||
"github.com/schollz/croc/src/utils"
|
||||
"github.com/schollz/croc/src/zipper"
|
||||
"github.com/schollz/pake"
|
||||
"github.com/schollz/progressbar/v2"
|
||||
"github.com/schollz/spinner"
|
||||
)
|
||||
|
||||
var DebugLevel string
|
||||
|
||||
// Receive is the async operation to receive a file
|
||||
func (cr *Croc) startRecipient(forceSend int, serverAddress string, tcpPorts []string, isLocal bool, done chan error, c *websocket.Conn, codephrase string, noPrompt bool, useStdout bool) {
|
||||
logger.SetLogLevel(DebugLevel)
|
||||
err := cr.receive(forceSend, serverAddress, tcpPorts, isLocal, c, codephrase, noPrompt, useStdout)
|
||||
if err != nil && strings.HasPrefix(err.Error(), "websocket: close 100") {
|
||||
err = nil
|
||||
}
|
||||
done <- err
|
||||
}
|
||||
|
||||
func (cr *Croc) receive(forceSend int, serverAddress string, tcpPorts []string, isLocal bool, c *websocket.Conn, codephrase string, noPrompt bool, useStdout bool) (err error) {
|
||||
var sessionKey []byte
|
||||
var transferTime time.Duration
|
||||
var hash256 []byte
|
||||
var progressFile string
|
||||
var resumeFile bool
|
||||
var tcpConnections []comm.Comm
|
||||
var Q *pake.Pake
|
||||
|
||||
dataChan := make(chan []byte, 1024*1024)
|
||||
isConnectedIfUsingTCP := make(chan bool)
|
||||
blocks := []string{}
|
||||
|
||||
useWebsockets := true
|
||||
switch forceSend {
|
||||
case 0:
|
||||
if !isLocal {
|
||||
useWebsockets = false
|
||||
}
|
||||
case 1:
|
||||
useWebsockets = true
|
||||
case 2:
|
||||
useWebsockets = false
|
||||
}
|
||||
|
||||
// start a spinner
|
||||
spin := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
|
||||
spin.Writer = os.Stderr
|
||||
spin.Suffix = " connecting..."
|
||||
cr.StateString = "Connecting as recipient..."
|
||||
spin.Start()
|
||||
defer spin.Stop()
|
||||
|
||||
// both parties should have a weak key
|
||||
pw := []byte(codephrase)
|
||||
|
||||
// start the reader
|
||||
websocketMessages := make(chan WebSocketMessage, 1024)
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Debugf("recovered from %s", r)
|
||||
}
|
||||
}()
|
||||
for {
|
||||
messageType, message, err := c.ReadMessage()
|
||||
websocketMessages <- WebSocketMessage{messageType, message, err}
|
||||
}
|
||||
}()
|
||||
|
||||
step := 0
|
||||
for {
|
||||
var websocketMessageMain WebSocketMessage
|
||||
// websocketMessageMain = <-websocketMessages
|
||||
timeWaitingForMessage := time.Now()
|
||||
for {
|
||||
done := false
|
||||
select {
|
||||
case websocketMessageMain = <-websocketMessages:
|
||||
done = true
|
||||
default:
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
if done {
|
||||
break
|
||||
}
|
||||
if time.Since(timeWaitingForMessage).Seconds() > 3 && step == 0 {
|
||||
return fmt.Errorf("You are trying to receive a file with no sender.")
|
||||
}
|
||||
}
|
||||
|
||||
messageType := websocketMessageMain.messageType
|
||||
message := websocketMessageMain.message
|
||||
err := websocketMessageMain.err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if messageType == websocket.PongMessage || messageType == websocket.PingMessage {
|
||||
continue
|
||||
}
|
||||
if messageType == websocket.TextMessage && bytes.Equal(message, []byte("interrupt")) {
|
||||
return errors.New("\rinterrupted by other party")
|
||||
}
|
||||
|
||||
log.Debugf("got %d: %s", messageType, message)
|
||||
switch step {
|
||||
case 0:
|
||||
spin.Stop()
|
||||
spin.Suffix = " performing PAKE..."
|
||||
cr.StateString = "Performing PAKE..."
|
||||
spin.Start()
|
||||
// sender has initiated, sends their initial data
|
||||
var initialData models.Initial
|
||||
err = json.Unmarshal(message, &initialData)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "incompatible versions of croc")
|
||||
return err
|
||||
}
|
||||
cr.OtherIP = initialData.IPAddress
|
||||
log.Debugf("sender IP: %s", cr.OtherIP)
|
||||
|
||||
// check whether the version strings are compatible
|
||||
versionStringsOther := strings.Split(initialData.VersionString, ".")
|
||||
versionStringsSelf := strings.Split(cr.Version, ".")
|
||||
if len(versionStringsOther) == 3 && len(versionStringsSelf) == 3 {
|
||||
if versionStringsSelf[0] != versionStringsOther[0] || versionStringsSelf[1] != versionStringsOther[1] {
|
||||
return fmt.Errorf("version sender %s is not compatible with recipient %s", cr.Version, initialData.VersionString)
|
||||
}
|
||||
}
|
||||
|
||||
// initialize the PAKE with the curve sent from the sender
|
||||
Q, err = pake.InitCurve(pw, 1, initialData.CurveType, 1*time.Millisecond)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "incompatible curve type")
|
||||
return err
|
||||
}
|
||||
|
||||
// recipient begins by sending back initial data to sender
|
||||
ip := ""
|
||||
if isLocal {
|
||||
ip = utils.LocalIP()
|
||||
} else {
|
||||
ip, _ = utils.PublicIP()
|
||||
}
|
||||
initialData.VersionString = cr.Version
|
||||
initialData.IPAddress = ip
|
||||
bInitialData, _ := json.Marshal(initialData)
|
||||
c.WriteMessage(websocket.BinaryMessage, bInitialData)
|
||||
case 1:
|
||||
// Q receives u
|
||||
log.Debugf("[%d] Q computes k, sends H(k), v back to P", step)
|
||||
if err := Q.Update(message); err != nil {
|
||||
return fmt.Errorf("Recipient is using wrong code phrase.")
|
||||
}
|
||||
|
||||
// Q has the session key now, but we will still check if its valid
|
||||
sessionKey, err = Q.SessionKey()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Recipient is using wrong code phrase.")
|
||||
}
|
||||
log.Debugf("%x\n", sessionKey)
|
||||
|
||||
// initialize TCP connections if using (possible, but unlikely, race condition)
|
||||
go func() {
|
||||
log.Debug("initializing TCP connections")
|
||||
if !useWebsockets {
|
||||
log.Debugf("connecting to server")
|
||||
tcpConnections = make([]comm.Comm, len(tcpPorts))
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(tcpPorts))
|
||||
for i, tcpPort := range tcpPorts {
|
||||
go func(i int, tcpPort string) {
|
||||
defer wg.Done()
|
||||
log.Debugf("connecting to %d", i)
|
||||
var message string
|
||||
tcpConnections[i], message, err = connectToTCPServer(utils.SHA256(fmt.Sprintf("%d%x", i, sessionKey)), serverAddress+":"+tcpPort)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
if message != "recipient" {
|
||||
log.Errorf("got wrong message: %s", message)
|
||||
}
|
||||
}(i, tcpPort)
|
||||
}
|
||||
wg.Wait()
|
||||
log.Debugf("fully connected")
|
||||
}
|
||||
isConnectedIfUsingTCP <- true
|
||||
}()
|
||||
|
||||
c.WriteMessage(websocket.BinaryMessage, Q.Bytes())
|
||||
case 2:
|
||||
log.Debugf("[%d] Q recieves H(k) from P", step)
|
||||
// check if everything is still kosher with our computed session key
|
||||
if err := Q.Update(message); err != nil {
|
||||
log.Debug(err)
|
||||
return fmt.Errorf("Recipient is using wrong code phrase.")
|
||||
}
|
||||
c.WriteMessage(websocket.BinaryMessage, []byte("ready"))
|
||||
case 3:
|
||||
spin.Stop()
|
||||
cr.StateString = "Recieving file info..."
|
||||
|
||||
// unmarshal the file info
|
||||
log.Debugf("[%d] recieve file info", step)
|
||||
// do decryption on the file stats
|
||||
enc, err := crypt.FromBytes(message)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
decryptedFileData, err := enc.Decrypt(sessionKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal(decryptedFileData, &cr.FileInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("got file stats: %+v", cr.FileInfo)
|
||||
|
||||
// determine if the file is resuming or not
|
||||
progressFile = fmt.Sprintf("%s.progress", cr.FileInfo.SentName)
|
||||
overwritingOrReceiving := "Receiving"
|
||||
if utils.Exists(cr.FileInfo.Name) || utils.Exists(cr.FileInfo.SentName) {
|
||||
overwritingOrReceiving = "Overwriting"
|
||||
if utils.Exists(progressFile) {
|
||||
overwritingOrReceiving = "Resume receiving"
|
||||
resumeFile = true
|
||||
}
|
||||
}
|
||||
|
||||
// send blocks
|
||||
if resumeFile {
|
||||
fileWithBlocks, _ := os.Open(progressFile)
|
||||
scanner := bufio.NewScanner(fileWithBlocks)
|
||||
for scanner.Scan() {
|
||||
blocks = append(blocks, strings.TrimSpace(scanner.Text()))
|
||||
}
|
||||
fileWithBlocks.Close()
|
||||
}
|
||||
blocksBytes, _ := json.Marshal(blocks)
|
||||
// encrypt the block data and send
|
||||
encblockBytes := crypt.Encrypt(blocksBytes, sessionKey)
|
||||
|
||||
// wait for TCP connections if using them
|
||||
_ = <-isConnectedIfUsingTCP
|
||||
c.WriteMessage(websocket.BinaryMessage, encblockBytes.Bytes())
|
||||
|
||||
// prompt user about the file
|
||||
fileOrFolder := "file"
|
||||
if cr.FileInfo.IsDir {
|
||||
fileOrFolder = "folder"
|
||||
}
|
||||
cr.WindowReceivingString = fmt.Sprintf("%s %s (%s) into: %s",
|
||||
overwritingOrReceiving,
|
||||
fileOrFolder,
|
||||
humanize.Bytes(uint64(cr.FileInfo.Size)),
|
||||
cr.FileInfo.Name,
|
||||
)
|
||||
fmt.Fprintf(os.Stderr, "\r%s\n",
|
||||
cr.WindowReceivingString,
|
||||
)
|
||||
if !noPrompt {
|
||||
if "y" != utils.GetInput("ok? (y/N): ") {
|
||||
fmt.Fprintf(os.Stderr, "Cancelling request")
|
||||
c.WriteMessage(websocket.BinaryMessage, []byte("no"))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if cr.WindowRecipientPrompt {
|
||||
// wait until it switches to false
|
||||
// the window should then set WindowRecipientAccept
|
||||
for {
|
||||
if !cr.WindowRecipientPrompt {
|
||||
if cr.WindowRecipientAccept {
|
||||
break
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Cancelling request")
|
||||
c.WriteMessage(websocket.BinaryMessage, []byte("no"))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
// await file
|
||||
// erase file if overwriting
|
||||
if overwritingOrReceiving == "Overwriting" {
|
||||
os.Remove(cr.FileInfo.SentName)
|
||||
}
|
||||
var f *os.File
|
||||
if utils.Exists(cr.FileInfo.SentName) && resumeFile {
|
||||
if !useWebsockets {
|
||||
f, err = os.OpenFile(cr.FileInfo.SentName, os.O_WRONLY, 0644)
|
||||
} else {
|
||||
f, err = os.OpenFile(cr.FileInfo.SentName, os.O_APPEND|os.O_WRONLY, 0644)
|
||||
}
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
f, err = os.Create(cr.FileInfo.SentName)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
if !useWebsockets {
|
||||
if err = f.Truncate(cr.FileInfo.Size); err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blockSize := 0
|
||||
if useWebsockets {
|
||||
blockSize = models.WEBSOCKET_BUFFER_SIZE / 8
|
||||
} else {
|
||||
blockSize = models.TCP_BUFFER_SIZE / 2
|
||||
}
|
||||
// start the ui for pgoress
|
||||
cr.StateString = "Recieving file..."
|
||||
bytesWritten := 0
|
||||
fmt.Fprintf(os.Stderr, "\nReceiving (<-%s)...\n", cr.OtherIP)
|
||||
cr.Bar = progressbar.NewOptions(
|
||||
int(cr.FileInfo.Size),
|
||||
progressbar.OptionSetRenderBlankState(true),
|
||||
progressbar.OptionSetBytes(int(cr.FileInfo.Size)),
|
||||
progressbar.OptionSetWriter(os.Stderr),
|
||||
progressbar.OptionThrottle(1/60*time.Second),
|
||||
)
|
||||
cr.Bar.Add((len(blocks) * blockSize))
|
||||
finished := make(chan bool)
|
||||
|
||||
go func(finished chan bool, dataChan chan []byte) (err error) {
|
||||
// remove previous progress
|
||||
var fProgress *os.File
|
||||
var progressErr error
|
||||
if resumeFile {
|
||||
fProgress, progressErr = os.OpenFile(progressFile, os.O_APPEND|os.O_WRONLY, 0644)
|
||||
bytesWritten = len(blocks) * blockSize
|
||||
} else {
|
||||
os.Remove(progressFile)
|
||||
fProgress, progressErr = os.Create(progressFile)
|
||||
}
|
||||
if progressErr != nil {
|
||||
panic(progressErr)
|
||||
}
|
||||
defer fProgress.Close()
|
||||
|
||||
blocksWritten := 0.0
|
||||
blocksToWrite := float64(cr.FileInfo.Size)
|
||||
if useWebsockets {
|
||||
blocksToWrite = blocksToWrite/float64(models.WEBSOCKET_BUFFER_SIZE/8) - float64(len(blocks))
|
||||
} else {
|
||||
blocksToWrite = blocksToWrite/float64(models.TCP_BUFFER_SIZE/2) - float64(len(blocks))
|
||||
}
|
||||
for {
|
||||
message := <-dataChan
|
||||
// do decryption
|
||||
var enc crypt.Encryption
|
||||
err = json.Unmarshal(message, &enc)
|
||||
if err != nil {
|
||||
// log.Errorf("%s: [%s] [%+v] (%d/%d) %+v", err.Error(), message, message, len(message), numBytes, bs)
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
decrypted, err := enc.Decrypt(sessionKey, !cr.FileInfo.IsEncrypted)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// get location if TCP
|
||||
var locationToWrite int
|
||||
if !useWebsockets {
|
||||
pieces := bytes.SplitN(decrypted, []byte("-"), 2)
|
||||
decrypted = pieces[1]
|
||||
locationToWrite, _ = strconv.Atoi(string(pieces[0]))
|
||||
}
|
||||
|
||||
// do decompression
|
||||
if cr.FileInfo.IsCompressed && !cr.FileInfo.IsDir {
|
||||
decrypted = compress.Decompress(decrypted)
|
||||
}
|
||||
|
||||
var n int
|
||||
if !useWebsockets {
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
n, err = f.WriteAt(decrypted, int64(locationToWrite))
|
||||
fProgress.WriteString(fmt.Sprintf("%d\n", locationToWrite))
|
||||
log.Debugf("wrote %d bytes to location %d (%2.0f/%2.0f)", n, locationToWrite, blocksWritten, blocksToWrite)
|
||||
} else {
|
||||
// write to file
|
||||
n, err = f.Write(decrypted)
|
||||
log.Debugf("wrote %d bytes to location %d (%2.0f/%2.0f)", n, bytesWritten, blocksWritten, blocksToWrite)
|
||||
fProgress.WriteString(fmt.Sprintf("%d\n", bytesWritten))
|
||||
}
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// update the bytes written
|
||||
bytesWritten += n
|
||||
blocksWritten += 1.0
|
||||
// update the progress bar
|
||||
cr.Bar.Add(n)
|
||||
if int64(bytesWritten) == cr.FileInfo.Size || blocksWritten >= blocksToWrite {
|
||||
log.Debug("finished", int64(bytesWritten), cr.FileInfo.Size, blocksWritten, blocksToWrite)
|
||||
break
|
||||
}
|
||||
}
|
||||
finished <- true
|
||||
return
|
||||
}(finished, dataChan)
|
||||
|
||||
log.Debug("telling sender i'm ready")
|
||||
c.WriteMessage(websocket.BinaryMessage, []byte("ready"))
|
||||
|
||||
startTime := time.Now()
|
||||
if useWebsockets {
|
||||
for {
|
||||
// read from websockets
|
||||
websocketMessageData := <-websocketMessages
|
||||
if bytes.HasPrefix(websocketMessageData.message, []byte("error")) {
|
||||
return fmt.Errorf("%s", websocketMessageData.message)
|
||||
}
|
||||
if websocketMessageData.messageType != websocket.BinaryMessage {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
if bytes.Equal(websocketMessageData.message, []byte("magic")) {
|
||||
log.Debug("got magic")
|
||||
break
|
||||
}
|
||||
dataChan <- websocketMessageData.message
|
||||
}
|
||||
} else {
|
||||
log.Debugf("starting listening with tcp with %d connections", len(tcpConnections))
|
||||
|
||||
// check to see if any messages are sent
|
||||
stopMessageSignal := make(chan bool, 1)
|
||||
errorsDuringTransfer := make(chan error, 24)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case sig := <-stopMessageSignal:
|
||||
errorsDuringTransfer <- nil
|
||||
log.Debugf("got message signal: %+v", sig)
|
||||
return
|
||||
case wsMessage := <-websocketMessages:
|
||||
log.Debugf("got message: %s", wsMessage.message)
|
||||
if bytes.HasPrefix(wsMessage.message, []byte("error")) {
|
||||
log.Debug("stopping transfer")
|
||||
for i := 0; i < len(tcpConnections)+1; i++ {
|
||||
errorsDuringTransfer <- fmt.Errorf("%s", wsMessage.message)
|
||||
}
|
||||
return
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// using TCP
|
||||
go func() {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(tcpConnections))
|
||||
for i := range tcpConnections {
|
||||
defer func(i int) {
|
||||
log.Debugf("closing connection %d", i)
|
||||
tcpConnections[i].Close()
|
||||
}(i)
|
||||
go func(wg *sync.WaitGroup, j int) {
|
||||
defer wg.Done()
|
||||
for {
|
||||
select {
|
||||
case _ = <-errorsDuringTransfer:
|
||||
log.Debugf("%d got stop", i)
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
log.Debugf("waiting to read on %d", j)
|
||||
// read from TCP connection
|
||||
message, _, _, err := tcpConnections[j].Read()
|
||||
// log.Debugf("message: %s", message)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if bytes.Equal(message, []byte("magic")) {
|
||||
log.Debugf("%d got magic, leaving", j)
|
||||
return
|
||||
}
|
||||
dataChan <- message
|
||||
}
|
||||
}(&wg, i)
|
||||
}
|
||||
log.Debug("waiting for tcp goroutines")
|
||||
wg.Wait()
|
||||
errorsDuringTransfer <- nil
|
||||
}()
|
||||
|
||||
// block until this is done
|
||||
|
||||
log.Debug("waiting for error")
|
||||
errorDuringTransfer := <-errorsDuringTransfer
|
||||
log.Debug("sending stop message signal")
|
||||
stopMessageSignal <- true
|
||||
if errorDuringTransfer != nil {
|
||||
log.Debugf("got error during transfer: %s", errorDuringTransfer.Error())
|
||||
return errorDuringTransfer
|
||||
}
|
||||
}
|
||||
|
||||
_ = <-finished
|
||||
log.Debug("telling sender i'm done")
|
||||
c.WriteMessage(websocket.BinaryMessage, []byte("done"))
|
||||
// we are finished
|
||||
transferTime = time.Since(startTime)
|
||||
|
||||
// close file
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// finish bar
|
||||
cr.Bar.Finish()
|
||||
|
||||
// check hash
|
||||
hash256, err = utils.HashFile(cr.FileInfo.SentName)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
// tell the sender the hash so they can quit
|
||||
c.WriteMessage(websocket.BinaryMessage, append([]byte("hash:"), hash256...))
|
||||
case 4:
|
||||
// receive the hash from the sender so we can check it and quit
|
||||
log.Debugf("got hash: %x", message)
|
||||
if bytes.Equal(hash256, message) {
|
||||
// open directory
|
||||
if cr.FileInfo.IsDir {
|
||||
err = zipper.UnzipFile(cr.FileInfo.SentName, ".")
|
||||
if DebugLevel != "debug" {
|
||||
os.Remove(cr.FileInfo.SentName)
|
||||
}
|
||||
} else {
|
||||
err = nil
|
||||
}
|
||||
if err == nil {
|
||||
if useStdout && !cr.FileInfo.IsDir {
|
||||
var bFile []byte
|
||||
bFile, err = ioutil.ReadFile(cr.FileInfo.SentName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
os.Stdout.Write(bFile)
|
||||
os.Remove(cr.FileInfo.SentName)
|
||||
}
|
||||
transferRate := float64(cr.FileInfo.Size) / 1000000.0 / transferTime.Seconds()
|
||||
transferType := "MB/s"
|
||||
if transferRate < 1 {
|
||||
transferRate = float64(cr.FileInfo.Size) / 1000.0 / transferTime.Seconds()
|
||||
transferType = "kB/s"
|
||||
}
|
||||
folderOrFile := "file"
|
||||
if cr.FileInfo.IsDir {
|
||||
folderOrFile = "folder"
|
||||
}
|
||||
if useStdout {
|
||||
cr.FileInfo.Name = "stdout"
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\nReceived %s written to %s (%2.1f %s)", folderOrFile, cr.FileInfo.Name, transferRate, transferType)
|
||||
os.Remove(progressFile)
|
||||
cr.StateString = fmt.Sprintf("Received %s written to %s (%2.1f %s)", folderOrFile, cr.FileInfo.Name, transferRate, transferType)
|
||||
}
|
||||
return err
|
||||
} else {
|
||||
if DebugLevel != "debug" {
|
||||
log.Debug("removing corrupted file")
|
||||
os.Remove(cr.FileInfo.SentName)
|
||||
}
|
||||
return errors.New("file corrupted")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown step")
|
||||
}
|
||||
step++
|
||||
}
|
||||
}
|
|
@ -1,570 +0,0 @@
|
|||
package croc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/cihub/seelog"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/schollz/croc/src/comm"
|
||||
"github.com/schollz/croc/src/compress"
|
||||
"github.com/schollz/croc/src/crypt"
|
||||
"github.com/schollz/croc/src/logger"
|
||||
"github.com/schollz/croc/src/models"
|
||||
"github.com/schollz/croc/src/utils"
|
||||
"github.com/schollz/croc/src/zipper"
|
||||
"github.com/schollz/pake"
|
||||
progressbar "github.com/schollz/progressbar/v2"
|
||||
"github.com/schollz/spinner"
|
||||
)
|
||||
|
||||
// Send is the async call to send data
|
||||
func (cr *Croc) startSender(forceSend int, serverAddress string, tcpPorts []string, isLocal bool, done chan error, c *websocket.Conn, fname string, codephrase string, useCompression bool, useEncryption bool) {
|
||||
logger.SetLogLevel(DebugLevel)
|
||||
log.Debugf("sending %s", fname)
|
||||
err := cr.send(forceSend, serverAddress, tcpPorts, isLocal, c, fname, codephrase, useCompression, useEncryption)
|
||||
if err != nil && strings.HasPrefix(err.Error(), "websocket: close 100") {
|
||||
err = nil
|
||||
}
|
||||
done <- err
|
||||
}
|
||||
|
||||
func (cr *Croc) send(forceSend int, serverAddress string, tcpPorts []string, isLocal bool, c *websocket.Conn, fname string, codephrase string, useCompression bool, useEncryption bool) (err error) {
|
||||
var f *os.File
|
||||
defer f.Close() // ignore the error if it wasn't opened :(
|
||||
var fileHash []byte
|
||||
var startTransfer time.Time
|
||||
var tcpConnections []comm.Comm
|
||||
blocksToSkip := make(map[int64]struct{})
|
||||
isConnectedIfUsingTCP := make(chan bool)
|
||||
|
||||
type DataChan struct {
|
||||
b []byte
|
||||
currentPostition int64
|
||||
bytesRead int
|
||||
err error
|
||||
}
|
||||
dataChan := make(chan DataChan, 1024*1024)
|
||||
defer close(dataChan)
|
||||
|
||||
useWebsockets := true
|
||||
switch forceSend {
|
||||
case 0:
|
||||
if !isLocal {
|
||||
useWebsockets = false
|
||||
}
|
||||
case 1:
|
||||
useWebsockets = true
|
||||
case 2:
|
||||
useWebsockets = false
|
||||
}
|
||||
|
||||
fileReady := make(chan error)
|
||||
|
||||
// normalize the file name
|
||||
fname, err = filepath.Abs(fname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, filename := filepath.Split(fname)
|
||||
|
||||
// get ready to generate session key
|
||||
var sessionKey []byte
|
||||
|
||||
// start a spinner
|
||||
spin := spinner.New(spinner.CharSets[9], 100*time.Millisecond)
|
||||
spin.Writer = os.Stderr
|
||||
defer spin.Stop()
|
||||
|
||||
// both parties should have a weak key
|
||||
pw := []byte(codephrase)
|
||||
// initialize sender P ("0" indicates sender)
|
||||
P, err := pake.InitCurve(pw, 0, cr.CurveType, 1*time.Millisecond)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// start the reader
|
||||
websocketMessages := make(chan WebSocketMessage, 1024)
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Debugf("recovered from %s", r)
|
||||
}
|
||||
}()
|
||||
for {
|
||||
messageType, message, err := c.ReadMessage()
|
||||
websocketMessages <- WebSocketMessage{messageType, message, err}
|
||||
}
|
||||
}()
|
||||
|
||||
step := 0
|
||||
for {
|
||||
websocketMessage := <-websocketMessages
|
||||
messageType := websocketMessage.messageType
|
||||
message := websocketMessage.message
|
||||
errRead := websocketMessage.err
|
||||
if errRead != nil {
|
||||
return errRead
|
||||
}
|
||||
if messageType == websocket.PongMessage || messageType == websocket.PingMessage {
|
||||
continue
|
||||
}
|
||||
if messageType == websocket.TextMessage && bytes.HasPrefix(message, []byte("interrupt")) {
|
||||
return errors.New("\rinterrupted by other party")
|
||||
}
|
||||
if messageType == websocket.TextMessage && bytes.HasPrefix(message, []byte("err")) {
|
||||
return errors.New("\r" + string(message))
|
||||
}
|
||||
log.Debugf("got %d: %s", messageType, message)
|
||||
switch step {
|
||||
case 0:
|
||||
// sender initiates communication
|
||||
ip := ""
|
||||
if isLocal {
|
||||
ip = utils.LocalIP()
|
||||
} else {
|
||||
ip, _ = utils.PublicIP()
|
||||
}
|
||||
|
||||
initialData := models.Initial{
|
||||
CurveType: cr.CurveType,
|
||||
IPAddress: ip,
|
||||
VersionString: cr.Version, // version should match
|
||||
}
|
||||
bInitialData, _ := json.Marshal(initialData)
|
||||
// send the initial data
|
||||
c.WriteMessage(websocket.BinaryMessage, bInitialData)
|
||||
case 1:
|
||||
// first receive the initial data from the recipient
|
||||
var initialData models.Initial
|
||||
err = json.Unmarshal(message, &initialData)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "incompatible versions of croc")
|
||||
return
|
||||
}
|
||||
cr.OtherIP = initialData.IPAddress
|
||||
log.Debugf("recipient IP: %s", cr.OtherIP)
|
||||
|
||||
go func() {
|
||||
// recipient might want file! start gathering information about file
|
||||
fstat, err := os.Stat(fname)
|
||||
if err != nil {
|
||||
fileReady <- err
|
||||
return
|
||||
}
|
||||
cr.FileInfo = models.FileStats{
|
||||
Name: filename,
|
||||
Size: fstat.Size(),
|
||||
ModTime: fstat.ModTime(),
|
||||
IsDir: fstat.IsDir(),
|
||||
SentName: fstat.Name(),
|
||||
IsCompressed: useCompression,
|
||||
IsEncrypted: useEncryption,
|
||||
}
|
||||
if cr.FileInfo.IsDir {
|
||||
// zip the directory
|
||||
cr.FileInfo.SentName, err = zipper.ZipFile(fname, true)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
fileReady <- err
|
||||
return
|
||||
}
|
||||
fname = cr.FileInfo.SentName
|
||||
|
||||
fstat, err := os.Stat(fname)
|
||||
if err != nil {
|
||||
fileReady <- err
|
||||
return
|
||||
}
|
||||
// get new size
|
||||
cr.FileInfo.Size = fstat.Size()
|
||||
}
|
||||
|
||||
// open the file
|
||||
f, err = os.Open(fname)
|
||||
if err != nil {
|
||||
fileReady <- err
|
||||
return
|
||||
}
|
||||
fileReady <- nil
|
||||
|
||||
}()
|
||||
|
||||
// send pake data
|
||||
log.Debugf("[%d] first, P sends u to Q", step)
|
||||
c.WriteMessage(websocket.BinaryMessage, P.Bytes())
|
||||
// start PAKE spinnner
|
||||
spin.Suffix = " performing PAKE..."
|
||||
cr.StateString = "Performing PAKE..."
|
||||
spin.Start()
|
||||
case 2:
|
||||
// P recieves H(k),v from Q
|
||||
log.Debugf("[%d] P computes k, H(k), sends H(k) to Q", step)
|
||||
err := P.Update(message)
|
||||
c.WriteMessage(websocket.BinaryMessage, P.Bytes())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Recipient is using wrong code phrase.")
|
||||
}
|
||||
|
||||
sessionKey, _ = P.SessionKey()
|
||||
// check(err)
|
||||
log.Debugf("%x\n", sessionKey)
|
||||
|
||||
// wait for readiness
|
||||
spin.Stop()
|
||||
spin.Suffix = " waiting for recipient ok..."
|
||||
cr.StateString = "Waiting for recipient ok...."
|
||||
spin.Start()
|
||||
case 3:
|
||||
log.Debugf("[%d] recipient declares readiness for file info", step)
|
||||
if !bytes.HasPrefix(message, []byte("ready")) {
|
||||
return errors.New("Recipient refused file")
|
||||
}
|
||||
|
||||
err = <-fileReady // block until file is ready
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fstatsBytes, err := json.Marshal(cr.FileInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// encrypt the file meta data
|
||||
enc := crypt.Encrypt(fstatsBytes, sessionKey)
|
||||
// send the file meta data
|
||||
c.WriteMessage(websocket.BinaryMessage, enc.Bytes())
|
||||
case 4:
|
||||
log.Debugf("[%d] recipient gives blocks", step)
|
||||
// recipient sends blocks, and sender does not send anything back
|
||||
// determine if any blocks were sent to skip
|
||||
enc, err := crypt.FromBytes(message)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
decrypted, err := enc.Decrypt(sessionKey)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "could not decrypt blocks with session key")
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
var blocks []string
|
||||
errBlocks := json.Unmarshal(decrypted, &blocks)
|
||||
if errBlocks == nil {
|
||||
for _, block := range blocks {
|
||||
blockInt64, errBlock := strconv.Atoi(block)
|
||||
if errBlock == nil {
|
||||
blocksToSkip[int64(blockInt64)] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Debugf("found blocks: %+v", blocksToSkip)
|
||||
|
||||
// connect to TCP in background
|
||||
tcpConnections = make([]comm.Comm, len(tcpPorts))
|
||||
go func() {
|
||||
if !useWebsockets {
|
||||
log.Debugf("connecting to server")
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(tcpPorts))
|
||||
for i, tcpPort := range tcpPorts {
|
||||
go func(i int, tcpPort string) {
|
||||
defer wg.Done()
|
||||
log.Debugf("connecting to %s on connection %d", tcpPort, i)
|
||||
var message string
|
||||
tcpConnections[i], message, err = connectToTCPServer(utils.SHA256(fmt.Sprintf("%d%x", i, sessionKey)), serverAddress+":"+tcpPort)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
if message != "sender" {
|
||||
log.Errorf("got wrong message: %s", message)
|
||||
}
|
||||
}(i, tcpPort)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
isConnectedIfUsingTCP <- true
|
||||
}()
|
||||
|
||||
// start loading the file into memory
|
||||
// start streaming encryption/compression
|
||||
if cr.FileInfo.IsDir {
|
||||
// remove file if zipped
|
||||
defer os.Remove(cr.FileInfo.SentName)
|
||||
}
|
||||
go func(dataChan chan DataChan) {
|
||||
var buffer []byte
|
||||
if useWebsockets {
|
||||
buffer = make([]byte, models.WEBSOCKET_BUFFER_SIZE/8)
|
||||
} else {
|
||||
buffer = make([]byte, models.TCP_BUFFER_SIZE/2)
|
||||
}
|
||||
|
||||
currentPostition := int64(0)
|
||||
for {
|
||||
bytesread, err := f.Read(buffer)
|
||||
if bytesread > 0 {
|
||||
if _, ok := blocksToSkip[currentPostition]; ok {
|
||||
log.Debugf("skipping the sending of block %d", currentPostition)
|
||||
currentPostition += int64(bytesread)
|
||||
continue
|
||||
}
|
||||
|
||||
// do compression
|
||||
var compressedBytes []byte
|
||||
if useCompression && !cr.FileInfo.IsDir {
|
||||
compressedBytes = compress.Compress(buffer[:bytesread])
|
||||
} else {
|
||||
compressedBytes = buffer[:bytesread]
|
||||
}
|
||||
|
||||
// if using TCP, prepend the location to write the data to in the resulting file
|
||||
if !useWebsockets {
|
||||
compressedBytes = append([]byte(fmt.Sprintf("%d-", currentPostition)), compressedBytes...)
|
||||
}
|
||||
|
||||
// do encryption
|
||||
enc := crypt.Encrypt(compressedBytes, sessionKey, !useEncryption)
|
||||
encBytes, err := json.Marshal(enc)
|
||||
if err != nil {
|
||||
dataChan <- DataChan{
|
||||
b: nil,
|
||||
bytesRead: 0,
|
||||
err: err,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
dataChan <- DataChan{
|
||||
b: encBytes,
|
||||
bytesRead: bytesread,
|
||||
err: nil,
|
||||
}
|
||||
currentPostition += int64(bytesread)
|
||||
}
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
log.Error(err)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
// finish
|
||||
log.Debug("sending magic")
|
||||
dataChan <- DataChan{
|
||||
b: []byte("magic"),
|
||||
bytesRead: 0,
|
||||
err: nil,
|
||||
}
|
||||
if !useWebsockets {
|
||||
log.Debug("sending extra magic to %d others", len(tcpPorts)-1)
|
||||
for i := 0; i < len(tcpPorts)-1; i++ {
|
||||
log.Debug("sending magic")
|
||||
dataChan <- DataChan{
|
||||
b: []byte("magic"),
|
||||
bytesRead: 0,
|
||||
err: nil,
|
||||
}
|
||||
}
|
||||
}
|
||||
}(dataChan)
|
||||
|
||||
case 5:
|
||||
spin.Stop()
|
||||
|
||||
log.Debugf("[%d] recipient declares readiness for file data", step)
|
||||
if !bytes.HasPrefix(message, []byte("ready")) {
|
||||
return errors.New("Recipient refused file")
|
||||
}
|
||||
cr.StateString = "Transfer in progress..."
|
||||
fmt.Fprintf(os.Stderr, "\rSending (->%s)...\n", cr.OtherIP)
|
||||
// send file, compure hash simultaneously
|
||||
startTransfer = time.Now()
|
||||
|
||||
blockSize := 0
|
||||
if useWebsockets {
|
||||
blockSize = models.WEBSOCKET_BUFFER_SIZE / 8
|
||||
} else {
|
||||
blockSize = models.TCP_BUFFER_SIZE / 2
|
||||
}
|
||||
cr.Bar = progressbar.NewOptions(
|
||||
int(cr.FileInfo.Size),
|
||||
progressbar.OptionSetRenderBlankState(true),
|
||||
progressbar.OptionSetBytes(int(cr.FileInfo.Size)),
|
||||
progressbar.OptionSetWriter(os.Stderr),
|
||||
progressbar.OptionThrottle(1/60*time.Second),
|
||||
)
|
||||
cr.Bar.Add(blockSize * len(blocksToSkip))
|
||||
|
||||
if useWebsockets {
|
||||
for {
|
||||
data := <-dataChan
|
||||
if data.err != nil {
|
||||
return data.err
|
||||
}
|
||||
cr.Bar.Add(data.bytesRead)
|
||||
|
||||
// write data to websockets
|
||||
err = c.WriteMessage(websocket.BinaryMessage, data.b)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "problem writing message")
|
||||
return err
|
||||
}
|
||||
if bytes.Equal(data.b, []byte("magic")) {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_ = <-isConnectedIfUsingTCP
|
||||
log.Debug("connected and ready to send on tcp")
|
||||
|
||||
// check to see if any messages are sent
|
||||
stopMessageSignal := make(chan bool, 1)
|
||||
errorsDuringTransfer := make(chan error, 24)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case sig := <-stopMessageSignal:
|
||||
errorsDuringTransfer <- nil
|
||||
log.Debugf("got message signal: %+v", sig)
|
||||
return
|
||||
case wsMessage := <-websocketMessages:
|
||||
log.Debugf("got message: %s", wsMessage.message)
|
||||
if bytes.HasPrefix(wsMessage.message, []byte("error")) {
|
||||
log.Debug("stopping transfer")
|
||||
for i := 0; i < len(tcpConnections)+1; i++ {
|
||||
errorsDuringTransfer <- fmt.Errorf("%s", wsMessage.message)
|
||||
}
|
||||
return
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(tcpConnections))
|
||||
for i := range tcpConnections {
|
||||
defer func(i int) {
|
||||
log.Debugf("closing connection %d", i)
|
||||
tcpConnections[i].Close()
|
||||
}(i)
|
||||
go func(i int, wg *sync.WaitGroup, dataChan <-chan DataChan) {
|
||||
defer wg.Done()
|
||||
for data := range dataChan {
|
||||
select {
|
||||
case _ = <-errorsDuringTransfer:
|
||||
log.Debugf("%d got stop", i)
|
||||
return
|
||||
default:
|
||||
}
|
||||
if data.err != nil {
|
||||
log.Error(data.err)
|
||||
return
|
||||
}
|
||||
cr.Bar.Add(data.bytesRead)
|
||||
// write data to tcp connection
|
||||
_, errTcp := tcpConnections[i].Write(data.b)
|
||||
if errTcp != nil {
|
||||
errTcp = errors.Wrap(errTcp, "problem writing message")
|
||||
log.Debug(errTcp)
|
||||
errorsDuringTransfer <- errTcp
|
||||
return
|
||||
}
|
||||
if bytes.Equal(data.b, []byte("magic")) {
|
||||
log.Debugf("%d got magic", i)
|
||||
return
|
||||
}
|
||||
}
|
||||
}(i, &wg, dataChan)
|
||||
}
|
||||
|
||||
// block until this is done
|
||||
log.Debug("waiting for tcp goroutines")
|
||||
wg.Wait()
|
||||
log.Debug("sending stop message signal")
|
||||
stopMessageSignal <- true
|
||||
log.Debug("waiting for error")
|
||||
errorDuringTransfer := <-errorsDuringTransfer
|
||||
if errorDuringTransfer != nil {
|
||||
log.Debugf("got error during transfer: %s", errorDuringTransfer.Error())
|
||||
return errorDuringTransfer
|
||||
}
|
||||
}
|
||||
|
||||
cr.Bar.Finish()
|
||||
log.Debug("send hash to finish file")
|
||||
fileHash, err = utils.HashFile(fname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case 6:
|
||||
// recevied something, maybe the file hash
|
||||
transferTime := time.Since(startTransfer)
|
||||
if !bytes.HasPrefix(message, []byte("hash:")) {
|
||||
log.Debugf("%s", message)
|
||||
continue
|
||||
}
|
||||
c.WriteMessage(websocket.BinaryMessage, fileHash)
|
||||
message = bytes.TrimPrefix(message, []byte("hash:"))
|
||||
log.Debugf("[%d] determing whether it went ok", step)
|
||||
if bytes.Equal(message, fileHash) {
|
||||
log.Debug("file transfered successfully")
|
||||
transferRate := float64(cr.FileInfo.Size) / 1000000.0 / transferTime.Seconds()
|
||||
transferType := "MB/s"
|
||||
if transferRate < 1 {
|
||||
transferRate = float64(cr.FileInfo.Size) / 1000.0 / transferTime.Seconds()
|
||||
transferType = "kB/s"
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\nTransfer complete (%2.1f %s)", transferRate, transferType)
|
||||
cr.StateString = fmt.Sprintf("Transfer complete (%2.1f %s)", transferRate, transferType)
|
||||
return nil
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "\nTransfer corrupted")
|
||||
return errors.New("file not transfered succesfully")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown step")
|
||||
}
|
||||
step++
|
||||
}
|
||||
}
|
||||
|
||||
func connectToTCPServer(room string, address string) (com comm.Comm, message string, err error) {
|
||||
connection, err := net.DialTimeout("tcp", address, 3*time.Hour)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
connection.SetReadDeadline(time.Now().Add(3 * time.Hour))
|
||||
connection.SetDeadline(time.Now().Add(3 * time.Hour))
|
||||
connection.SetWriteDeadline(time.Now().Add(3 * time.Hour))
|
||||
|
||||
com = comm.New(connection)
|
||||
ok, err := com.Receive()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.Debugf("server says: %s", ok)
|
||||
|
||||
err = com.Send(room)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
message, err = com.Receive()
|
||||
log.Debugf("server says: %s", message)
|
||||
return
|
||||
}
|
|
@ -1,217 +0,0 @@
|
|||
package croc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/cihub/seelog"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/schollz/croc/src/relay"
|
||||
"github.com/schollz/croc/src/utils"
|
||||
"github.com/schollz/peerdiscovery"
|
||||
)
|
||||
|
||||
// Send the file
|
||||
func (c *Croc) Send(fname, codephrase string) (err error) {
|
||||
defer log.Flush()
|
||||
log.Debugf("sending %s", fname)
|
||||
errChan := make(chan error)
|
||||
|
||||
// normally attempt two connections
|
||||
waitingFor := 2
|
||||
|
||||
// use public relay
|
||||
if !c.LocalOnly {
|
||||
go func() {
|
||||
// atttempt to connect to public relay
|
||||
errChan <- c.sendReceive(c.Address, c.AddressWebsocketPort, c.AddressTCPPorts, fname, codephrase, true, false)
|
||||
}()
|
||||
} else {
|
||||
waitingFor = 1
|
||||
}
|
||||
|
||||
// use local relay
|
||||
if !c.NoLocal {
|
||||
defer func() {
|
||||
log.Debug("sending relay stop signal")
|
||||
relay.Stop()
|
||||
}()
|
||||
go func() {
|
||||
// start own relay and connect to it
|
||||
go relay.Run(c.RelayWebsocketPort, c.RelayTCPPorts)
|
||||
time.Sleep(250 * time.Millisecond) // race condition here, but this should work most of the time :(
|
||||
|
||||
// broadcast for peer discovery
|
||||
go func() {
|
||||
log.Debug("starting local discovery...")
|
||||
discovered, err := peerdiscovery.Discover(peerdiscovery.Settings{
|
||||
Limit: 1,
|
||||
TimeLimit: 600 * time.Second,
|
||||
Delay: 50 * time.Millisecond,
|
||||
Payload: []byte(c.RelayWebsocketPort + "- " + strings.Join(c.RelayTCPPorts, ",")),
|
||||
MulticastAddress: fmt.Sprintf("239.255.255.%d", 230+int64(time.Now().Minute()/5)),
|
||||
})
|
||||
log.Debug(discovered, err)
|
||||
}()
|
||||
|
||||
// connect to own relay
|
||||
errChan <- c.sendReceive("localhost", c.RelayWebsocketPort, c.RelayTCPPorts, fname, codephrase, true, true)
|
||||
}()
|
||||
} else {
|
||||
waitingFor = 1
|
||||
}
|
||||
|
||||
err = <-errChan
|
||||
if err == nil || waitingFor == 1 {
|
||||
log.Debug("returning")
|
||||
return
|
||||
}
|
||||
log.Debug(err)
|
||||
return <-errChan
|
||||
}
|
||||
|
||||
// Receive the file
|
||||
func (c *Croc) Receive(codephrase string) (err error) {
|
||||
defer log.Flush()
|
||||
log.Debug("receiving")
|
||||
|
||||
// use local relay first
|
||||
if !c.NoLocal {
|
||||
log.Debug("trying to discover")
|
||||
// try to discovery codephrase and server through peer network
|
||||
discovered, errDiscover := peerdiscovery.Discover(peerdiscovery.Settings{
|
||||
Limit: 1,
|
||||
TimeLimit: 300 * time.Millisecond,
|
||||
Delay: 50 * time.Millisecond,
|
||||
Payload: []byte("checking"),
|
||||
AllowSelf: true,
|
||||
DisableBroadcast: true,
|
||||
MulticastAddress: fmt.Sprintf("239.255.255.%d", 230+int64(time.Now().Minute()/5)),
|
||||
})
|
||||
log.Debug("finished")
|
||||
log.Debug(discovered)
|
||||
if errDiscover != nil {
|
||||
log.Debug(errDiscover)
|
||||
}
|
||||
if len(discovered) > 0 {
|
||||
if discovered[0].Address == utils.LocalIP() {
|
||||
discovered[0].Address = "localhost"
|
||||
}
|
||||
log.Debugf("discovered %s:%s", discovered[0].Address, discovered[0].Payload)
|
||||
// see if we can actually connect to it
|
||||
timeout := time.Duration(200 * time.Millisecond)
|
||||
client := http.Client{
|
||||
Timeout: timeout,
|
||||
}
|
||||
ports := strings.Split(string(discovered[0].Payload), "-")
|
||||
if len(ports) != 2 {
|
||||
return errors.New("bad payload")
|
||||
}
|
||||
resp, err := client.Get(fmt.Sprintf("http://%s:%s/", discovered[0].Address, ports[0]))
|
||||
if err == nil {
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
// we connected, so use this
|
||||
return c.sendReceive(discovered[0].Address, strings.TrimSpace(ports[0]), strings.Split(strings.TrimSpace(ports[1]), ","), "", codephrase, false, true)
|
||||
}
|
||||
} else {
|
||||
log.Debugf("could not connect: %s", err.Error())
|
||||
}
|
||||
} else {
|
||||
log.Debug("discovered no peers")
|
||||
}
|
||||
}
|
||||
|
||||
// use public relay
|
||||
if !c.LocalOnly {
|
||||
log.Debug("using public relay")
|
||||
return c.sendReceive(c.Address, c.AddressWebsocketPort, c.AddressTCPPorts, "", codephrase, false, false)
|
||||
}
|
||||
|
||||
return errors.New("must use local or public relay")
|
||||
}
|
||||
|
||||
func (c *Croc) sendReceive(address, websocketPort string, tcpPorts []string, fname string, codephrase string, isSender bool, isLocal bool) (err error) {
|
||||
defer log.Flush()
|
||||
if len(codephrase) < 4 {
|
||||
return fmt.Errorf("codephrase is too short")
|
||||
}
|
||||
|
||||
// allow interrupts from Ctl+C
|
||||
interrupt := make(chan os.Signal, 1)
|
||||
signal.Notify(interrupt, os.Interrupt)
|
||||
|
||||
done := make(chan error)
|
||||
// connect to server
|
||||
websocketAddress := ""
|
||||
if len(websocketPort) > 0 {
|
||||
websocketAddress = fmt.Sprintf("ws://%s:%s/ws?room=%s", address, websocketPort, codephrase[:3])
|
||||
} else {
|
||||
websocketAddress = fmt.Sprintf("ws://%s/ws?room=%s", address, codephrase[:3])
|
||||
}
|
||||
log.Debugf("connecting to %s", websocketAddress)
|
||||
sock, _, err := websocket.DefaultDialer.Dial(websocketAddress, nil)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
defer sock.Close()
|
||||
|
||||
// tell the websockets we are connected
|
||||
err = sock.WriteMessage(websocket.BinaryMessage, []byte("connected"))
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if isSender {
|
||||
go c.startSender(c.ForceSend, address, tcpPorts, isLocal, done, sock, fname, codephrase, c.UseCompression, c.UseEncryption)
|
||||
} else {
|
||||
go c.startRecipient(c.ForceSend, address, tcpPorts, isLocal, done, sock, codephrase, c.NoRecipientPrompt, c.Stdout)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case doneError := <-done:
|
||||
log.Debug("received done signal")
|
||||
if doneError != nil {
|
||||
c.StateString = doneError.Error()
|
||||
sock.WriteMessage(websocket.TextMessage, []byte("error: "+doneError.Error()))
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
return doneError
|
||||
case <-interrupt:
|
||||
if !c.Debug {
|
||||
SetDebugLevel("critical")
|
||||
}
|
||||
log.Debug("interrupt")
|
||||
err = sock.WriteMessage(websocket.TextMessage, []byte("error: interrupted by other party"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
|
||||
// Cleanly close the connection by sending a close message and then
|
||||
// waiting (with timeout) for the server to close the connection.
|
||||
err := sock.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
|
||||
if err != nil {
|
||||
log.Debug("write close:", err)
|
||||
return nil
|
||||
}
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Relay will start a relay on the specified port
|
||||
func (c *Croc) Relay() (err error) {
|
||||
return relay.Run(c.RelayWebsocketPort, c.RelayTCPPorts)
|
||||
}
|
|
@ -5,85 +5,79 @@ import (
|
|||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
// Encryption stores the data
|
||||
type Encryption struct {
|
||||
Encrypted []byte `json:"e"`
|
||||
Salt []byte `json:"s"`
|
||||
IV []byte `json:"i"`
|
||||
key []byte
|
||||
passphrase []byte
|
||||
salt []byte
|
||||
}
|
||||
|
||||
func (e Encryption) Bytes() []byte {
|
||||
return []byte(base64.StdEncoding.EncodeToString(e.Encrypted) + "-" + base64.StdEncoding.EncodeToString(e.Salt) + "-" + base64.StdEncoding.EncodeToString(e.IV))
|
||||
}
|
||||
|
||||
func FromBytes(b []byte) (enc Encryption, err error) {
|
||||
enc = Encryption{}
|
||||
items := strings.Split(string(b), "-")
|
||||
if len(items) != 3 {
|
||||
err = errors.New("not valid")
|
||||
// New generates a new Encryption, using the supplied passphrase and
|
||||
// an optional supplied salt.
|
||||
// Passing nil passphrase will not use decryption.
|
||||
func New(passphrase []byte, salt []byte) (e Encryption, err error) {
|
||||
if passphrase == nil {
|
||||
e = Encryption{nil, nil, nil}
|
||||
return
|
||||
}
|
||||
enc.Encrypted, err = base64.StdEncoding.DecodeString(items[0])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
enc.Salt, err = base64.StdEncoding.DecodeString(items[1])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
enc.IV, err = base64.StdEncoding.DecodeString(items[2])
|
||||
return
|
||||
}
|
||||
|
||||
// Encrypt will generate an encryption
|
||||
func Encrypt(plaintext []byte, passphrase []byte, dontencrypt ...bool) Encryption {
|
||||
if len(dontencrypt) > 0 && dontencrypt[0] {
|
||||
return Encryption{
|
||||
Encrypted: plaintext,
|
||||
Salt: []byte("salt"),
|
||||
IV: []byte("iv"),
|
||||
}
|
||||
}
|
||||
key, saltBytes := deriveKey(passphrase, nil)
|
||||
ivBytes := make([]byte, 12)
|
||||
// http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
|
||||
// Section 8.2
|
||||
rand.Read(ivBytes)
|
||||
b, _ := aes.NewCipher(key)
|
||||
aesgcm, _ := cipher.NewGCM(b)
|
||||
encrypted := aesgcm.Seal(nil, ivBytes, plaintext, nil)
|
||||
return Encryption{
|
||||
Encrypted: encrypted,
|
||||
Salt: saltBytes,
|
||||
IV: ivBytes,
|
||||
}
|
||||
}
|
||||
|
||||
// Decrypt an encryption
|
||||
func (e Encryption) Decrypt(passphrase []byte, dontencrypt ...bool) (plaintext []byte, err error) {
|
||||
if len(dontencrypt) > 0 && dontencrypt[0] {
|
||||
return e.Encrypted, nil
|
||||
}
|
||||
key, _ := deriveKey(passphrase, e.Salt)
|
||||
b, _ := aes.NewCipher(key)
|
||||
aesgcm, _ := cipher.NewGCM(b)
|
||||
plaintext, err = aesgcm.Open(nil, e.IV, e.Encrypted, nil)
|
||||
return
|
||||
}
|
||||
|
||||
func deriveKey(passphrase []byte, salt []byte) ([]byte, []byte) {
|
||||
e.passphrase = passphrase
|
||||
if salt == nil {
|
||||
salt = make([]byte, 8)
|
||||
e.salt = make([]byte, 8)
|
||||
// http://www.ietf.org/rfc/rfc2898.txt
|
||||
// Salt.
|
||||
rand.Read(salt)
|
||||
rand.Read(e.salt)
|
||||
} else {
|
||||
e.salt = salt
|
||||
}
|
||||
return pbkdf2.Key([]byte(passphrase), salt, 100, 32, sha256.New), salt
|
||||
e.key = pbkdf2.Key([]byte(passphrase), e.salt, 100, 32, sha256.New)
|
||||
return
|
||||
}
|
||||
|
||||
func (e Encryption) Salt() []byte {
|
||||
return e.salt
|
||||
}
|
||||
|
||||
// Encrypt will generate an Encryption, prefixed with the IV
|
||||
func (e Encryption) Encrypt(plaintext []byte) (encrypted []byte, err error) {
|
||||
if e.passphrase == nil {
|
||||
encrypted = plaintext
|
||||
return
|
||||
}
|
||||
// generate a random iv each time
|
||||
// http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf
|
||||
// Section 8.2
|
||||
ivBytes := make([]byte, 12)
|
||||
rand.Read(ivBytes)
|
||||
b, err := aes.NewCipher(e.key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
aesgcm, err := cipher.NewGCM(b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
encrypted = aesgcm.Seal(nil, ivBytes, plaintext, nil)
|
||||
encrypted = append(ivBytes, encrypted...)
|
||||
return
|
||||
}
|
||||
|
||||
// Decrypt an Encryption
|
||||
func (e Encryption) Decrypt(encrypted []byte) (plaintext []byte, err error) {
|
||||
if e.passphrase == nil {
|
||||
plaintext = encrypted
|
||||
return
|
||||
}
|
||||
b, err := aes.NewCipher(e.key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
aesgcm, err := cipher.NewGCM(b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
plaintext, err = aesgcm.Open(nil, encrypted[:12], encrypted[12:], nil)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,41 +1,61 @@
|
|||
package crypt
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
func BenchmarkEncryption(b *testing.B) {
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func BenchmarkEncryptionNew(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Encrypt([]byte(`
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse laoreet justo quis augue vehicula ornare. Nullam nec risus volutpat nulla dictum consectetur. Phasellus porttitor, justo non tincidunt finibus, massa justo iaculis urna, eget mattis nulla libero vitae risus. Vestibulum vehicula nunc id dignissim rutrum. Cras varius ac nulla a imperdiet. Sed finibus, libero in tempor hendrerit, turpis erat faucibus nisl, a consectetur massa mi eget mi. Vestibulum pulvinar lorem id ipsum elementum auctor.
|
||||
|
||||
Morbi at odio a eros eleifend faucibus. Sed at tempor urna, in interdum neque. Curabitur condimentum rhoncus orci, vel vulputate risus ultricies efficitur. Curabitur eu vehicula ligula. Curabitur suscipit ex vitae nunc faucibus vehicula. Mauris sed dictum mauris. Vivamus nec dui at urna porttitor suscipit. Integer ut eros finibus orci consectetur hendrerit id quis dolor. Proin dapibus orci quis massa viverra finibus. Sed quis ligula neque.
|
||||
|
||||
Aenean et fringilla nulla. In et venenatis massa, vel feugiat diam. Sed ornare felis nec egestas suscipit. Sed a ultricies sapien. Aliquam ex leo, tincidunt faucibus neque non, vehicula commodo velit. Vestibulum molestie efficitur velit in vestibulum. Aliquam eget leo felis. Etiam pharetra vulputate egestas. Cras gravida nibh eu sollicitudin facilisis. Nulla facilisi. Nulla tristique arcu vitae arcu pulvinar feugiat. Suspendisse finibus a urna a cursus. Donec bibendum sodales nunc eget tincidunt. Pellentesque blandit ac nunc at consectetur.
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce congue nibh nisl, et porttitor mauris tempor eget. Vivamus sit amet sagittis ligula. Quisque aliquam orci non odio egestas, vel semper felis efficitur. Vivamus non leo lacus. Etiam non ligula eget erat porta laoreet id id ligula. Proin imperdiet erat id efficitur vehicula. Suspendisse potenti. Morbi ornare finibus metus, eu pretium leo tristique sit amet. Praesent placerat elit quis porttitor rhoncus. Vestibulum consectetur turpis sed lacus placerat, vel laoreet est interdum. Proin id quam ut risus tempor hendrerit nec eu lacus. Duis vel aliquam ex.
|
||||
|
||||
Sed facilisis in ex vitae pellentesque. Donec tempor lobortis dui. Praesent sagittis, elit ac dictum hendrerit, eros risus auctor erat, in pretium nulla mi et felis. Nullam imperdiet erat id erat rutrum fermentum. Praesent ultrices, diam non efficitur gravida, sem quam fermentum est, vitae tristique libero velit eget turpis. Nunc sem risus, venenatis nec suscipit quis, accumsan eu nunc. Fusce fringilla sit amet purus vel ornare. Donec nulla dolor, gravida ac metus nec, tincidunt feugiat sem. Fusce semper varius nunc.
|
||||
|
||||
Nulla facilisi. Quisque in euismod ex, ac sollicitudin dolor. Aliquam erat volutpat. Etiam nisl sem, posuere et pellentesque fermentum, pretium non lorem. Nunc blandit nisl at leo interdum gravida. Proin ullamcorper ultrices dictum. Pellentesque non ligula magna. Sed justo nibh, finibus vitae malesuada ultricies, molestie ac ante. Suspendisse maximus congue viverra.
|
||||
|
||||
Donec a est eu arcu tristique fermentum a quis velit. Aenean eu mollis turpis. Morbi euismod risus eros, at vestibulum lectus iaculis sed. Pellentesque varius eu justo in viverra. Phasellus feugiat tincidunt urna ut accumsan. In ullamcorper vehicula hendrerit. Sed sapien diam, rhoncus nec porta non, gravida et ante. Nulla et volutpat quam, nec venenatis ex. Donec vitae dictum libero, id ornare sapien. Morbi id aliquam ipsum. Cras a ultricies purus.
|
||||
|
||||
Morbi sit amet sagittis metus, vitae dictum nunc. Pellentesque ornare consequat diam, vitae maximus dolor facilisis at. Cras semper imperdiet mollis. Fusce maximus augue quis elit pulvinar, vitae varius nunc tempus. Maecenas et suscipit leo. Morbi tempus neque enim, sit amet vestibulum ex sollicitudin ac. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed vel rutrum nisl. Nullam ac vehicula velit. Sed suscipit lacus libero, ac laoreet tortor vehicula a. In aliquam tellus tincidunt ligula fringilla, ut finibus lorem commodo. Sed sit amet porta magna, et aliquam lacus.
|
||||
|
||||
Nulla at libero in velit lacinia feugiat sed sit amet erat. Cras nec iaculis magna. Curabitur efficitur turpis vel risus euismod, sit amet posuere orci efficitur. Nunc tincidunt ante eu nunc lacinia, varius dignissim tellus eleifend. Pellentesque enim urna, porttitor eget tincidunt ut, sollicitudin in enim. Donec sit amet hendrerit orci, ut viverra arcu. Nam vitae semper est. Vestibulum aliquet dolor sed turpis blandit eleifend. Nullam auctor accumsan mauris eu mattis. Etiam interdum purus sit amet libero placerat vehicula.
|
||||
|
||||
Ut ac odio risus. Sed ut dolor ut metus tincidunt egestas. Curabitur ullamcorper lectus interdum nisi euismod, et faucibus dui fringilla. Nunc at purus vel erat hendrerit dapibus a ut ligula. Nulla facilisi. Etiam gravida, dui nec posuere gravida, nunc quam sodales dui, ut congue mauris tortor vitae nunc. Praesent dapibus mi quis pulvinar consequat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Pellentesque ultricies placerat dolor tempus cursus. Etiam vehicula, dui vel varius ullamcorper, sapien sem malesuada felis, quis suscipit est nibh vel massa.
|
||||
|
||||
Nulla sed mollis enim. Interdum et malesuada fames ac ante ipsum primis in faucibus. Quisque magna purus, faucibus vel ornare vel, semper et odio. Donec non interdum nunc. Maecenas metus augue, maximus ac volutpat id, euismod eget sem. Sed efficitur non diam sed ultrices. Fusce ultrices nisl et suscipit tristique. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi luctus augue vel vehicula iaculis.
|
||||
|
||||
Vivamus iaculis luctus nisl, quis molestie purus mollis vitae. Proin vehicula rutrum finibus. Nulla lacinia auctor tincidunt. Vivamus sollicitudin orci eu porttitor vehicula. Vivamus auctor sem sed risus porta cursus. Donec orci dui, lacinia vitae mi quis, tincidunt feugiat elit. Mauris pharetra faucibus justo non volutpat. Praesent pellentesque condimentum quam quis porta. Praesent sed nisi id eros iaculis condimentum nec in libero. Donec eu blandit enim. Aliquam nec elementum libero. Donec nec nisi suscipit felis blandit rutrum. Morbi magna nulla, porttitor id nisl vitae, imperdiet efficitur leo. Ut eu vestibulum justo, vitae dapibus felis.
|
||||
|
||||
Quisque ac est nec metus sagittis lobortis mollis id metus. Duis a augue eu erat vulputate facilisis varius ac nunc. Suspendisse eleifend enim suscipit erat aliquet, sed feugiat ligula tempor. Aenean dapibus felis porttitor mauris ultricies vehicula. Vestibulum lacinia scelerisque turpis, ut varius nunc suscipit vitae. Praesent blandit mauris eu semper lacinia. Cras mauris augue, tincidunt quis risus tincidunt, blandit euismod erat. Etiam non sagittis leo, eget pharetra risus.
|
||||
|
||||
Donec sodales ultricies neque, non accumsan velit blandit non. Aliquam lacinia orci mauris, commodo porttitor ipsum venenatis sed. Integer sit amet pretium nisi. Ut porttitor, sapien quis gravida egestas, nulla tellus ullamcorper ante, et eleifend diam turpis et odio. Quisque dui orci, commodo sit amet rhoncus ut, pharetra ac leo. Suspendisse non vehicula ex. Morbi gravida lacus vitae ex lacinia, nec aliquam purus consequat. Donec aliquam pretium massa, id viverra nunc blandit sit amet. Donec sed dapibus elit. Cras hendrerit efficitur eros quis malesuada. Aenean a massa in dolor gravida volutpat a et nisi. Nullam sit amet est tempus, condimentum odio egestas, dignissim nibh. Integer tempor id sapien at sagittis.
|
||||
|
||||
Aliquam in urna semper, suscipit metus in, pharetra lectus. Proin tempor nibh turpis, a pulvinar justo mollis sit amet. Fusce massa turpis, tristique id semper sit amet, venenatis in mauris. Maecenas id consectetur purus, sed efficitur ipsum. Sed pretium nisi ut sem pulvinar ullamcorper. Nullam at nunc et quam rhoncus egestas eu eu odio. Nullam commodo urna cursus massa porta consectetur. Sed blandit erat ut imperdiet malesuada. Cras nec fringilla ante. Nam sed gravida urna. Nam non dui quis turpis efficitur feugiat. Fusce in quam ex.
|
||||
|
||||
Nullam purus libero, egestas eget luctus et, malesuada eget est. Curabitur tristique sollicitudin est, imperdiet cras amet. `), []byte(`password`))
|
||||
bob, _ := New([]byte("password"), nil)
|
||||
bob.Encrypt([]byte("hello, world"))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncryption(b *testing.B) {
|
||||
bob, _ := New([]byte("password"), nil)
|
||||
for i := 0; i < b.N; i++ {
|
||||
bob.Encrypt([]byte("hello, world"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncryption(t *testing.T) {
|
||||
bob, err := New([]byte("password"), nil)
|
||||
assert.Nil(t, err)
|
||||
jane, err := New([]byte("password"), bob.Salt())
|
||||
assert.Nil(t, err)
|
||||
enc, err := bob.Encrypt([]byte("hello, world"))
|
||||
assert.Nil(t, err)
|
||||
dec, err := jane.Decrypt(enc)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, dec, []byte("hello, world"))
|
||||
|
||||
jane2, err := New([]byte("password"), nil)
|
||||
assert.Nil(t, err)
|
||||
dec, err = jane2.Decrypt(enc)
|
||||
assert.NotNil(t, err)
|
||||
assert.NotEqual(t, dec, []byte("hello, world"))
|
||||
|
||||
jane3, err := New([]byte("passwordwrong"), bob.Salt())
|
||||
assert.Nil(t, err)
|
||||
dec, err = jane3.Decrypt(enc)
|
||||
assert.NotNil(t, err)
|
||||
assert.NotEqual(t, dec, []byte("hello, world"))
|
||||
|
||||
}
|
||||
|
||||
|
||||
func TestNoEncryption(t *testing.T) {
|
||||
bob, err := New(nil, nil)
|
||||
assert.Nil(t, err)
|
||||
jane, err := New(nil,nil)
|
||||
assert.Nil(t, err)
|
||||
enc, err := bob.Encrypt([]byte("hello, world"))
|
||||
assert.Nil(t, err)
|
||||
dec, err := jane.Decrypt(enc)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, dec, []byte("hello, world"))
|
||||
assert.Equal(t, enc, []byte("hello, world"))
|
||||
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := run()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func run() (err error) {
|
||||
version, err := exec.Command("git", "describe").Output()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
versionNew := strings.TrimSpace(string(version))
|
||||
|
||||
err = replaceInFile("src/cli/cli.go", `Version = "`, `"`, versionNew)
|
||||
if err == nil {
|
||||
fmt.Printf("updated cli.go to version %s\n", versionNew)
|
||||
}
|
||||
err = replaceInFile("README.md", `version-`, `-b`, strings.Split(versionNew, "-")[0])
|
||||
if err == nil {
|
||||
fmt.Printf("updated README to version %s\n", strings.Split(versionNew, "-")[0])
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func replaceInFile(fname, start, end, replacement string) (err error) {
|
||||
b, err := ioutil.ReadFile(fname)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
oldVersion := GetStringInBetween(string(b), start, end)
|
||||
if oldVersion == "" {
|
||||
err = fmt.Errorf("nothing")
|
||||
return
|
||||
}
|
||||
newF := strings.Replace(
|
||||
string(b),
|
||||
fmt.Sprintf("%s%s%s", start, oldVersion, end),
|
||||
fmt.Sprintf("%s%s%s", start, replacement, end),
|
||||
1,
|
||||
)
|
||||
err = ioutil.WriteFile(fname, []byte(newF), 0644)
|
||||
return
|
||||
}
|
||||
|
||||
// GetStringInBetween Returns empty string if no start string found
|
||||
func GetStringInBetween(str string, start string, end string) (result string) {
|
||||
s := strings.Index(str, start)
|
||||
if s == -1 {
|
||||
return
|
||||
}
|
||||
s += len(start)
|
||||
e := strings.Index(str[s:], end)
|
||||
if e == -1 {
|
||||
return
|
||||
}
|
||||
e += s
|
||||
return str[s:e]
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package message
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/schollz/croc/v6/src/comm"
|
||||
"github.com/schollz/croc/v6/src/compress"
|
||||
"github.com/schollz/croc/v6/src/crypt"
|
||||
)
|
||||
|
||||
// Message is the possible payload for messaging
|
||||
type Message struct {
|
||||
Type string `json:"t,omitempty"`
|
||||
Message string `json:"m,omitempty"`
|
||||
Bytes []byte `json:"b,omitempty"`
|
||||
Num int `json:"n,omitempty"`
|
||||
}
|
||||
|
||||
func (m Message) String() string {
|
||||
b, _ := json.Marshal(m)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// Send will send out
|
||||
func Send(c *comm.Comm, key crypt.Encryption, m Message) (err error) {
|
||||
mSend, err := Encode(key, m)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = c.Write(mSend)
|
||||
return
|
||||
}
|
||||
|
||||
// Encode will convert to bytes
|
||||
func Encode(key crypt.Encryption, m Message) (b []byte, err error) {
|
||||
b, err = json.Marshal(m)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
b = compress.Compress(b)
|
||||
b, err = key.Encrypt(b)
|
||||
return
|
||||
}
|
||||
|
||||
// Decode will convert from bytes
|
||||
func Decode(key crypt.Encryption, b []byte) (m Message, err error) {
|
||||
b, err = key.Decrypt(b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
b = compress.Decompress(b)
|
||||
err = json.Unmarshal(b, &m)
|
||||
return
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package message
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/schollz/croc/v6/src/crypt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMessage(t *testing.T) {
|
||||
m := Message{Type: "message", Message: "hello, world"}
|
||||
e, err := crypt.New(nil, nil)
|
||||
assert.Nil(t, err)
|
||||
fmt.Println(e.Salt())
|
||||
b, err := Encode(e, m)
|
||||
assert.Nil(t, err)
|
||||
fmt.Printf("%x\n", b)
|
||||
|
||||
m2, err := Decode(e, b)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, m, m2)
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
package models
|
||||
|
||||
const WEBSOCKET_BUFFER_SIZE = 1024 * 1024 * 32
|
||||
const TCP_BUFFER_SIZE = 1024 * 64
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// FileStats are the file stats transfered to the other
|
||||
type FileStats struct {
|
||||
Name string
|
||||
Size int64
|
||||
ModTime time.Time
|
||||
IsDir bool
|
||||
SentName string
|
||||
IsCompressed bool
|
||||
IsEncrypted bool
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package models
|
||||
|
||||
type Initial struct {
|
||||
CurveType string
|
||||
IPAddress string
|
||||
VersionString string
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
package relay
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
log "github.com/cihub/seelog"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/schollz/croc/src/models"
|
||||
)
|
||||
|
||||
const (
|
||||
// Time allowed to write a message to the peer.
|
||||
writeWait = 6000 * time.Second
|
||||
|
||||
// Time allowed to read the next pong message from the peer.
|
||||
pongWait = 6000 * time.Second
|
||||
|
||||
// Send pings to peer with this period. Must be less than pongWait.
|
||||
pingPeriod = (pongWait * 9) / 10
|
||||
|
||||
// Maximum message size allowed from peer.
|
||||
maxMessageSize = models.WEBSOCKET_BUFFER_SIZE / 2
|
||||
)
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: models.WEBSOCKET_BUFFER_SIZE,
|
||||
WriteBufferSize: models.WEBSOCKET_BUFFER_SIZE,
|
||||
}
|
||||
|
||||
// connection is an middleman between the websocket connection and the hub.
|
||||
type connection struct {
|
||||
// The websocket connection.
|
||||
ws *websocket.Conn
|
||||
|
||||
// Buffered channel of outbound messages.
|
||||
send chan messageChannel
|
||||
}
|
||||
|
||||
type messageChannel struct {
|
||||
data []byte
|
||||
messageType int
|
||||
}
|
||||
|
||||
// readPump pumps messages from the websocket connection to the hub.
|
||||
func (s subscription) readPump() {
|
||||
c := s.conn
|
||||
defer func() {
|
||||
h.unregister <- s
|
||||
c.ws.Close()
|
||||
}()
|
||||
c.ws.SetReadLimit(maxMessageSize)
|
||||
c.ws.SetReadDeadline(time.Now().Add(pongWait))
|
||||
c.ws.SetPongHandler(func(string) error { c.ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
|
||||
for {
|
||||
messageType, msg, err := c.ws.ReadMessage()
|
||||
if err != nil {
|
||||
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
|
||||
log.Debugf("unexpected close: %v", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
h.broadcast <- message{messageChannel{msg, messageType}, s.room, c.ws.RemoteAddr().String()}
|
||||
}
|
||||
}
|
||||
|
||||
// write writes a message with the given message type and payload.
|
||||
func (c *connection) write(mt int, payload []byte) error {
|
||||
c.ws.SetWriteDeadline(time.Now().Add(writeWait))
|
||||
return c.ws.WriteMessage(mt, payload)
|
||||
}
|
||||
|
||||
// writePump pumps messages from the hub to the websocket connection.
|
||||
func (s *subscription) writePump() {
|
||||
c := s.conn
|
||||
ticker := time.NewTicker(pingPeriod)
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
c.ws.Close()
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case message, ok := <-c.send:
|
||||
if !ok {
|
||||
err := c.write(websocket.CloseMessage, []byte{})
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err := c.write(message.messageType, message.data); err != nil {
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
case <-ticker.C:
|
||||
if err := c.write(websocket.PingMessage, []byte{}); err != nil {
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// serveWs handles websocket requests from the peer.
|
||||
func serveWs(w http.ResponseWriter, r *http.Request) {
|
||||
ws, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
return
|
||||
}
|
||||
vals := r.URL.Query()
|
||||
room := "default"
|
||||
rooms, ok := vals["room"]
|
||||
if ok {
|
||||
room = rooms[0]
|
||||
}
|
||||
|
||||
c := &connection{send: make(chan messageChannel, 256), ws: ws}
|
||||
s := subscription{c, room}
|
||||
h.register <- s
|
||||
go s.writePump()
|
||||
s.readPump()
|
||||
}
|
105
src/relay/hub.go
105
src/relay/hub.go
|
@ -1,105 +0,0 @@
|
|||
package relay
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
log "github.com/cihub/seelog"
|
||||
)
|
||||
|
||||
type message struct {
|
||||
msg messageChannel
|
||||
room string
|
||||
remoteOrigin string
|
||||
}
|
||||
|
||||
type subscription struct {
|
||||
conn *connection
|
||||
room string
|
||||
}
|
||||
|
||||
// hub maintains the set of active connections and broadcasts messages to the
|
||||
// connections.
|
||||
type hub struct {
|
||||
// Registered connections.
|
||||
rooms roomMap
|
||||
|
||||
// Inbound messages from the connections.
|
||||
broadcast chan message
|
||||
|
||||
// Register requests from the connections.
|
||||
register chan subscription
|
||||
|
||||
// Unregister requests from connections.
|
||||
unregister chan subscription
|
||||
}
|
||||
|
||||
type roomMap struct {
|
||||
rooms map[string]map[*connection]bool
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
var h = hub{
|
||||
broadcast: make(chan message),
|
||||
register: make(chan subscription),
|
||||
unregister: make(chan subscription),
|
||||
rooms: roomMap{rooms: make(map[string]map[*connection]bool)},
|
||||
}
|
||||
|
||||
func (h *hub) run() {
|
||||
for {
|
||||
if stop {
|
||||
log.Debug("stopping hub")
|
||||
return
|
||||
}
|
||||
select {
|
||||
case s := <-h.register:
|
||||
log.Debugf("adding connection to %s", s.room)
|
||||
h.rooms.Lock()
|
||||
connections := h.rooms.rooms[s.room]
|
||||
if connections == nil {
|
||||
connections = make(map[*connection]bool)
|
||||
h.rooms.rooms[s.room] = connections
|
||||
}
|
||||
h.rooms.rooms[s.room][s.conn] = true
|
||||
if len(h.rooms.rooms) > 2 {
|
||||
// if more than three, close all of them
|
||||
for connection := range h.rooms.rooms[s.room] {
|
||||
close(connection.send)
|
||||
}
|
||||
log.Debugf("deleting room %s", s.room)
|
||||
delete(h.rooms.rooms, s.room)
|
||||
}
|
||||
h.rooms.Unlock()
|
||||
case s := <-h.unregister:
|
||||
// if one leaves, close all of them
|
||||
h.rooms.Lock()
|
||||
if _, ok := h.rooms.rooms[s.room]; ok {
|
||||
for connection := range h.rooms.rooms[s.room] {
|
||||
close(connection.send)
|
||||
}
|
||||
log.Debugf("deleting room %s", s.room)
|
||||
delete(h.rooms.rooms, s.room)
|
||||
}
|
||||
h.rooms.Unlock()
|
||||
case m := <-h.broadcast:
|
||||
h.rooms.Lock()
|
||||
connections := h.rooms.rooms[m.room]
|
||||
for c := range connections {
|
||||
if c.ws.RemoteAddr().String() == m.remoteOrigin {
|
||||
continue
|
||||
}
|
||||
select {
|
||||
case c.send <- m.msg:
|
||||
default:
|
||||
close(c.send)
|
||||
delete(connections, c)
|
||||
if len(connections) == 0 {
|
||||
log.Debugf("deleting room %s", m.room)
|
||||
delete(h.rooms.rooms, m.room)
|
||||
}
|
||||
}
|
||||
}
|
||||
h.rooms.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
package relay
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
log "github.com/cihub/seelog"
|
||||
"github.com/schollz/croc/src/logger"
|
||||
"github.com/schollz/croc/src/tcp"
|
||||
)
|
||||
|
||||
var DebugLevel string
|
||||
var stop bool
|
||||
|
||||
func Stop() {
|
||||
log.Debug("got stop signal")
|
||||
stop = true
|
||||
}
|
||||
|
||||
// Run is the async operation for running a server
|
||||
func Run(port string, tcpPorts []string) (err error) {
|
||||
logger.SetLogLevel(DebugLevel)
|
||||
|
||||
if len(tcpPorts) > 0 {
|
||||
for _, tcpPort := range tcpPorts {
|
||||
go tcp.Run(DebugLevel, tcpPort)
|
||||
}
|
||||
}
|
||||
|
||||
go h.run()
|
||||
log.Debug("running relay on " + port)
|
||||
m := http.NewServeMux()
|
||||
s := http.Server{Addr: ":" + port, Handler: m}
|
||||
m.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
|
||||
serveWs(w, r)
|
||||
})
|
||||
m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "ok")
|
||||
})
|
||||
go func() {
|
||||
for {
|
||||
if stop {
|
||||
s.Shutdown(context.Background())
|
||||
log.Debug("stopping http server")
|
||||
return
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
s.ListenAndServe()
|
||||
log.Debug("finished")
|
||||
return
|
||||
}
|
182
src/tcp/tcp.go
182
src/tcp/tcp.go
|
@ -1,20 +1,32 @@
|
|||
package tcp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"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"
|
||||
"github.com/schollz/croc/v6/src/comm"
|
||||
"github.com/schollz/croc/v6/src/logger"
|
||||
"github.com/schollz/croc/v6/src/models"
|
||||
)
|
||||
|
||||
type server struct {
|
||||
port string
|
||||
debugLevel string
|
||||
banner string
|
||||
rooms roomMap
|
||||
}
|
||||
|
||||
type roomInfo struct {
|
||||
receiver comm.Comm
|
||||
opened time.Time
|
||||
first *comm.Comm
|
||||
second *comm.Comm
|
||||
opened time.Time
|
||||
full bool
|
||||
}
|
||||
|
||||
type roomMap struct {
|
||||
|
@ -22,40 +34,49 @@ type roomMap struct {
|
|||
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()
|
||||
func Run(debugLevel, port string, banner ...string) (err error) {
|
||||
s := new(server)
|
||||
s.port = port
|
||||
s.debugLevel = debugLevel
|
||||
if len(banner) > 0 {
|
||||
s.banner = banner[0]
|
||||
}
|
||||
return s.start()
|
||||
}
|
||||
|
||||
func (s *server) start() (err error) {
|
||||
logger.SetLogLevel(s.debugLevel)
|
||||
s.rooms.Lock()
|
||||
s.rooms.rooms = make(map[string]roomInfo)
|
||||
s.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)
|
||||
s.rooms.Lock()
|
||||
for room := range s.rooms.rooms {
|
||||
if time.Since(s.rooms.rooms[room].opened) > 3*time.Hour {
|
||||
delete(s.rooms.rooms, room)
|
||||
}
|
||||
}
|
||||
rooms.Unlock()
|
||||
s.rooms.Unlock()
|
||||
}
|
||||
}()
|
||||
|
||||
err := run(port)
|
||||
err = s.run()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func run(port string) (err error) {
|
||||
log.Debugf("starting TCP server on " + port)
|
||||
server, err := net.Listen("tcp", "0.0.0.0:"+port)
|
||||
func (s *server) run() (err error) {
|
||||
log.Infof("starting TCP server on " + s.port)
|
||||
server, err := net.Listen("tcp", ":"+s.port)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error listening on :"+port)
|
||||
return errors.Wrap(err, "Error listening on :"+s.port)
|
||||
}
|
||||
defer server.Close()
|
||||
// spawn a new goroutine whenever a client connects
|
||||
|
@ -66,81 +87,115 @@ func run(port string) (err error) {
|
|||
}
|
||||
log.Debugf("client %s connected", connection.RemoteAddr().String())
|
||||
go func(port string, connection net.Conn) {
|
||||
errCommunication := clientCommuncation(port, comm.New(connection))
|
||||
errCommunication := s.clientCommuncation(port, comm.New(connection))
|
||||
if errCommunication != nil {
|
||||
log.Warnf("relay-%s: %s", connection.RemoteAddr().String(), errCommunication.Error())
|
||||
}
|
||||
}(port, connection)
|
||||
}(s.port, connection)
|
||||
}
|
||||
}
|
||||
|
||||
func clientCommuncation(port string, c comm.Comm) (err error) {
|
||||
func (s *server) clientCommuncation(port string, c *comm.Comm) (err error) {
|
||||
// send ok to tell client they are connected
|
||||
log.Debug("sending ok")
|
||||
err = c.Send("ok")
|
||||
banner := s.banner
|
||||
if len(banner) == 0 {
|
||||
banner = "ok"
|
||||
}
|
||||
log.Debugf("sending '%s'", banner)
|
||||
err = c.Send([]byte(banner + "|||" + c.Connection().RemoteAddr().String()))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// wait for client to tell me which room they want
|
||||
log.Debug("waiting for answer")
|
||||
room, err := c.Receive()
|
||||
roomBytes, err := c.Receive()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
room := string(roomBytes)
|
||||
|
||||
rooms.Lock()
|
||||
// first connection is always the receiver
|
||||
if _, ok := rooms.rooms[room]; !ok {
|
||||
rooms.rooms[room] = roomInfo{
|
||||
receiver: c,
|
||||
opened: time.Now(),
|
||||
s.rooms.Lock()
|
||||
// create the room if it is new
|
||||
if _, ok := s.rooms.rooms[room]; !ok {
|
||||
s.rooms.rooms[room] = roomInfo{
|
||||
first: c,
|
||||
opened: time.Now(),
|
||||
}
|
||||
rooms.Unlock()
|
||||
s.rooms.Unlock()
|
||||
// tell the client that they got the room
|
||||
err = c.Send("recipient")
|
||||
err = c.Send([]byte("ok"))
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
s.deleteRoom(room)
|
||||
return
|
||||
}
|
||||
log.Debug("recipient connected")
|
||||
log.Debugf("room %s has 1", room)
|
||||
return nil
|
||||
}
|
||||
log.Debug("sender connected")
|
||||
receiver := rooms.rooms[room].receiver
|
||||
rooms.Unlock()
|
||||
if s.rooms.rooms[room].full {
|
||||
s.rooms.Unlock()
|
||||
err = c.Send([]byte("room full"))
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
s.deleteRoom(room)
|
||||
return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
log.Debugf("room %s has 2", room)
|
||||
s.rooms.rooms[room] = roomInfo{
|
||||
first: s.rooms.rooms[room].first,
|
||||
second: c,
|
||||
opened: s.rooms.rooms[room].opened,
|
||||
full: true,
|
||||
}
|
||||
otherConnection := s.rooms.rooms[room].first
|
||||
s.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) {
|
||||
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)
|
||||
}(otherConnection, c, &wg)
|
||||
|
||||
// tell the sender everything is ready
|
||||
err = c.Send("sender")
|
||||
err = c.Send([]byte("ok"))
|
||||
if err != nil {
|
||||
s.deleteRoom(room)
|
||||
return
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// delete room
|
||||
rooms.Lock()
|
||||
log.Debugf("deleting room: %s", room)
|
||||
delete(rooms.rooms, room)
|
||||
rooms.Unlock()
|
||||
s.deleteRoom(room)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) deleteRoom(room string) {
|
||||
s.rooms.Lock()
|
||||
defer s.rooms.Unlock()
|
||||
if _, ok := s.rooms.rooms[room]; !ok {
|
||||
return
|
||||
}
|
||||
log.Debugf("deleting room: %s", room)
|
||||
s.rooms.rooms[room].first.Close()
|
||||
s.rooms.rooms[room].second.Close()
|
||||
s.rooms.rooms[room] = roomInfo{first: nil, second: nil}
|
||||
delete(s.rooms.rooms, room)
|
||||
|
||||
}
|
||||
|
||||
// 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)
|
||||
c := make(chan []byte, 1)
|
||||
|
||||
go func() {
|
||||
b := make([]byte, models.TCP_BUFFER_SIZE)
|
||||
|
@ -159,6 +214,7 @@ func chanFromConn(conn net.Conn) chan []byte {
|
|||
break
|
||||
}
|
||||
}
|
||||
log.Debug("exiting")
|
||||
}()
|
||||
|
||||
return c
|
||||
|
@ -186,3 +242,33 @@ func pipe(conn1 net.Conn, conn2 net.Conn) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ConnectToTCPServer(address, room string) (c *comm.Comm, banner string, ipaddr string, err error) {
|
||||
c, err = comm.NewConnection(address)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.Debug("waiting for first ok")
|
||||
data, err := c.Receive()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
banner = strings.Split(string(data), "|||")[0]
|
||||
ipaddr = strings.Split(string(data), "|||")[1]
|
||||
log.Debug("sending room")
|
||||
err = c.Send([]byte(room))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.Debug("waiting for room confirmation")
|
||||
data, err = c.Receive()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !bytes.Equal(data, []byte("ok")) {
|
||||
err = fmt.Errorf("got bad response: %s", data)
|
||||
return
|
||||
}
|
||||
log.Debug("all set")
|
||||
return
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package tcp
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTCP(t *testing.T) {
|
||||
go Run("debug", "8081", "8082")
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
c1, banner, ipaddr, err := ConnectToTCPServer("localhost:8081", "testRoom")
|
||||
assert.Equal(t, banner, "8082")
|
||||
assert.True(t, strings.HasPrefix(ipaddr, "127.0.0.1"))
|
||||
assert.Nil(t, err)
|
||||
c2, _, _, err := ConnectToTCPServer("localhost:8081", "testRoom")
|
||||
assert.Nil(t, err)
|
||||
_, _, _, err = ConnectToTCPServer("localhost:8081", "testRoom")
|
||||
assert.NotNil(t, err)
|
||||
|
||||
// try sending data
|
||||
assert.Nil(t, c1.Send([]byte("hello, c2")))
|
||||
data, err := c2.Receive()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []byte("hello, c2"), data)
|
||||
|
||||
assert.Nil(t, c2.Send([]byte("hello, c1")))
|
||||
data, err = c1.Receive()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []byte("hello, c1"), data)
|
||||
|
||||
c1.Close()
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package utils
|
||||
|
||||
import "os"
|
||||
|
||||
// Exists reports whether the named file or directory exists.
|
||||
func Exists(name string) bool {
|
||||
if _, err := os.Stat(name); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetInput(prompt string) string {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
fmt.Fprintf(os.Stderr, "%s", prompt)
|
||||
text, _ := reader.ReadString('\n')
|
||||
return strings.TrimSpace(text)
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// HashFile returns the md5 hash of a file
|
||||
func HashFile(fname string) (hash256 []byte, err error) {
|
||||
f, err := os.Open(fname)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
h := md5.New()
|
||||
if _, err = io.Copy(h, f); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
hash256 = h.Sum(nil)
|
||||
return
|
||||
}
|
||||
|
||||
// SHA256 returns sha256 sum
|
||||
func SHA256(s string) string {
|
||||
sha := sha256.New()
|
||||
sha.Write([]byte(s))
|
||||
return fmt.Sprintf("%x", sha.Sum(nil))
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func PublicIP() (ip string, err error) {
|
||||
resp, err := http.Get("https://canhazip.com")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
bodyBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ip = strings.TrimSpace(string(bodyBytes))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get preferred outbound ip of this machine
|
||||
func LocalIP() string {
|
||||
conn, err := net.Dial("udp", "8.8.8.8:80")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
localAddr := conn.LocalAddr().(*net.UDPAddr)
|
||||
|
||||
return localAddr.IP.String()
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetIP(t *testing.T) {
|
||||
fmt.Println(PublicIP())
|
||||
fmt.Println(LocalIP())
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"strings"
|
||||
|
||||
"github.com/schollz/mnemonicode"
|
||||
)
|
||||
|
||||
func GetRandomName() string {
|
||||
result := []string{}
|
||||
bs := make([]byte, 4)
|
||||
rand.Read(bs)
|
||||
result = mnemonicode.EncodeWordList(result, bs)
|
||||
return strings.Join(result, "-")
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"bytes"
|
||||
"net"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/schollz/mnemonicode"
|
||||
)
|
||||
|
||||
// Exists reports whether the named file or directory exists.
|
||||
func Exists(name string) bool {
|
||||
if _, err := os.Stat(name); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// GetInput returns the input with a given prompt
|
||||
func GetInput(prompt string) string {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
fmt.Fprintf(os.Stderr, "%s", prompt)
|
||||
text, _ := reader.ReadString('\n')
|
||||
return strings.TrimSpace(text)
|
||||
}
|
||||
|
||||
// HashFile returns the md5 hash of a file
|
||||
func HashFile(fname string) (hash256 []byte, err error) {
|
||||
f, err := os.Open(fname)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
h := md5.New()
|
||||
if _, err = io.Copy(h, f); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
hash256 = h.Sum(nil)
|
||||
return
|
||||
}
|
||||
|
||||
// SHA256 returns sha256 sum
|
||||
func SHA256(s string) string {
|
||||
sha := sha256.New()
|
||||
sha.Write([]byte(s))
|
||||
return fmt.Sprintf("%x", sha.Sum(nil))
|
||||
}
|
||||
|
||||
func PublicIP() (ip string, err error) {
|
||||
resp, err := http.Get("https://canhazip.com")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
bodyBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ip = strings.TrimSpace(string(bodyBytes))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get preferred outbound ip of this machine
|
||||
func LocalIP() string {
|
||||
conn, err := net.Dial("udp", "8.8.8.8:80")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
localAddr := conn.LocalAddr().(*net.UDPAddr)
|
||||
|
||||
return localAddr.IP.String()
|
||||
}
|
||||
|
||||
func GetRandomName() string {
|
||||
result := []string{}
|
||||
bs := make([]byte, 4)
|
||||
rand.Read(bs)
|
||||
result = mnemonicode.EncodeWordList(result, bs)
|
||||
return strings.Join(result, "-")
|
||||
}
|
||||
|
||||
func ByteCountDecimal(b int64) string {
|
||||
const unit = 1000
|
||||
if b < unit {
|
||||
return fmt.Sprintf("%d B", b)
|
||||
}
|
||||
div, exp := int64(unit), 0
|
||||
for n := b / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp])
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MissingChunks returns the positions of missing chunks.
|
||||
// If file doesn't exist, it returns an empty chunk list (all chunks).
|
||||
// If the file size is not the same as requested, it returns an empty chunk list (all chunks).
|
||||
func MissingChunks(fname string, fsize int64, chunkSize int) (chunks []int64) {
|
||||
fstat, err := os.Stat(fname)
|
||||
if fstat.Size() != fsize || err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
f, err := os.Open(fname)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
emptyBuffer := make([]byte, chunkSize)
|
||||
chunkNum := 0
|
||||
chunks = make([]int64, int64(math.Ceil(float64(fsize)/float64(chunkSize))))
|
||||
var currentLocation int64
|
||||
for {
|
||||
buffer := make([]byte, chunkSize)
|
||||
bytesread, err := f.Read(buffer)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if bytes.Equal(buffer[:bytesread], emptyBuffer[:bytesread]) {
|
||||
chunks[chunkNum] = currentLocation
|
||||
chunkNum++
|
||||
}
|
||||
currentLocation += int64(bytesread)
|
||||
}
|
||||
if chunkNum == 0 {
|
||||
chunks = []int64{}
|
||||
} else {
|
||||
chunks = chunks[:chunkNum]
|
||||
}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Exists reports whether the named file or directory exists.
|
||||
func TestExists(t *testing.T) {
|
||||
assert.True(t, Exists("utils.go"))
|
||||
}
|
||||
|
||||
// HashFile returns the md5 hash of a file
|
||||
func TestHashFile(t *testing.T) {
|
||||
b, err := HashFile("utils.go")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "314965d4170cd0c70bd2e4f6c977750a", fmt.Sprintf("%x", b))
|
||||
}
|
||||
|
||||
// SHA256 returns sha256 sum
|
||||
func TestSHA256(t *testing.T) {
|
||||
assert.Equal(t, "09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b", SHA256("hello, world"))
|
||||
}
|
||||
|
||||
func TestByteCountDecimal(t *testing.T) {
|
||||
assert.Equal(t, "10.0 kB", ByteCountDecimal(10000))
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
VERSION=$(shell git describe --tags --abbrev=0)
|
||||
LDFLAGS=-ldflags "-X main.Version=${VERSION}"
|
||||
|
||||
.PHONY: linux
|
||||
linux:
|
||||
GO111MODULE=off qtdeploy ${LDFLAGS} --tags='wincroc' --debug build desktop
|
||||
|
||||
.PHONY: fast
|
||||
fast:
|
||||
GO111MODULE=off qtdeploy ${LDFLAGS} --fast --tags='wincroc' --debug build desktop
|
||||
|
||||
windows:
|
||||
GO111MODULE=off qtdeploy ${LDFLAGS} --tags='wincroc' --debug --docker build windows_64_static
|
||||
|
||||
release: linux windows
|
||||
mv deploy/linux/win croc
|
||||
tar -czvf croc_${VERSION}_Linux-64bit_GUI.tar.gz croc
|
||||
mv deploy/windows/win.exe croc.exe
|
||||
zip croc_${VERSION}_Windows-64bit_GUI.zip croc.exe
|
||||
rm -rf dist
|
||||
mkdir dist
|
||||
mv *zip dist/
|
||||
mv *tar.gz dist/
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
REM getting the output of an executed command in a batch file is not trivial, we use a temp file for it
|
||||
git describe --tags --abbrev=0 > temp.txt
|
||||
set /P VERSION=< temp.txt
|
||||
echo %VERSION%
|
||||
del temp.txt
|
||||
|
||||
REM build a 32 bit Windows application, this way it will run on both 32 but and 64 bit Windows machines
|
||||
set GOARCH=386
|
||||
|
||||
REM -s and -w strip the program of debugging information, making it smaller
|
||||
REM -H=windowsgui makes the program not have a console window on start-up
|
||||
go build -ldflags="-s -w -H=windowsgui -X main.Version=%VERSION%" -o croc.exe
|
||||
|
||||
if errorlevel 1 pause
|
3028
src/win/icon.go
3028
src/win/icon.go
File diff suppressed because it is too large
Load Diff
BIN
src/win/icon.ico
BIN
src/win/icon.ico
Binary file not shown.
Before Width: | Height: | Size: 35 KiB |
|
@ -1 +0,0 @@
|
|||
IDI_ICON1 ICON DISCARDABLE "icon.ico"
|
Binary file not shown.
Binary file not shown.
230
src/win/main.go
230
src/win/main.go
|
@ -1,230 +0,0 @@
|
|||
// +build wincroc
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/schollz/croc/src/cli"
|
||||
"github.com/schollz/croc/src/croc"
|
||||
"github.com/schollz/croc/src/utils"
|
||||
"github.com/skratchdot/open-golang/open"
|
||||
"github.com/therecipe/qt/core"
|
||||
"github.com/therecipe/qt/widgets"
|
||||
)
|
||||
|
||||
type CustomLabel struct {
|
||||
widgets.QLabel
|
||||
|
||||
_ func(string) `signal:"updateTextFromGoroutine,auto(this.QLabel.setText)"` //TODO: support this.setText as well
|
||||
}
|
||||
|
||||
var Version string
|
||||
|
||||
func main() {
|
||||
if len(os.Args) > 1 {
|
||||
cli.Run()
|
||||
return
|
||||
}
|
||||
|
||||
var isWorking bool
|
||||
app := widgets.NewQApplication(len(os.Args), os.Args)
|
||||
|
||||
window := widgets.NewQMainWindow(nil, 0)
|
||||
if runtime.GOOS == "windows" {
|
||||
window.SetFixedSize2(300, 150)
|
||||
window.SetWindowTitle("croc " + Version)
|
||||
} else {
|
||||
window.SetFixedSize2(400, 150)
|
||||
window.SetWindowTitle("🐊📦 croc " + Version)
|
||||
}
|
||||
|
||||
widget := widgets.NewQWidget(nil, 0)
|
||||
widget.SetLayout(widgets.NewQVBoxLayout())
|
||||
window.SetCentralWidget(widget)
|
||||
|
||||
labels := make([]*CustomLabel, 3)
|
||||
for i := range labels {
|
||||
label := NewCustomLabel(nil, 0)
|
||||
label.SetAlignment(core.Qt__AlignCenter)
|
||||
widget.Layout().AddWidget(label)
|
||||
labels[i] = label
|
||||
}
|
||||
labels[0].SetText("secure data transfer")
|
||||
labels[1].SetText("Click 'Send' or 'Receive' to start")
|
||||
|
||||
button := widgets.NewQPushButton2("Send", nil)
|
||||
button.ConnectClicked(func(bool) {
|
||||
if isWorking {
|
||||
dialog("Can only do one send or receive at a time")
|
||||
return
|
||||
}
|
||||
isWorking = true
|
||||
|
||||
var fileDialog = widgets.NewQFileDialog2(window, "Open file to send...", "", "")
|
||||
fileDialog.SetAcceptMode(widgets.QFileDialog__AcceptOpen)
|
||||
fileDialog.SetFileMode(widgets.QFileDialog__AnyFile)
|
||||
if fileDialog.Exec() != int(widgets.QDialog__Accepted) {
|
||||
isWorking = false
|
||||
return
|
||||
}
|
||||
var fn = fileDialog.SelectedFiles()[0]
|
||||
if len(fn) == 0 {
|
||||
dialog(fmt.Sprintf("No file selected"))
|
||||
isWorking = false
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
cr := croc.Init(false)
|
||||
done := make(chan bool)
|
||||
codePhrase := utils.GetRandomName()
|
||||
_, fname := filepath.Split(fn)
|
||||
labels[0].UpdateTextFromGoroutine(fmt.Sprintf("Sending '%s'", fname))
|
||||
labels[1].UpdateTextFromGoroutine(fmt.Sprintf("Code phrase: %s", codePhrase))
|
||||
|
||||
go func(done chan bool) {
|
||||
for {
|
||||
if cr.OtherIP != "" && cr.FileInfo.SentName != "" {
|
||||
bytesString := humanize.Bytes(uint64(cr.FileInfo.Size))
|
||||
fileOrFolder := "file"
|
||||
if cr.FileInfo.IsDir {
|
||||
fileOrFolder = "folder"
|
||||
}
|
||||
labels[0].UpdateTextFromGoroutine(fmt.Sprintf("Sending %s %s '%s' to %s", bytesString, fileOrFolder, cr.FileInfo.SentName, cr.OtherIP))
|
||||
}
|
||||
if cr.Bar != nil {
|
||||
barState := cr.Bar.State()
|
||||
labels[1].UpdateTextFromGoroutine(fmt.Sprintf("%2.1f%% [%2.0fs:%2.0fs]", barState.CurrentPercent*100, barState.SecondsSince, barState.SecondsLeft))
|
||||
}
|
||||
labels[2].UpdateTextFromGoroutine(cr.StateString)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
select {
|
||||
case _ = <-done:
|
||||
labels[2].UpdateTextFromGoroutine(cr.StateString)
|
||||
return
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}(done)
|
||||
|
||||
cr.Send(fn, codePhrase)
|
||||
done <- true
|
||||
isWorking = false
|
||||
}()
|
||||
})
|
||||
widget.Layout().AddWidget(button)
|
||||
|
||||
receiveButton := widgets.NewQPushButton2("Receive", nil)
|
||||
receiveButton.ConnectClicked(func(bool) {
|
||||
if isWorking {
|
||||
dialog("Can only do one send or receive at a time")
|
||||
return
|
||||
}
|
||||
labels[1].SetText("")
|
||||
labels[2].SetText("please wait...")
|
||||
isWorking = true
|
||||
defer func() {
|
||||
isWorking = false
|
||||
}()
|
||||
|
||||
// determine the folder to save the file
|
||||
var folderDialog = widgets.NewQFileDialog2(window, "Open folder to receive file...", "", "")
|
||||
folderDialog.SetAcceptMode(widgets.QFileDialog__AcceptOpen)
|
||||
folderDialog.SetFileMode(widgets.QFileDialog__DirectoryOnly)
|
||||
if folderDialog.Exec() != int(widgets.QDialog__Accepted) {
|
||||
return
|
||||
}
|
||||
var fn = folderDialog.SelectedFiles()[0]
|
||||
if len(fn) == 0 {
|
||||
labels[2].SetText(fmt.Sprintf("No folder selected"))
|
||||
return
|
||||
}
|
||||
|
||||
var codePhrase = widgets.QInputDialog_GetText(window, "croc", "Enter code phrase:",
|
||||
widgets.QLineEdit__Normal, "", true, core.Qt__Dialog, core.Qt__ImhNone)
|
||||
if len(codePhrase) < 3 {
|
||||
labels[2].SetText(fmt.Sprintf("Invalid codephrase: '%s'", codePhrase))
|
||||
return
|
||||
}
|
||||
|
||||
cr := croc.Init(false)
|
||||
cr.WindowRecipientPrompt = true
|
||||
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
// change into the receiving directory
|
||||
cwd, _ := os.Getwd()
|
||||
defer os.Chdir(cwd)
|
||||
os.Chdir(fn)
|
||||
err := cr.Receive(codePhrase)
|
||||
if err == nil {
|
||||
open.Run(fn)
|
||||
}
|
||||
done <- true
|
||||
done <- true
|
||||
isWorking = false
|
||||
}()
|
||||
go func() {
|
||||
for {
|
||||
if cr.Bar != nil {
|
||||
barState := cr.Bar.State()
|
||||
labels[1].UpdateTextFromGoroutine(fmt.Sprintf("%2.1f%% [%2.0fs:%2.0fs]", barState.CurrentPercent*100, barState.SecondsSince, barState.SecondsLeft))
|
||||
}
|
||||
if cr.StateString != "" {
|
||||
labels[2].UpdateTextFromGoroutine(cr.StateString)
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
select {
|
||||
case _ = <-done:
|
||||
labels[2].UpdateTextFromGoroutine(cr.StateString)
|
||||
return
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
if cr.WindowReceivingString != "" {
|
||||
var question = widgets.QMessageBox_Question(window, "croc", fmt.Sprintf("%s?", cr.WindowReceivingString), widgets.QMessageBox__Yes|widgets.QMessageBox__No, 0)
|
||||
if question == widgets.QMessageBox__Yes {
|
||||
cr.WindowRecipientAccept = true
|
||||
labels[0].SetText(cr.WindowReceivingString)
|
||||
} else {
|
||||
cr.WindowRecipientAccept = false
|
||||
labels[2].SetText("canceled")
|
||||
}
|
||||
cr.WindowRecipientPrompt = false
|
||||
cr.WindowReceivingString = ""
|
||||
break
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
select {
|
||||
case _ = <-done:
|
||||
labels[2].SetText(cr.StateString)
|
||||
return
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
widget.Layout().AddWidget(receiveButton)
|
||||
|
||||
window.Show()
|
||||
app.Exec()
|
||||
}
|
||||
|
||||
func dialog(s string) {
|
||||
var info = widgets.NewQMessageBox(nil)
|
||||
info.SetWindowTitle("Info")
|
||||
info.SetText(s)
|
||||
info.Exec()
|
||||
}
|
|
@ -1,250 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/gonutz/w32"
|
||||
"github.com/gonutz/wui"
|
||||
"github.com/schollz/croc/src/cli"
|
||||
"github.com/schollz/croc/src/croc"
|
||||
"github.com/schollz/croc/src/utils"
|
||||
"github.com/skratchdot/open-golang/open"
|
||||
)
|
||||
|
||||
var Version string
|
||||
|
||||
func main() {
|
||||
if len(os.Args) > 1 {
|
||||
cli.Run()
|
||||
return
|
||||
}
|
||||
|
||||
var isWorking bool
|
||||
|
||||
font, _ := wui.NewFont(wui.FontDesc{
|
||||
Name: "Tahoma",
|
||||
Height: -11,
|
||||
})
|
||||
|
||||
window := wui.NewWindow()
|
||||
window.SetFont(font)
|
||||
window.SetIconFromMem(icon)
|
||||
window.SetStyle(w32.WS_OVERLAPPED | w32.WS_CAPTION | w32.WS_SYSMENU | w32.WS_MINIMIZEBOX)
|
||||
if runtime.GOOS == "windows" {
|
||||
window.SetClientSize(300, 150)
|
||||
window.SetTitle("croc " + Version)
|
||||
} else {
|
||||
window.SetClientSize(400, 150)
|
||||
window.SetTitle("🐊📦 croc " + Version)
|
||||
}
|
||||
|
||||
labels := make([]*wui.Label, 3)
|
||||
for i := range labels {
|
||||
label := wui.NewLabel()
|
||||
label.SetCenterAlign()
|
||||
label.SetBounds(0, 10+i*20, window.ClientWidth(), 20)
|
||||
window.Add(label)
|
||||
labels[i] = label
|
||||
}
|
||||
labels[0].SetText("secure data transfer")
|
||||
labels[1].SetText("Click 'Send' or 'Receive' to start")
|
||||
|
||||
button := wui.NewButton()
|
||||
button.SetText("Send")
|
||||
window.Add(button)
|
||||
button.SetBounds(10, window.ClientHeight()-70, window.ClientWidth()-20, 25)
|
||||
button.SetOnClick(func() {
|
||||
if isWorking {
|
||||
wui.MessageBoxError(window, "Error", "Can only do one send or receive at a time")
|
||||
return
|
||||
}
|
||||
isWorking = true
|
||||
|
||||
fileDialog := wui.NewFileOpenDialog()
|
||||
fileDialog.SetTitle("Open file to send...")
|
||||
accepted, fn := fileDialog.ExecuteSingleSelection(window)
|
||||
if !accepted {
|
||||
isWorking = false
|
||||
return
|
||||
}
|
||||
if fn == "" {
|
||||
wui.MessageBoxError(window, "Error", "No file selected")
|
||||
isWorking = false
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
cr := croc.Init(false)
|
||||
done := make(chan bool)
|
||||
codePhrase := utils.GetRandomName()
|
||||
_, fname := filepath.Split(fn)
|
||||
labels[0].SetText(fmt.Sprintf("Sending '%s'", fname))
|
||||
labels[1].SetText(fmt.Sprintf("Code phrase: %s", codePhrase))
|
||||
|
||||
go func(done chan bool) {
|
||||
for {
|
||||
if cr.OtherIP != "" && cr.FileInfo.SentName != "" {
|
||||
bytesString := humanize.Bytes(uint64(cr.FileInfo.Size))
|
||||
fileOrFolder := "file"
|
||||
if cr.FileInfo.IsDir {
|
||||
fileOrFolder = "folder"
|
||||
}
|
||||
labels[0].SetText(fmt.Sprintf("Sending %s %s '%s' to %s", bytesString, fileOrFolder, cr.FileInfo.SentName, cr.OtherIP))
|
||||
}
|
||||
if cr.Bar != nil {
|
||||
barState := cr.Bar.State()
|
||||
labels[1].SetText(fmt.Sprintf("%2.1f%% [%2.0fs:%2.0fs]", barState.CurrentPercent*100, barState.SecondsSince, barState.SecondsLeft))
|
||||
}
|
||||
labels[2].SetText(cr.StateString)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
select {
|
||||
case _ = <-done:
|
||||
labels[2].SetText(cr.StateString)
|
||||
return
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}(done)
|
||||
|
||||
cr.Send(fn, codePhrase)
|
||||
done <- true
|
||||
isWorking = false
|
||||
}()
|
||||
})
|
||||
|
||||
receiveButton := wui.NewButton()
|
||||
receiveButton.SetText("Receive")
|
||||
window.Add(receiveButton)
|
||||
receiveButton.SetBounds(10, window.ClientHeight()-35, window.ClientWidth()-20, 25)
|
||||
receiveButton.SetOnClick(func() {
|
||||
if isWorking {
|
||||
wui.MessageBoxError(window, "Error", "Can only do one send or receive at a time")
|
||||
return
|
||||
}
|
||||
labels[1].SetText("")
|
||||
labels[2].SetText("please wait...")
|
||||
isWorking = true
|
||||
defer func() {
|
||||
isWorking = false
|
||||
}()
|
||||
|
||||
// determine the folder to save the file
|
||||
folderDialog := wui.NewFolderSelectDialog()
|
||||
folderDialog.SetTitle("Open folder to receive file...")
|
||||
accepted, fn := folderDialog.Execute(window)
|
||||
if !accepted {
|
||||
return
|
||||
}
|
||||
if len(fn) == 0 {
|
||||
labels[2].SetText(fmt.Sprintf("No folder selected"))
|
||||
return
|
||||
}
|
||||
|
||||
passDlg := wui.NewDialogWindow()
|
||||
passDlg.SetTitle("Enter code phrase")
|
||||
passDlg.SetClientSize(window.ClientWidth()-20, 40)
|
||||
pass := wui.NewEditLine()
|
||||
passDlg.Add(pass)
|
||||
pass.SetPassword(true)
|
||||
pass.SetBounds(10, 10, passDlg.ClientWidth()-20, 20)
|
||||
var passAccepted bool
|
||||
passDlg.SetShortcut(wui.ShortcutKeys{Key: w32.VK_RETURN}, func() {
|
||||
passAccepted = true
|
||||
passDlg.Close()
|
||||
})
|
||||
passDlg.SetShortcut(wui.ShortcutKeys{Key: w32.VK_ESCAPE}, func() {
|
||||
passDlg.Close()
|
||||
})
|
||||
var codePhrase string
|
||||
passDlg.SetOnShow(func() {
|
||||
passDlg.SetX(window.X() + (window.Width()-passDlg.Width())/2)
|
||||
passDlg.SetY(window.Y() + (window.Height()-passDlg.Height())/2)
|
||||
pass.Focus()
|
||||
})
|
||||
passDlg.SetOnClose(func() {
|
||||
if passAccepted {
|
||||
codePhrase = pass.Text()
|
||||
}
|
||||
})
|
||||
passDlg.ShowModal(window)
|
||||
passDlg.Destroy()
|
||||
if len(codePhrase) < 3 {
|
||||
labels[2].SetText(fmt.Sprintf("Invalid codephrase: '%s'", codePhrase))
|
||||
return
|
||||
}
|
||||
|
||||
cr := croc.Init(false)
|
||||
cr.WindowRecipientPrompt = true
|
||||
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
// change into the receiving directory
|
||||
cwd, _ := os.Getwd()
|
||||
defer os.Chdir(cwd)
|
||||
os.Chdir(fn)
|
||||
err := cr.Receive(codePhrase)
|
||||
if err == nil {
|
||||
open.Run(fn)
|
||||
}
|
||||
done <- true
|
||||
done <- true
|
||||
isWorking = false
|
||||
}()
|
||||
go func() {
|
||||
for {
|
||||
if cr.Bar != nil {
|
||||
barState := cr.Bar.State()
|
||||
labels[1].SetText(fmt.Sprintf("%2.1f%% [%2.0fs:%2.0fs]", barState.CurrentPercent*100, barState.SecondsSince, barState.SecondsLeft))
|
||||
}
|
||||
if cr.StateString != "" {
|
||||
labels[2].SetText(cr.StateString)
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
select {
|
||||
case _ = <-done:
|
||||
labels[2].SetText(cr.StateString)
|
||||
return
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
if cr.WindowReceivingString != "" {
|
||||
question := wui.MessageBoxYesNo(
|
||||
window,
|
||||
"croc",
|
||||
fmt.Sprintf("%s?", cr.WindowReceivingString),
|
||||
)
|
||||
if question {
|
||||
cr.WindowRecipientAccept = true
|
||||
labels[0].SetText(cr.WindowReceivingString)
|
||||
} else {
|
||||
cr.WindowRecipientAccept = false
|
||||
labels[2].SetText("canceled")
|
||||
}
|
||||
cr.WindowRecipientPrompt = false
|
||||
cr.WindowReceivingString = ""
|
||||
break
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
select {
|
||||
case _ = <-done:
|
||||
labels[2].SetText(cr.StateString)
|
||||
return
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
window.Show()
|
||||
}
|
|
@ -1,230 +0,0 @@
|
|||
package zipper
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"compress/flate"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
log "github.com/cihub/seelog"
|
||||
"github.com/schollz/croc/src/logger"
|
||||
)
|
||||
|
||||
var DebugLevel string
|
||||
|
||||
func init() {
|
||||
DebugLevel = "info"
|
||||
}
|
||||
|
||||
// UnzipFile will unzip the src directory into the dest
|
||||
func UnzipFile(src, dest string) (err error) {
|
||||
logger.SetLogLevel(DebugLevel)
|
||||
|
||||
r, err := zip.OpenReader(src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
for _, f := range r.File {
|
||||
var rc io.ReadCloser
|
||||
rc, err = f.Open()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer rc.Close()
|
||||
|
||||
// Store filename/path for returning and using later on
|
||||
fpath := filepath.Join(dest, f.Name)
|
||||
log.Debugf("unzipping %s", fpath)
|
||||
fpath = filepath.FromSlash(fpath)
|
||||
|
||||
if f.FileInfo().IsDir() {
|
||||
|
||||
// Make Folder
|
||||
os.MkdirAll(fpath, os.ModePerm)
|
||||
|
||||
} else {
|
||||
|
||||
// Make File
|
||||
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var outFile *os.File
|
||||
outFile, err = os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = io.Copy(outFile, rc)
|
||||
|
||||
// Close the file without defer to close before next iteration of loop
|
||||
outFile.Close()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
log.Debugf("unzipped %s to %s", src, dest)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ZipFiles will zip all the files and the folders as if they were in the same directory
|
||||
func ZipFiles(fnames []string, compress bool) (writtenFilename string, err error) {
|
||||
logger.SetLogLevel(DebugLevel)
|
||||
if len(fnames) == 0 {
|
||||
err = fmt.Errorf("must provide files to zip")
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("zipping %s with compression? %v", fnames, compress)
|
||||
writtenFilename = fmt.Sprintf("%d_files.croc.zip", len(fnames))
|
||||
err = makeZip(writtenFilename, fnames, compress)
|
||||
return
|
||||
}
|
||||
|
||||
// ZipFile will zip the folder
|
||||
func ZipFile(fname string, compress bool) (writtenFilename string, err error) {
|
||||
logger.SetLogLevel(DebugLevel)
|
||||
|
||||
// get path to file and the filename
|
||||
_, filename := filepath.Split(fname)
|
||||
writtenFilename = filename + ".croc.zip"
|
||||
err = makeZip(writtenFilename, []string{fname}, compress)
|
||||
return
|
||||
}
|
||||
|
||||
func makeZip(writtenFilename string, fnames []string, compress bool) (err error) {
|
||||
log.Debugf("creating file: %s", writtenFilename)
|
||||
f, err := os.Create(writtenFilename)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
zipWriter := zip.NewWriter(f)
|
||||
zipWriter.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {
|
||||
if compress {
|
||||
return flate.NewWriter(out, flate.BestSpeed)
|
||||
} else {
|
||||
return flate.NewWriter(out, flate.NoCompression)
|
||||
}
|
||||
})
|
||||
defer zipWriter.Close()
|
||||
|
||||
err = zipFiles(fnames, compress, zipWriter)
|
||||
if err == nil {
|
||||
log.Debugf("wrote zip file to %s", writtenFilename)
|
||||
} else {
|
||||
log.Error(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func zipFiles(fnames []string, compress bool, zipWriter *zip.Writer) (err error) {
|
||||
for _, fname := range fnames {
|
||||
// get absolute filename
|
||||
absPath, err := filepath.Abs(filepath.Clean(fname))
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
absPath = filepath.ToSlash(absPath)
|
||||
|
||||
// get path to file and the filename
|
||||
fpath, fname := filepath.Split(absPath)
|
||||
|
||||
// Get the file information for the target
|
||||
log.Debugf("checking %s", absPath)
|
||||
ftarget, err := os.Open(absPath)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
defer ftarget.Close()
|
||||
info, err := ftarget.Stat()
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// write header informaiton
|
||||
header, err := zip.FileInfoHeader(info)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
var writer io.Writer
|
||||
if info.IsDir() {
|
||||
baseDir := filepath.Clean(path.Join(fpath, fname))
|
||||
log.Debugf("walking base dir: %s", baseDir)
|
||||
filepath.Walk(baseDir, func(curpath string, info os.FileInfo, err error) error {
|
||||
curpath = filepath.Clean(curpath)
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
header, err := zip.FileInfoHeader(info)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if baseDir != "" {
|
||||
header.Name = path.Join(fname, strings.TrimPrefix(curpath, baseDir))
|
||||
}
|
||||
header.Name = filepath.ToSlash(filepath.Clean(header.Name))
|
||||
log.Debug(header.Name)
|
||||
|
||||
if info.IsDir() {
|
||||
header.Name += "/"
|
||||
} else {
|
||||
header.Method = zip.Deflate
|
||||
}
|
||||
|
||||
writer, err = zipWriter.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
file, err := os.Open(curpath)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
_, err = io.Copy(writer, file)
|
||||
return err
|
||||
})
|
||||
} else {
|
||||
writer, err = zipWriter.CreateHeader(header)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(writer, ftarget)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
package zipper
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
log "github.com/cihub/seelog"
|
||||
"github.com/schollz/croc/src/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestZip(t *testing.T) {
|
||||
defer log.Flush()
|
||||
DebugLevel = "debug"
|
||||
writtenFilename1, err := ZipFile("../croc", true)
|
||||
assert.Nil(t, err)
|
||||
err = UnzipFile(writtenFilename1, ".")
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, utils.Exists("croc"))
|
||||
|
||||
writtenFilename2, err := ZipFile("../../README.md", false)
|
||||
assert.Nil(t, err)
|
||||
err = UnzipFile(writtenFilename2, ".")
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, utils.Exists("README.md"))
|
||||
|
||||
os.Remove("README.md")
|
||||
os.RemoveAll("croc")
|
||||
os.Remove(writtenFilename1)
|
||||
os.Remove(writtenFilename2)
|
||||
}
|
||||
|
||||
func TestZipFiles(t *testing.T) {
|
||||
defer log.Flush()
|
||||
DebugLevel = "debug"
|
||||
writtenFilename, err := ZipFiles([]string{"../../LICENSE", "../win/Makefile", "../utils"}, true)
|
||||
assert.Nil(t, err)
|
||||
err = UnzipFile(writtenFilename, "zipfilestest")
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, utils.Exists("zipfilestest/LICENSE"))
|
||||
assert.True(t, utils.Exists("zipfilestest/Makefile"))
|
||||
assert.True(t, utils.Exists("zipfilestest/utils/exists.go"))
|
||||
os.RemoveAll("zipfilestest")
|
||||
err = os.Remove(path.Join(".", writtenFilename))
|
||||
assert.Nil(t, err)
|
||||
}
|
Loading…
Reference in New Issue