Implement config files
This commit is contained in:
parent
029b16a1a3
commit
9bd7b30222
282
config.go
282
config.go
@ -1,278 +1,66 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/tar"
|
"errors"
|
||||||
"encoding/json"
|
"github.com/pelletier/go-toml"
|
||||||
"github.com/pkg/browser"
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/url"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create config type to store action type and data
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
ActionType string
|
Receiver ReceiverConfig
|
||||||
ActionData string
|
Sender SenderConfig
|
||||||
|
Targets map[string]map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instantiate and return a new Config struct
|
type ReceiverConfig struct {
|
||||||
func NewConfig(actionType string, actionData string) *Config {
|
DestDir string `toml:"destinationDirectory"`
|
||||||
return &Config{ActionType: actionType, ActionData: actionData}
|
SkipZeroconf bool
|
||||||
|
WorkDir string `toml:"workingDirectory"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (config *Config) Validate() {
|
type SenderConfig struct {
|
||||||
if config.ActionType == "url" {
|
WorkDir string `toml:"workingDirectory"`
|
||||||
// Parse URL in config
|
|
||||||
urlParser, err := url.Parse(config.ActionData)
|
|
||||||
// If there was an error parsing
|
|
||||||
if err != nil {
|
|
||||||
// Alert user of invalid url
|
|
||||||
log.Fatal().Err(err).Msg("Invalid URL")
|
|
||||||
// If scheme is not detected
|
|
||||||
} else if urlParser.Scheme == "" {
|
|
||||||
// Alert user of invalid scheme
|
|
||||||
log.Fatal().Msg("Invalid URL scheme")
|
|
||||||
// If host is not detected
|
|
||||||
} else if urlParser.Host == "" {
|
|
||||||
// Alert user of invalid host
|
|
||||||
log.Fatal().Msg("Invalid URL host")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create config file
|
func GetConfigPath() string {
|
||||||
func (config *Config) CreateFile(dir string) {
|
|
||||||
// Use ConsoleWriter logger
|
// Use ConsoleWriter logger
|
||||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
|
||||||
// Create config file at given directory
|
configLocations := []string{"~/.config/opensend.toml", "/etc/opensend.toml"}
|
||||||
configFile, err := os.Create(dir + "/config.json")
|
for _, configLocation := range configLocations {
|
||||||
if err != nil {
|
expandedPath := ExpandPath(configLocation)
|
||||||
log.Fatal().Err(err).Msg("Error creating config file")
|
if _, err := os.Stat(expandedPath); errors.Is(err, os.ErrNotExist) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return expandedPath
|
||||||
}
|
}
|
||||||
// Close config file at the end of this function
|
return ""
|
||||||
defer configFile.Close()
|
|
||||||
// Marshal given Config struct into a []byte
|
|
||||||
jsonData, err := json.Marshal(config)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Error encoding JSON")
|
|
||||||
}
|
|
||||||
// Write []byte to previously created config file
|
|
||||||
bytesWritten, err := configFile.Write(jsonData)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Error writing JSON to file")
|
|
||||||
}
|
|
||||||
// Log bytes written
|
|
||||||
log.Info().Str("file", "config.json").Msg("Wrote " + strconv.Itoa(bytesWritten) + " bytes")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect all required files into given directory
|
func NewConfig(path string) *Config {
|
||||||
func (config *Config) CollectFiles(dir string) {
|
|
||||||
// Use ConsoleWriter logger
|
// Use ConsoleWriter logger
|
||||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
|
||||||
// If action type is file
|
newConfig := &Config{}
|
||||||
if config.ActionType == "file" {
|
newConfig.SetDefaults()
|
||||||
// Open file path in config.ActionData
|
if path != "" {
|
||||||
src, err := os.Open(config.ActionData)
|
confData, err := ioutil.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Error opening file from config")
|
log.Fatal().Err(err).Msg("Error reading config")
|
||||||
}
|
}
|
||||||
// Close source file at the end of this function
|
err = toml.Unmarshal(confData, newConfig)
|
||||||
defer src.Close()
|
|
||||||
// Create new file with the same name at given directory
|
|
||||||
dst, err := os.Create(dir + "/" + filepath.Base(config.ActionData))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Error creating file")
|
log.Fatal().Err(err).Msg("Error unmarshalling toml")
|
||||||
}
|
}
|
||||||
// Close new file at the end of this function
|
|
||||||
defer dst.Close()
|
|
||||||
// Copy data from source file to destination file
|
|
||||||
_, err = io.Copy(dst, src)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Error copying data to file")
|
|
||||||
}
|
|
||||||
// Replace file path in config.ActionData with file name
|
|
||||||
config.ActionData = filepath.Base(config.ActionData)
|
|
||||||
} else if config.ActionType == "dir" {
|
|
||||||
// Create tar archive
|
|
||||||
tarFile, err := os.Create(dir + "/" + filepath.Base(config.ActionData) + ".tar")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Error creating file")
|
|
||||||
}
|
|
||||||
// Close tar file at the end of this function
|
|
||||||
defer tarFile.Close()
|
|
||||||
// Create writer for tar archive
|
|
||||||
tarArchiver := tar.NewWriter(tarFile)
|
|
||||||
// Close archiver at the end of this function
|
|
||||||
defer tarArchiver.Close()
|
|
||||||
// Walk given directory
|
|
||||||
err = filepath.Walk(config.ActionData, func(path string, info os.FileInfo, err error) error {
|
|
||||||
// Return if error walking
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Skip if file is not normal mode
|
|
||||||
if !info.Mode().IsRegular() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Create tar header for file
|
|
||||||
header, err := tar.FileInfoHeader(info, info.Name())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Change header name to reflect decompressed filepath
|
|
||||||
header.Name = strings.TrimPrefix(strings.ReplaceAll(path, config.ActionData, ""), string(filepath.Separator))
|
|
||||||
// Write header to archive
|
|
||||||
if err := tarArchiver.WriteHeader(header); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Open source file
|
|
||||||
src, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Close source file at the end of this function
|
|
||||||
defer src.Close()
|
|
||||||
// Copy source bytes to tar archive
|
|
||||||
if _, err := io.Copy(tarArchiver, src); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Return at the end of the function
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Error creating tar archive")
|
|
||||||
}
|
|
||||||
// Set config data to base path for receiver
|
|
||||||
config.ActionData = filepath.Base(config.ActionData)
|
|
||||||
}
|
}
|
||||||
|
return newConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read config file at given file path
|
func (config *Config) SetDefaults() {
|
||||||
func (config *Config) ReadFile(filePath string) {
|
config.Receiver.DestDir = ExpandPath("~/Downloads")
|
||||||
// Use ConsoleWriter logger
|
config.Receiver.WorkDir = ExpandPath("~/.opensend")
|
||||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
|
config.Receiver.SkipZeroconf = false
|
||||||
// Read file at filePath
|
config.Sender.WorkDir = ExpandPath("~/.opensend")
|
||||||
fileData, err := ioutil.ReadFile(filePath)
|
config.Targets = map[string]map[string]string{}
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Error reading config file")
|
|
||||||
}
|
|
||||||
// Unmarshal data from JSON into config struct
|
|
||||||
err = json.Unmarshal(fileData, config)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Error decoding JSON")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute action specified in config
|
|
||||||
func (config *Config) ExecuteAction(srcDir string, destDir string) {
|
|
||||||
// Use ConsoleWriter logger
|
|
||||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
|
|
||||||
// If action is file
|
|
||||||
if config.ActionType == "file" {
|
|
||||||
// Open file from config at given directory
|
|
||||||
src, err := os.Open(srcDir + "/" + config.ActionData)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Error reading file from config")
|
|
||||||
}
|
|
||||||
// Close source file at the end of this function
|
|
||||||
defer src.Close()
|
|
||||||
// Create file in user's Downloads directory
|
|
||||||
dst, err := os.Create(filepath.Clean(destDir) + "/" + config.ActionData)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Error creating file")
|
|
||||||
}
|
|
||||||
// Close destination file at the end of this function
|
|
||||||
defer dst.Close()
|
|
||||||
// Copy data from source file to destination file
|
|
||||||
_, err = io.Copy(dst, src)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Error copying data to file")
|
|
||||||
}
|
|
||||||
// If action is url
|
|
||||||
} else if config.ActionType == "url" {
|
|
||||||
// Parse received URL
|
|
||||||
urlParser, err := url.Parse(config.ActionData)
|
|
||||||
// If there was an error parsing
|
|
||||||
if err != nil {
|
|
||||||
// Alert user of invalid url
|
|
||||||
log.Fatal().Err(err).Msg("Invalid URL")
|
|
||||||
// If scheme is not detected
|
|
||||||
} else if urlParser.Scheme == "" {
|
|
||||||
// Alert user of invalid scheme
|
|
||||||
log.Fatal().Msg("Invalid URL scheme")
|
|
||||||
// If host is not detected
|
|
||||||
} else if urlParser.Host == "" {
|
|
||||||
// Alert user of invalid host
|
|
||||||
log.Fatal().Msg("Invalid URL host")
|
|
||||||
}
|
|
||||||
// Attempt to open URL in browser
|
|
||||||
err = browser.OpenURL(config.ActionData)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Error opening browser")
|
|
||||||
}
|
|
||||||
// If action is dir
|
|
||||||
} else if config.ActionType == "dir" {
|
|
||||||
// Set destination directory to ~/Downloads/{dir name}
|
|
||||||
dstDir := filepath.Clean(destDir) + "/" + config.ActionData
|
|
||||||
// Try to create destination directory
|
|
||||||
err := os.MkdirAll(dstDir, 0755)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Error creating directory")
|
|
||||||
}
|
|
||||||
// Try to open tar archive file
|
|
||||||
tarFile, err := os.Open(srcDir + "/" + config.ActionData + ".tar")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Error opening tar archive")
|
|
||||||
}
|
|
||||||
// Close tar archive file at the end of this function
|
|
||||||
defer tarFile.Close()
|
|
||||||
// Create tar reader to unarchive tar archive
|
|
||||||
tarUnarchiver := tar.NewReader(tarFile)
|
|
||||||
// Loop to recursively unarchive tar file
|
|
||||||
unarchiveLoop:
|
|
||||||
for {
|
|
||||||
// Jump to next header in tar archive
|
|
||||||
header, err := tarUnarchiver.Next()
|
|
||||||
switch {
|
|
||||||
// If EOF
|
|
||||||
case err == io.EOF:
|
|
||||||
// break loop
|
|
||||||
break unarchiveLoop
|
|
||||||
case err != nil:
|
|
||||||
log.Fatal().Err(err).Msg("Error unarchiving tar archive")
|
|
||||||
// If nil header
|
|
||||||
case header == nil:
|
|
||||||
// Skip
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Set target path to header name in destination dir
|
|
||||||
targetPath := filepath.Join(dstDir, header.Name)
|
|
||||||
switch header.Typeflag {
|
|
||||||
// If regular file
|
|
||||||
case tar.TypeReg:
|
|
||||||
// Try to create containing folder ignoring errors
|
|
||||||
_ = os.MkdirAll(filepath.Dir(targetPath), 0755)
|
|
||||||
// Create file with mode contained in header at target path
|
|
||||||
dstFile, err := os.OpenFile(targetPath, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Error creating file during unarchiving")
|
|
||||||
}
|
|
||||||
// Copy data from tar archive into file
|
|
||||||
_, err = io.Copy(dstFile, tarUnarchiver)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Error copying data to file")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Catchall
|
|
||||||
} else {
|
|
||||||
// Log unknown action type
|
|
||||||
log.Fatal().Msg("Unknown action type " + config.ActionType)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
24
extra.go
Normal file
24
extra.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExpandPath(s string) string {
|
||||||
|
// Use ConsoleWriter logger
|
||||||
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
|
||||||
|
homeDir, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error getting home directory")
|
||||||
|
}
|
||||||
|
expandedString := os.ExpandEnv(s)
|
||||||
|
if strings.HasPrefix(expandedString, "~") {
|
||||||
|
expandedString = strings.Replace(expandedString, "~", homeDir, 1)
|
||||||
|
}
|
||||||
|
expandedString = filepath.Clean(expandedString)
|
||||||
|
return expandedString
|
||||||
|
}
|
2
files.go
2
files.go
@ -223,7 +223,7 @@ func RecvFiles(connection net.Conn) {
|
|||||||
// Otherwise
|
// Otherwise
|
||||||
} else {
|
} else {
|
||||||
// Create new file at index filepath
|
// Create new file at index filepath
|
||||||
newFile, err := os.Create(opensendDir + "/" + file)
|
newFile, err := os.Create(*workDir + "/" + file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Error creating file")
|
log.Fatal().Err(err).Msg("Error creating file")
|
||||||
}
|
}
|
||||||
|
1
go.mod
1
go.mod
@ -5,6 +5,7 @@ go 1.15
|
|||||||
require (
|
require (
|
||||||
github.com/grandcat/zeroconf v1.0.0
|
github.com/grandcat/zeroconf v1.0.0
|
||||||
github.com/klauspost/compress v1.11.3
|
github.com/klauspost/compress v1.11.3
|
||||||
|
github.com/pelletier/go-toml v1.8.1
|
||||||
github.com/pkg/browser v0.0.0-20201112035734-206646e67786
|
github.com/pkg/browser v0.0.0-20201112035734-206646e67786
|
||||||
github.com/rs/zerolog v1.20.0
|
github.com/rs/zerolog v1.20.0
|
||||||
)
|
)
|
||||||
|
4
go.sum
4
go.sum
@ -1,12 +1,16 @@
|
|||||||
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
||||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
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/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE=
|
github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE=
|
||||||
github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs=
|
github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs=
|
||||||
github.com/klauspost/compress v1.11.3 h1:dB4Bn0tN3wdCzQxnS8r06kV74qN/TAfaIS0bVE8h3jc=
|
github.com/klauspost/compress v1.11.3 h1:dB4Bn0tN3wdCzQxnS8r06kV74qN/TAfaIS0bVE8h3jc=
|
||||||
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||||
github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM=
|
github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM=
|
||||||
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||||
|
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
|
||||||
|
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
||||||
github.com/pkg/browser v0.0.0-20201112035734-206646e67786 h1:4Gk0Dsp90g2YwfsxDOjvkEIgKGh+2R9FlvormRycveA=
|
github.com/pkg/browser v0.0.0-20201112035734-206646e67786 h1:4Gk0Dsp90g2YwfsxDOjvkEIgKGh+2R9FlvormRycveA=
|
||||||
github.com/pkg/browser v0.0.0-20201112035734-206646e67786/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
|
github.com/pkg/browser v0.0.0-20201112035734-206646e67786/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
@ -15,7 +15,7 @@ func (hook FatalHook) Run(_ *zerolog.Event, level zerolog.Level, _ string) {
|
|||||||
// If log event is fatal
|
// If log event is fatal
|
||||||
if level == zerolog.FatalLevel {
|
if level == zerolog.FatalLevel {
|
||||||
// Attempt removal of opensend directory
|
// Attempt removal of opensend directory
|
||||||
_ = os.RemoveAll(opensendDir)
|
_ = os.RemoveAll(*workDir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ func (hook TCPFatalHook) Run(_ *zerolog.Event, level zerolog.Level, _ string) {
|
|||||||
// Close connection
|
// Close connection
|
||||||
_ = hook.conn.Close()
|
_ = hook.conn.Close()
|
||||||
// Attempt removal of opensend directory
|
// Attempt removal of opensend directory
|
||||||
_ = os.RemoveAll(opensendDir)
|
_ = os.RemoveAll(*workDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
83
main.go
83
main.go
@ -17,19 +17,43 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var opensendDir string
|
var workDir *string
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Use ConsoleWriter logger
|
// Use ConsoleWriter logger
|
||||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
|
||||||
|
|
||||||
// Get user's home directory
|
confPath := GetConfigPath()
|
||||||
homeDir, err := os.UserHomeDir()
|
config := NewConfig(confPath)
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("Error getting home directory")
|
// Create --send-to flag to send to a specific IP
|
||||||
|
sendTo := flag.String("send-to", "", "Use IP address of receiver instead of mDNS")
|
||||||
|
// Create --dest-dir flag to save to a specified folder
|
||||||
|
destDir := flag.String("dest-dir", "", "Destination directory for files or dirs sent over opensend")
|
||||||
|
workDir = flag.String("work-dir", "", "Working directory for opensend")
|
||||||
|
givenCfgPath := flag.String("config", "", "Opensend config to use")
|
||||||
|
// Create --skip-mdns to skip service registration
|
||||||
|
skipMdns := flag.Bool("skip-mdns", config.Receiver.SkipZeroconf, "Skip zeroconf service registration (use if mdns fails)")
|
||||||
|
// Create -t flag for type
|
||||||
|
actionType := flag.String("t", "", "Type of data being sent")
|
||||||
|
// Create -d flag for data
|
||||||
|
actionData := flag.String("d", "", "Data to send")
|
||||||
|
// Create -s flag for sending
|
||||||
|
sendFlag := flag.Bool("s", false, "Send data")
|
||||||
|
// Create -r flag for receiving
|
||||||
|
recvFlag := flag.Bool("r", false, "Receive data")
|
||||||
|
// Parse flags
|
||||||
|
flag.Parse()
|
||||||
|
if *givenCfgPath != "" {
|
||||||
|
config = NewConfig(*givenCfgPath)
|
||||||
|
}
|
||||||
|
if *workDir == "" {
|
||||||
|
if *sendFlag {
|
||||||
|
*workDir = ExpandPath(config.Sender.WorkDir)
|
||||||
|
} else {
|
||||||
|
*workDir = ExpandPath(config.Receiver.WorkDir)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Define opensend directory as ~/.opensend
|
|
||||||
opensendDir = homeDir + "/.opensend"
|
|
||||||
|
|
||||||
// Create channel for signals
|
// Create channel for signals
|
||||||
sig := make(chan os.Signal, 1)
|
sig := make(chan os.Signal, 1)
|
||||||
@ -43,31 +67,14 @@ func main() {
|
|||||||
// Warn user that a signal has been received and that opensend is shutting down
|
// Warn user that a signal has been received and that opensend is shutting down
|
||||||
log.Warn().Msg("Signal received. Shutting down.")
|
log.Warn().Msg("Signal received. Shutting down.")
|
||||||
// Remove opensend directory to avoid future conflicts
|
// Remove opensend directory to avoid future conflicts
|
||||||
_ = os.RemoveAll(opensendDir)
|
_ = os.RemoveAll(*workDir)
|
||||||
// Exit with code 0
|
// Exit with code 0
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Create --send-to flag to send to a specific IP
|
|
||||||
sendTo := flag.String("send-to", "", "Use IP address of receiver instead of mDNS")
|
|
||||||
// Create --dest-dir flag to save to a specified folder
|
|
||||||
destDir := flag.String("dest-dir", homeDir+"/Downloads", "Destination directory for files or dirs sent over opensend")
|
|
||||||
// Create --skip-mdns to skip service registration
|
|
||||||
skipMdns := flag.Bool("skip-mdns", false, "Skip zeroconf service registration (use if mdns fails)")
|
|
||||||
// Create -t flag for type
|
|
||||||
actionType := flag.String("t", "", "Type of data being sent")
|
|
||||||
// Create -d flag for data
|
|
||||||
actionData := flag.String("d", "", "Data to send")
|
|
||||||
// Create -s flag for sending
|
|
||||||
sendFlag := flag.Bool("s", false, "Send data")
|
|
||||||
// Create -r flag for receiving
|
|
||||||
recvFlag := flag.Bool("r", false, "Receive data")
|
|
||||||
// Parse flags
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
// Create opensend dir ignoring errors
|
// Create opensend dir ignoring errors
|
||||||
_ = os.Mkdir(opensendDir, 0755)
|
_ = os.Mkdir(*workDir, 0755)
|
||||||
// If -s given
|
// If -s given
|
||||||
if *sendFlag {
|
if *sendFlag {
|
||||||
if *actionType == "" || *actionData == "" {
|
if *actionType == "" || *actionData == "" {
|
||||||
@ -119,13 +126,13 @@ func main() {
|
|||||||
choiceIP = discoveredIPs[choiceIndex]
|
choiceIP = discoveredIPs[choiceIndex]
|
||||||
}
|
}
|
||||||
// Instantiate Config object
|
// Instantiate Config object
|
||||||
config := NewConfig(*actionType, *actionData)
|
parameters := NewParameters(*actionType, *actionData)
|
||||||
// Validate data in config struct
|
// Validate data in config struct
|
||||||
config.Validate()
|
parameters.Validate()
|
||||||
// Collect any files that may be required for transaction into opensend directory
|
// Collect any files that may be required for transaction into opensend directory
|
||||||
config.CollectFiles(opensendDir)
|
parameters.CollectFiles(*workDir)
|
||||||
// Create config file in opensend directory
|
// Create config file in opensend directory
|
||||||
config.CreateFile(opensendDir)
|
parameters.CreateFile(*workDir)
|
||||||
// Notify user of key exchange
|
// Notify user of key exchange
|
||||||
log.Info().Msg("Performing key exchange")
|
log.Info().Msg("Performing key exchange")
|
||||||
// Exchange RSA keys with receiver
|
// Exchange RSA keys with receiver
|
||||||
@ -135,15 +142,15 @@ func main() {
|
|||||||
// Encrypt shared key using RSA public key
|
// Encrypt shared key using RSA public key
|
||||||
key := EncryptKey(sharedKey, rawKey)
|
key := EncryptKey(sharedKey, rawKey)
|
||||||
// Save encrypted key in opensend directory as key.aes
|
// Save encrypted key in opensend directory as key.aes
|
||||||
SaveEncryptedKey(key, opensendDir+"/key.aes")
|
SaveEncryptedKey(key, *workDir+"/key.aes")
|
||||||
// Notify user file encryption is beginning
|
// Notify user file encryption is beginning
|
||||||
log.Info().Msg("Encrypting files")
|
log.Info().Msg("Encrypting files")
|
||||||
// Encrypt all files in opensend directory using shared key
|
// Encrypt all files in opensend directory using shared key
|
||||||
EncryptFiles(opensendDir, sharedKey)
|
EncryptFiles(*workDir, sharedKey)
|
||||||
// Notify user server has started
|
// Notify user server has started
|
||||||
log.Info().Msg("Server started on port 9898")
|
log.Info().Msg("Server started on port 9898")
|
||||||
// Send all files in opensend directory using an HTTP server on port 9898
|
// Send all files in opensend directory using an HTTP server on port 9898
|
||||||
SendFiles(opensendDir)
|
SendFiles(*workDir)
|
||||||
// If -r given
|
// If -r given
|
||||||
} else if *recvFlag {
|
} else if *recvFlag {
|
||||||
// If --skip-mdns is not given
|
// If --skip-mdns is not given
|
||||||
@ -178,21 +185,21 @@ func main() {
|
|||||||
// Notify user file decryption is beginning
|
// Notify user file decryption is beginning
|
||||||
log.Info().Msg("Decrypting files")
|
log.Info().Msg("Decrypting files")
|
||||||
// Decrypt all files in opensend directory using shared key
|
// Decrypt all files in opensend directory using shared key
|
||||||
DecryptFiles(opensendDir, sharedKey)
|
DecryptFiles(*workDir, sharedKey)
|
||||||
// Instantiate Config
|
// Instantiate Config
|
||||||
config := &Config{}
|
parameters := &Parameters{}
|
||||||
// Read config file in opensend directory
|
// Read config file in opensend directory
|
||||||
config.ReadFile(opensendDir + "/config.json")
|
parameters.ReadFile(*workDir + "/parameters.json")
|
||||||
// Notify user that action is being executed
|
// Notify user that action is being executed
|
||||||
log.Info().Msg("Executing JSON action")
|
log.Info().Msg("Executing JSON action")
|
||||||
// Execute JSON action using files within opensend directory
|
// Execute JSON action using files within opensend directory
|
||||||
config.ExecuteAction(opensendDir, *destDir)
|
parameters.ExecuteAction(*workDir, *destDir)
|
||||||
} else {
|
} else {
|
||||||
flag.Usage()
|
flag.Usage()
|
||||||
log.Fatal().Msg("You must choose sender or receiver mode using -s or -r")
|
log.Fatal().Msg("You must choose sender or receiver mode using -s or -r")
|
||||||
}
|
}
|
||||||
// Remove opensend directory
|
// Remove opensend directory
|
||||||
err = os.RemoveAll(opensendDir)
|
err := os.RemoveAll(*workDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("Error removing opensend directory")
|
log.Fatal().Err(err).Msg("Error removing opensend directory")
|
||||||
}
|
}
|
||||||
|
278
parameterSerialization.go
Normal file
278
parameterSerialization.go
Normal file
@ -0,0 +1,278 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/pkg/browser"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create config type to store action type and data
|
||||||
|
type Parameters struct {
|
||||||
|
ActionType string
|
||||||
|
ActionData string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instantiate and return a new Config struct
|
||||||
|
func NewParameters(actionType string, actionData string) *Parameters {
|
||||||
|
return &Parameters{ActionType: actionType, ActionData: actionData}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (parameters *Parameters) Validate() {
|
||||||
|
if parameters.ActionType == "url" {
|
||||||
|
// Parse URL in parameters
|
||||||
|
urlParser, err := url.Parse(parameters.ActionData)
|
||||||
|
// If there was an error parsing
|
||||||
|
if err != nil {
|
||||||
|
// Alert user of invalid url
|
||||||
|
log.Fatal().Err(err).Msg("Invalid URL")
|
||||||
|
// If scheme is not detected
|
||||||
|
} else if urlParser.Scheme == "" {
|
||||||
|
// Alert user of invalid scheme
|
||||||
|
log.Fatal().Msg("Invalid URL scheme")
|
||||||
|
// If host is not detected
|
||||||
|
} else if urlParser.Host == "" {
|
||||||
|
// Alert user of invalid host
|
||||||
|
log.Fatal().Msg("Invalid URL host")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create config file
|
||||||
|
func (parameters *Parameters) CreateFile(dir string) {
|
||||||
|
// Use ConsoleWriter logger
|
||||||
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
|
||||||
|
// Create parameters file at given directory
|
||||||
|
configFile, err := os.Create(dir + "/parameters.json")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error creating parameters file")
|
||||||
|
}
|
||||||
|
// Close parameters file at the end of this function
|
||||||
|
defer configFile.Close()
|
||||||
|
// Marshal given Parameters struct into a []byte
|
||||||
|
jsonData, err := json.Marshal(parameters)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error encoding JSON")
|
||||||
|
}
|
||||||
|
// Write []byte to previously created parameters file
|
||||||
|
bytesWritten, err := configFile.Write(jsonData)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error writing JSON to file")
|
||||||
|
}
|
||||||
|
// Log bytes written
|
||||||
|
log.Info().Str("file", "parameters.json").Msg("Wrote " + strconv.Itoa(bytesWritten) + " bytes")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect all required files into given directory
|
||||||
|
func (parameters *Parameters) CollectFiles(dir string) {
|
||||||
|
// Use ConsoleWriter logger
|
||||||
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
|
||||||
|
// If action type is file
|
||||||
|
if parameters.ActionType == "file" {
|
||||||
|
// Open file path in parameters.ActionData
|
||||||
|
src, err := os.Open(parameters.ActionData)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error opening file from parameters")
|
||||||
|
}
|
||||||
|
// Close source file at the end of this function
|
||||||
|
defer src.Close()
|
||||||
|
// Create new file with the same name at given directory
|
||||||
|
dst, err := os.Create(dir + "/" + filepath.Base(parameters.ActionData))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error creating file")
|
||||||
|
}
|
||||||
|
// Close new file at the end of this function
|
||||||
|
defer dst.Close()
|
||||||
|
// Copy data from source file to destination file
|
||||||
|
_, err = io.Copy(dst, src)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error copying data to file")
|
||||||
|
}
|
||||||
|
// Replace file path in parameters.ActionData with file name
|
||||||
|
parameters.ActionData = filepath.Base(parameters.ActionData)
|
||||||
|
} else if parameters.ActionType == "dir" {
|
||||||
|
// Create tar archive
|
||||||
|
tarFile, err := os.Create(dir + "/" + filepath.Base(parameters.ActionData) + ".tar")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error creating file")
|
||||||
|
}
|
||||||
|
// Close tar file at the end of this function
|
||||||
|
defer tarFile.Close()
|
||||||
|
// Create writer for tar archive
|
||||||
|
tarArchiver := tar.NewWriter(tarFile)
|
||||||
|
// Close archiver at the end of this function
|
||||||
|
defer tarArchiver.Close()
|
||||||
|
// Walk given directory
|
||||||
|
err = filepath.Walk(parameters.ActionData, func(path string, info os.FileInfo, err error) error {
|
||||||
|
// Return if error walking
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Skip if file is not normal mode
|
||||||
|
if !info.Mode().IsRegular() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Create tar header for file
|
||||||
|
header, err := tar.FileInfoHeader(info, info.Name())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Change header name to reflect decompressed filepath
|
||||||
|
header.Name = strings.TrimPrefix(strings.ReplaceAll(path, parameters.ActionData, ""), string(filepath.Separator))
|
||||||
|
// Write header to archive
|
||||||
|
if err := tarArchiver.WriteHeader(header); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Open source file
|
||||||
|
src, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Close source file at the end of this function
|
||||||
|
defer src.Close()
|
||||||
|
// Copy source bytes to tar archive
|
||||||
|
if _, err := io.Copy(tarArchiver, src); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Return at the end of the function
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error creating tar archive")
|
||||||
|
}
|
||||||
|
// Set parameters data to base path for receiver
|
||||||
|
parameters.ActionData = filepath.Base(parameters.ActionData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read config file at given file path
|
||||||
|
func (parameters *Parameters) ReadFile(filePath string) {
|
||||||
|
// Use ConsoleWriter logger
|
||||||
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
|
||||||
|
// Read file at filePath
|
||||||
|
fileData, err := ioutil.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error reading parameters file")
|
||||||
|
}
|
||||||
|
// Unmarshal data from JSON into parameters struct
|
||||||
|
err = json.Unmarshal(fileData, parameters)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error decoding JSON")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute action specified in config
|
||||||
|
func (parameters *Parameters) ExecuteAction(srcDir string, destDir string) {
|
||||||
|
// Use ConsoleWriter logger
|
||||||
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Hook(FatalHook{})
|
||||||
|
// If action is file
|
||||||
|
if parameters.ActionType == "file" {
|
||||||
|
// Open file from parameters at given directory
|
||||||
|
src, err := os.Open(srcDir + "/" + parameters.ActionData)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error reading file from parameters")
|
||||||
|
}
|
||||||
|
// Close source file at the end of this function
|
||||||
|
defer src.Close()
|
||||||
|
// Create file in user's Downloads directory
|
||||||
|
dst, err := os.Create(filepath.Clean(destDir) + "/" + parameters.ActionData)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error creating file")
|
||||||
|
}
|
||||||
|
// Close destination file at the end of this function
|
||||||
|
defer dst.Close()
|
||||||
|
// Copy data from source file to destination file
|
||||||
|
_, err = io.Copy(dst, src)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error copying data to file")
|
||||||
|
}
|
||||||
|
// If action is url
|
||||||
|
} else if parameters.ActionType == "url" {
|
||||||
|
// Parse received URL
|
||||||
|
urlParser, err := url.Parse(parameters.ActionData)
|
||||||
|
// If there was an error parsing
|
||||||
|
if err != nil {
|
||||||
|
// Alert user of invalid url
|
||||||
|
log.Fatal().Err(err).Msg("Invalid URL")
|
||||||
|
// If scheme is not detected
|
||||||
|
} else if urlParser.Scheme == "" {
|
||||||
|
// Alert user of invalid scheme
|
||||||
|
log.Fatal().Msg("Invalid URL scheme")
|
||||||
|
// If host is not detected
|
||||||
|
} else if urlParser.Host == "" {
|
||||||
|
// Alert user of invalid host
|
||||||
|
log.Fatal().Msg("Invalid URL host")
|
||||||
|
}
|
||||||
|
// Attempt to open URL in browser
|
||||||
|
err = browser.OpenURL(parameters.ActionData)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error opening browser")
|
||||||
|
}
|
||||||
|
// If action is dir
|
||||||
|
} else if parameters.ActionType == "dir" {
|
||||||
|
// Set destination directory to ~/Downloads/{dir name}
|
||||||
|
dstDir := filepath.Clean(destDir) + "/" + parameters.ActionData
|
||||||
|
// Try to create destination directory
|
||||||
|
err := os.MkdirAll(dstDir, 0755)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error creating directory")
|
||||||
|
}
|
||||||
|
// Try to open tar archive file
|
||||||
|
tarFile, err := os.Open(srcDir + "/" + parameters.ActionData + ".tar")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error opening tar archive")
|
||||||
|
}
|
||||||
|
// Close tar archive file at the end of this function
|
||||||
|
defer tarFile.Close()
|
||||||
|
// Create tar reader to unarchive tar archive
|
||||||
|
tarUnarchiver := tar.NewReader(tarFile)
|
||||||
|
// Loop to recursively unarchive tar file
|
||||||
|
unarchiveLoop:
|
||||||
|
for {
|
||||||
|
// Jump to next header in tar archive
|
||||||
|
header, err := tarUnarchiver.Next()
|
||||||
|
switch {
|
||||||
|
// If EOF
|
||||||
|
case err == io.EOF:
|
||||||
|
// break loop
|
||||||
|
break unarchiveLoop
|
||||||
|
case err != nil:
|
||||||
|
log.Fatal().Err(err).Msg("Error unarchiving tar archive")
|
||||||
|
// If nil header
|
||||||
|
case header == nil:
|
||||||
|
// Skip
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Set target path to header name in destination dir
|
||||||
|
targetPath := filepath.Join(dstDir, header.Name)
|
||||||
|
switch header.Typeflag {
|
||||||
|
// If regular file
|
||||||
|
case tar.TypeReg:
|
||||||
|
// Try to create containing folder ignoring errors
|
||||||
|
_ = os.MkdirAll(filepath.Dir(targetPath), 0755)
|
||||||
|
// Create file with mode contained in header at target path
|
||||||
|
dstFile, err := os.OpenFile(targetPath, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error creating file during unarchiving")
|
||||||
|
}
|
||||||
|
// Copy data from tar archive into file
|
||||||
|
_, err = io.Copy(dstFile, tarUnarchiver)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Error copying data to file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Catchall
|
||||||
|
} else {
|
||||||
|
// Log unknown action type
|
||||||
|
log.Fatal().Msg("Unknown action type " + parameters.ActionType)
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user