Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

F/copy with flag #47

Merged
merged 5 commits into from
Feb 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 17 additions & 85 deletions cmd/portal/main.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
package main

import (
"errors"
"fmt"
"io"
"log"
"net"
"os"
"path/filepath"
"unicode/utf8"

tea "github.com/charmbracelet/bubbletea"
homedir "github.com/mitchellh/go-homedir"
Expand All @@ -20,6 +17,21 @@ import (
// injected at link time using -ldflags.
var version string

// Initialization of cobra and viper.
func init() {
cobra.OnInitialize(initViperConfig)

rootCmd.PersistentFlags().BoolP("verbose", "v", false, "Log debug information to a file on the format `.portal-[command].log` in the current directory")
// Setup viper config.
// Add cobra subcommands.
rootCmd.AddCommand(sendCmd)
rootCmd.AddCommand(receiveCmd)
rootCmd.AddCommand(serveCmd)
rootCmd.AddCommand(versionCmd)
}

// ------------------------------------------------------ Command ------------------------------------------------------

// rootCmd is the top level `portal` command on which the other subcommands are attached to.
var rootCmd = &cobra.Command{
Use: "portal",
Expand All @@ -45,20 +57,7 @@ func main() {
}
}

// Initialization of cobra and viper.
func init() {
cobra.OnInitialize(initViperConfig)

rootCmd.PersistentFlags().BoolP("verbose", "v", false, "Specifes if portal logs debug information to a file on the format `.portal-[command].log` in the current directory")
// Setup viper config.
// Add cobra subcommands.
rootCmd.AddCommand(sendCmd)
rootCmd.AddCommand(receiveCmd)
rootCmd.AddCommand(serveCmd)
rootCmd.AddCommand(versionCmd)
}

// HELPER FUNCTIONS
// -------------------------------------------------- Helper Functions -------------------------------------------------

// initViperConfig initializes the viper config.
// It creates a `.portal.yml` file at the home directory if it has not been created earlier
Expand All @@ -67,8 +66,7 @@ func init() {
func initViperConfig() {
// Set default values
viper.SetDefault("verbose", false)
viper.SetDefault("rendezvousPort", DEFAULT_RENDEZVOUS_PORT)
viper.SetDefault("rendezvousAddress", DEFAULT_RENDEZVOUS_ADDRESS)
viper.SetDefault("relay", fmt.Sprintf("%s:%d", DEFAULT_RENDEZVOUS_ADDRESS, DEFAULT_RENDEZVOUS_PORT))

// Find home directory.
home, err := homedir.Dir()
Expand Down Expand Up @@ -105,17 +103,6 @@ func initViperConfig() {
}
}

// validateRendezvousAddressInViper validates that the `rendezvousAddress` value in viper is a valid hostname or IP
func validateRendezvousAddressInViper() error {
rendezvouzAdress := net.ParseIP(viper.GetString("rendezvousAddress"))
err := validateHostname(viper.GetString("rendezvousAddress"))
// neither a valid IP nor a valid hostname was provided
if (rendezvouzAdress == nil) && err != nil {
return errors.New("invalid IP or hostname provided")
}
return nil
}

func setupLoggingFromViper(cmd string) (*os.File, error) {
if viper.GetBool("verbose") {
f, err := tea.LogToFile(fmt.Sprintf(".portal-%s.log", cmd), fmt.Sprintf("portal-%s: \n", cmd))
Expand All @@ -127,58 +114,3 @@ func setupLoggingFromViper(cmd string) (*os.File, error) {
log.SetOutput(io.Discard)
return nil, nil
}

// validateHostname returns an error if the domain name is not valid
// See https://tools.ietf.org/html/rfc1034#section-3.5 and
// https://tools.ietf.org/html/rfc1123#section-2.
// source: https://gist.github.com/chmike/d4126a3247a6d9a70922fc0e8b4f4013
func validateHostname(name string) error {
switch {
case len(name) == 0:
return nil
case len(name) > 255:
return fmt.Errorf("name length is %d, can't exceed 255", len(name))
}
var l int
for i := 0; i < len(name); i++ {
b := name[i]
if b == '.' {
// check domain labels validity
switch {
case i == l:
return fmt.Errorf("invalid character '%c' at offset %d: label can't begin with a period", b, i)
case i-l > 63:
return fmt.Errorf("byte length of label '%s' is %d, can't exceed 63", name[l:i], i-l)
case name[l] == '-':
return fmt.Errorf("label '%s' at offset %d begins with a hyphen", name[l:i], l)
case name[i-1] == '-':
return fmt.Errorf("label '%s' at offset %d ends with a hyphen", name[l:i], l)
}
l = i + 1
continue
}
// test label character validity, note: tests are ordered by decreasing validity frequency
if !(b >= 'a' && b <= 'z' || b >= '0' && b <= '9' || b == '-' || b >= 'A' && b <= 'Z') {
// show the printable unicode character starting at byte offset i
c, _ := utf8.DecodeRuneInString(name[i:])
if c == utf8.RuneError {
return fmt.Errorf("invalid rune at offset %d", i)
}
return fmt.Errorf("invalid character '%c' at offset %d", c, i)
}
}
// check top level domain validity
switch {
case l == len(name):
return fmt.Errorf("missing top level domain, domain can't end with a period")
case len(name)-l > 63:
return fmt.Errorf("byte length of top level domain '%s' is %d, can't exceed 63", name[l:], len(name)-l)
case name[l] == '-':
return fmt.Errorf("top level domain '%s' at offset %d begins with a hyphen", name[l:], l)
case name[len(name)-1] == '-':
return fmt.Errorf("top level domain '%s' at offset %d ends with a hyphen", name[l:], l)
case name[l] >= '0' && name[l] <= '9':
return fmt.Errorf("top level domain '%s' at offset %d begins with a digit", name[l:], l)
}
return nil
}
41 changes: 23 additions & 18 deletions cmd/portal/receive.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,37 @@ import (
"golang.org/x/exp/slices"
)

// Setup flags.
func init() {
// Add subcommand flags (dummy default values as default values are handled through viper)
desc := `Address of relay server. Can be provided as,
- ipv4: 127.0.0.1:8080
- ipv6: [::1]:8080
- domain: somedomain.com
`
receiveCmd.Flags().StringP("relay", "r", "", desc)
// Add subcommand flags (dummy default values as default values are handled through viper).
}

// ------------------------------------------------------ Command ------------------------------------------------------

// receiveCmd is the cobra command for `portal receive`
var receiveCmd = &cobra.Command{
Use: "receive",
Short: "Receive files",
Long: "The receive command receives files from the sender with the matching password.",
Args: cobra.ExactArgs(1),
ValidArgsFunction: passwordCompletion,
PreRun: func(cmd *cobra.Command, args []string) {
// Bind flags to viper
//nolint
viper.BindPFlag("rendezvousPort", cmd.Flags().Lookup("rendezvous-port"))
//nolint
viper.BindPFlag("rendezvousAddress", cmd.Flags().Lookup("rendezvous-address"))
PreRunE: func(cmd *cobra.Command, args []string) error {
// BindvalidateRelayInViper
if err := viper.BindPFlag("relay", cmd.Flags().Lookup("relay")); err != nil {
return fmt.Errorf("binding relay flag: %w", err)
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
file.RemoveTemporaryFiles(file.RECEIVE_TEMP_FILE_NAME_PREFIX)
err := validateRendezvousAddressInViper()
if err != nil {
if err := validateRelayInViper(); err != nil {
return err
}
logFile, err := setupLoggingFromViper("receive")
Expand All @@ -50,24 +63,16 @@ var receiveCmd = &cobra.Command{
},
}

// Setup flags.
func init() {
// Add subcommand flags (dummy default values as default values are handled through viper).
// TODO: recactor this into a single flag for providing a TCPAddr.
receiveCmd.Flags().IntP("rendezvous-port", "p", 0, "port on which the rendezvous server is running")
receiveCmd.Flags().StringP("rendezvous-address", "a", "", "host address for the rendezvous server")
}
// ------------------------------------------------------ Handler ------------------------------------------------------

// handleReceiveCommand is the receive application.
func handleReceiveCommand(password string) {
addr := viper.GetString("rendezvousAddress")
port := viper.GetInt("rendezvousPort")
var opts []receiver.Option
ver, err := semver.Parse(version)
if err == nil {
opts = append(opts, receiver.WithVersion(ver))
}
receiver := receiver.New(fmt.Sprintf("%s:%d", addr, port), password, opts...)
receiver := receiver.New(viper.GetString("relay"), password, opts...)

if _, err := receiver.Run(); err != nil {
fmt.Println("Error initializing UI", err)
Expand Down
47 changes: 29 additions & 18 deletions cmd/portal/send.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,40 @@ import (
"github.com/spf13/viper"
)

// Set flags.
func init() {
// Add subcommand flags (dummy default values as default values are handled through viper)
desc := `Address of relay server. Can be provided as,
- ipv4: 127.0.0.1:8080
- ipv6: [::1]:8080
- domain: somedomain.com
`
sendCmd.Flags().StringP("relay", "r", "", desc)
}

// ------------------------------------------------------ Command ------------------------------------------------------

// sendCmd cobra command for `portal send`.
var sendCmd = &cobra.Command{
Use: "send",
Short: "Send one or more files",
Long: "The send command adds one or more files to be sent. Files are archived and compressed before sending.",
Args: cobra.MinimumNArgs(1),
PreRun: func(cmd *cobra.Command, args []string) {
PreRunE: func(cmd *cobra.Command, args []string) error {
// Bind flags to viper
//nolint:errcheck
viper.BindPFlag("rendezvousPort", cmd.Flags().Lookup("rendezvous-port"))
//nolint:errcheck
viper.BindPFlag("rendezvousAddress", cmd.Flags().Lookup("rendezvous-address"))
if err := viper.BindPFlag("relay", cmd.Flags().Lookup("relay")); err != nil {
return fmt.Errorf("binding relay flag: %w", err)
}
return nil

},
RunE: func(cmd *cobra.Command, args []string) error {
if err := sender.Init(); err != nil {
return err
}
file.RemoveTemporaryFiles(file.SEND_TEMP_FILE_NAME_PREFIX)
if err := validateRendezvousAddressInViper(); err != nil {

if err := validateRelayInViper(); err != nil {
return err
}

Expand All @@ -40,30 +55,26 @@ var sendCmd = &cobra.Command{
}
defer logFile.Close()

handleSendCommand(args)
handleSendCommand(args, cmd.Flag("relay").Changed)
return nil
},
}

// Set flags.
func init() {
// Add subcommand flags (dummy default values as default values are handled through viper)
//TODO: refactor into a single flag providing a string
sendCmd.Flags().IntP("rendezvous-port", "p", 0, "port on which the rendezvous server is running")
sendCmd.Flags().StringP("rendezvous-address", "a", "", "host address for the rendezvous server")
}
// ------------------------------------------------------ Handler ------------------------------------------------------

// handleSendCommand is the sender application.
func handleSendCommand(fileNames []string) {
addr := viper.GetString("rendezvousAddress")
port := viper.GetInt("rendezvousPort")
func handleSendCommand(fileNames []string, selfHostedRelay bool) {
var opts []senderui.Option
ver, err := semver.Parse(version)
// Conditionally add option to sender ui
if err == nil {
opts = append(opts, senderui.WithVersion(ver))
}
sender := senderui.New(fileNames, fmt.Sprintf("%s:%d", addr, port), opts...)
relayAddr := viper.GetString("relay")
if selfHostedRelay {
opts = append(opts, senderui.WithCopyFlags(map[string]string{"--relay": relayAddr}))
}
sender := senderui.New(fileNames, relayAddr, opts...)
ZinoKader marked this conversation as resolved.
Show resolved Hide resolved
if _, err := sender.Run(); err != nil {
fmt.Println("Error initializing UI", err)
os.Exit(1)
Expand Down
43 changes: 43 additions & 0 deletions cmd/portal/validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package main

import (
"errors"
"net"
"regexp"
"strings"

"github.com/spf13/viper"
"golang.org/x/net/idna"
)

var ErrInvalidRelay = errors.New("invalid relay provided")

var ipv6Rex = regexp.MustCompile(`\[(.*?)\]`)

func stripPort(addr string) string {
split := strings.Split(addr, ":")
if len(split) == 2 {
return split[0]
}

matches := ipv6Rex.FindStringSubmatch(addr)
if len(matches) >= 2 {
return matches[1]
}
return addr
}

// validateRelayInViper validates that the `rendezvousAddress` value in viper is a valid hostname or IP
func validateRelayInViper() error {
relayAddr := viper.GetString("relay")

if ip := net.ParseIP(stripPort(relayAddr)); ip != nil {
return nil
}

if _, err := idna.Lookup.ToASCII(relayAddr); err == nil {
return nil
}

return ErrInvalidRelay
}
Loading