From abefd93fb8ed6634d7278dc752670c79514fa24b Mon Sep 17 00:00:00 2001
From: Juan Batiz-Benet <juan@benet.ai>
Date: Mon, 6 Oct 2014 02:14:01 -0700
Subject: [PATCH] edited bootstrap cmd

- safer parsing
- multiple inputs
- add: dup detection
- common cfg writing
---
 cmd/ipfs/bootstrap.go | 278 +++++++++++++++++++++++++++---------------
 config/config.go      |   4 +
 2 files changed, 181 insertions(+), 101 deletions(-)

diff --git a/cmd/ipfs/bootstrap.go b/cmd/ipfs/bootstrap.go
index 82e5ca794..4c0ea9fca 100644
--- a/cmd/ipfs/bootstrap.go
+++ b/cmd/ipfs/bootstrap.go
@@ -1,12 +1,17 @@
 package main
 
 import (
-	"fmt"
+	"errors"
+	"strings"
+
 	"github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/gonuts/flag"
 	"github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/commander"
 	ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
+	mh "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multihash"
+
 	config "github.com/jbenet/go-ipfs/config"
-	"strings"
+	peer "github.com/jbenet/go-ipfs/peer"
+	u "github.com/jbenet/go-ipfs/util"
 )
 
 var cmdIpfsBootstrap = &commander.Command{
@@ -14,165 +19,236 @@ var cmdIpfsBootstrap = &commander.Command{
 	Short:     "Show a list of bootstrapped addresses.",
 	Long: `ipfs bootstrap - show, or manipulate bootstrap node addresses
 
-SECURITY WARNING:
-
-The bootstrap command manipulates the "bootstrap list", which contains
-the addresses of bootstrap nodes. These are the *trusted peers* from
-which to learn about other peers in the network. Only edit this list
-if you understand the risks of adding or removing nodes from this list.
-
 Running 'ipfs bootstrap' with no arguments will run 'ipfs bootstrap list'.
 
 Commands:
 
-  list               Show the boostrap list.
-  add <address>      Add a node's address to the bootstrap list.
-  remove <address>   Remove an address from the bootstrap list.
+	list               Show the boostrap list.
+	add <address>      Add a node's address to the bootstrap list.
+	remove <address>   Remove an address from the bootstrap list.
 
-`,
-	Run: bootstrapCmd,
+` + bootstrapSecurityWarning,
+	Run: bootstrapListCmd,
 	Subcommands: []*commander.Command{
 		cmdIpfsBootstrapRemove,
 		cmdIpfsBootstrapAdd,
+		cmdIpfsBootstrapList,
 	},
 	Flag: *flag.NewFlagSet("ipfs-bootstrap", flag.ExitOnError),
 }
 
 var cmdIpfsBootstrapRemove = &commander.Command{
-	UsageLine: "remove",
-	Run:       IpfsBootstrapRemoveCmd,
-	Flag:      *flag.NewFlagSet("ipfs-bootstrap-remove", flag.ExitOnError),
+	UsageLine: "remove <address | peerid>",
+	Short:     "Remove addresses from the bootstrap list.",
+	Long: `ipfs bootstrap remove - remove addresses from the bootstrap list
+` + bootstrapSecurityWarning,
+	Run:  bootstrapRemoveCmd,
+	Flag: *flag.NewFlagSet("ipfs-bootstrap-remove", flag.ExitOnError),
 }
 
 var cmdIpfsBootstrapAdd = &commander.Command{
-	UsageLine: "add",
-	Run:       IpfsBootstrapAddCmd,
-	Flag:      *flag.NewFlagSet("ipfs-bootstrap-add", flag.ExitOnError),
+	UsageLine: "add <address | peerid>",
+	Short:     "Add addresses to the bootstrap list.",
+	Long: `ipfs bootstrap add - add addresses to the bootstrap list
+` + bootstrapSecurityWarning,
+	Run:  bootstrapAddCmd,
+	Flag: *flag.NewFlagSet("ipfs-bootstrap-add", flag.ExitOnError),
 }
 
-func IpfsBootstrapRemoveCmd(c *commander.Command, inp []string) error {
+var cmdIpfsBootstrapList = &commander.Command{
+	UsageLine: "list",
+	Short:     "Show addresses in the bootstrap list.",
+	Run:       bootstrapListCmd,
+	Flag:      *flag.NewFlagSet("ipfs-bootstrap-list", flag.ExitOnError),
+}
+
+func bootstrapRemoveCmd(c *commander.Command, inp []string) error {
 
 	if len(inp) == 0 {
-		fmt.Println("No peer specified.")
-		return nil
+		return errors.New("remove: no address or peerid specified")
 	}
 
-	if strings.Contains(inp[0], "/") {
+	toRemove, err := bootstrapInputToPeers(inp)
+	if err != nil {
+		return err
+	}
 
-		var pID = inp[0][len(inp[0])-46:]
-		var ip = strings.TrimSuffix(inp[0], pID)
-		maddr, err := ma.NewMultiaddr(strings.TrimSuffix(ip, "/"))
-		var address, _ = maddr.String()
-		if err != nil {
-			return err
-		}
+	cfg, err := getConfig(c)
+	if err != nil {
+		return err
+	}
 
-		peer := config.BootstrapPeer{
-			Address: address,
-			PeerID:  pID,
-		}
+	keep := []*config.BootstrapPeer{}
+	remove := []*config.BootstrapPeer{}
 
-		configpath, _ := config.Filename("~/.go-ipfs/config")
-		var cfg config.Config
-		readErr := config.ReadConfigFile(configpath, &cfg)
-		if readErr != nil {
-			return readErr
-		}
+	// function to filer what to keep
+	shouldKeep := func(bp *config.BootstrapPeer) bool {
+		for _, skipBP := range toRemove {
 
-		i := 0
-		for _, v := range cfg.Bootstrap {
-			if v.PeerID == peer.PeerID && v.Address == peer.Address {
+			// IDs must match to skip.
+			if bp.PeerID != skipBP.PeerID {
 				continue
 			}
-			cfg.Bootstrap[i] = v
-			i++
-		}
-		cfg.Bootstrap = cfg.Bootstrap[:i]
 
-		writeErr := config.WriteConfigFile(configpath, cfg)
-		if writeErr != nil {
-			return writeErr
+			// if Addresses match, or skipBP has no addr (wildcard)
+			if skipBP.Address == bp.Address || skipBP.Address == "" {
+				return false
+			}
 		}
+		return true
 	}
 
-	if !strings.Contains(inp[0], "/") {
-
-		var peerID = inp[0]
-
-		configpath, _ := config.Filename("~/.go-ipfs/config")
-		var cfg config.Config
-		readErr := config.ReadConfigFile(configpath, &cfg)
-		if readErr != nil {
-			return readErr
+	// filter all the existing peers
+	for _, currBP := range cfg.Bootstrap {
+		if shouldKeep(currBP) {
+			keep = append(keep, currBP)
+		} else {
+			remove = append(remove, currBP)
 		}
+	}
 
-		i := 0
-		for _, v := range cfg.Bootstrap {
-			if v.PeerID == peerID {
-				continue
-			}
-			cfg.Bootstrap[i] = v
-			i++
-		}
-		cfg.Bootstrap = cfg.Bootstrap[:i]
+	// if didn't remove anyone, bail.
+	if len(keep) == len(cfg.Bootstrap) {
+		return errors.New("remove: peer given did not match any in list")
+	}
 
-		writeErr := config.WriteConfigFile(configpath, cfg)
-		if writeErr != nil {
-			return writeErr
-		}
+	// write new config
+	cfg.Bootstrap = keep
+	if err := writeConfig(c, cfg); err != nil {
+		return err
+	}
 
+	for _, bp := range remove {
+		u.POut("removed %s\n", bp)
 	}
 	return nil
 }
 
-func IpfsBootstrapAddCmd(c *commander.Command, inp []string) error {
+func bootstrapAddCmd(c *commander.Command, inp []string) error {
 
 	if len(inp) == 0 {
-		fmt.Println("No peer specified.")
-		return nil
+		return errors.New("add: no address specified")
 	}
 
-	var pID = inp[0][len(inp[0])-46:]
-	var ip = strings.TrimSuffix(inp[0], pID)
-	maddr, err := ma.NewMultiaddr(strings.TrimSuffix(ip, "/"))
-	var address, _ = maddr.String()
+	toAdd, err := bootstrapInputToPeers(inp)
 	if err != nil {
 		return err
 	}
 
-	peer := config.BootstrapPeer{
-		Address: address,
-		PeerID:  pID,
+	cfg, err := getConfig(c)
+	if err != nil {
+		return err
 	}
 
-	configpath, _ := config.Filename("~/.go-ipfs/config")
-	var cfg config.Config
-	readErr := config.ReadConfigFile(configpath, &cfg)
-	if readErr != nil {
-		return readErr
+	// function to check whether a peer is already in the list.
+	combine := func(lists ...[]*config.BootstrapPeer) []*config.BootstrapPeer {
+
+		set := map[string]struct{}{}
+		final := []*config.BootstrapPeer{}
+
+		for _, list := range lists {
+			for _, peer := range list {
+				// if already in the set, continue
+				_, found := set[peer.String()]
+				if found {
+					continue
+				}
+
+				set[peer.String()] = struct{}{}
+				final = append(final, peer)
+			}
+		}
+		return final
 	}
 
-	addedPeer := append(cfg.Bootstrap, &peer)
-	cfg.Bootstrap = addedPeer
+	// combine both lists, removing dups.
+	cfg.Bootstrap = combine(cfg.Bootstrap, toAdd)
+	if err := writeConfig(c, cfg); err != nil {
+		return err
+	}
 
-	writeErr := config.WriteConfigFile(configpath, cfg)
-	if writeErr != nil {
-		return writeErr
+	for _, bp := range toAdd {
+		u.POut("added %s\n", bp)
 	}
 	return nil
-	return nil
 }
-func bootstrapCmd(c *commander.Command, inp []string) error {
 
-	configpath, _ := config.Filename("~/.go-ipfs/config")
-	var cfg config.Config
-	config.ReadConfigFile(configpath, &cfg)
+func bootstrapListCmd(c *commander.Command, inp []string) error {
 
-	for i := range cfg.Bootstrap {
-		s := []string{cfg.Bootstrap[i].Address, "/", cfg.Bootstrap[i].PeerID, "\n"}
-		fmt.Printf(strings.Join(s, ""))
+	cfg, err := getConfig(c)
+	if err != nil {
+		return err
+	}
+
+	for _, bp := range cfg.Bootstrap {
+		u.POut("%s\n", bp)
 	}
 
 	return nil
+}
 
+func writeConfig(c *commander.Command, cfg *config.Config) error {
+
+	confdir, err := getConfigDir(c)
+	if err != nil {
+		return err
+	}
+
+	filename, err := config.Filename(confdir)
+	if err != nil {
+		return err
+	}
+
+	return config.WriteConfigFile(filename, cfg)
 }
+
+func bootstrapInputToPeers(input []string) ([]*config.BootstrapPeer, error) {
+	split := func(addr string) (string, string) {
+		idx := strings.LastIndex(addr, "/")
+		if idx == -1 {
+			return "", addr
+		}
+		return addr[:idx], addr[idx+1:]
+	}
+
+	peers := []*config.BootstrapPeer{}
+	for _, addr := range input {
+		addrS, peeridS := split(addr)
+
+		// make sure addrS parses as a multiaddr.
+		if len(addrS) > 0 {
+			maddr, err := ma.NewMultiaddr(addrS)
+			if err != nil {
+				return nil, err
+			}
+
+			addrS, err = maddr.String()
+			if err != nil {
+				return nil, err
+			}
+		}
+
+		// make sure idS parses as a peer.ID
+		peerid, err := mh.FromB58String(peeridS)
+		if err != nil {
+			return nil, err
+		}
+
+		// construct config entry
+		peers = append(peers, &config.BootstrapPeer{
+			Address: addrS,
+			PeerID:  peer.ID(peerid).Pretty(),
+		})
+	}
+	return peers, nil
+}
+
+const bootstrapSecurityWarning = `
+SECURITY WARNING:
+
+The bootstrap command manipulates the "bootstrap list", which contains
+the addresses of bootstrap nodes. These are the *trusted peers* from
+which to learn about other peers in the network. Only edit this list
+if you understand the risks of adding or removing nodes from this list.
+
+`
diff --git a/config/config.go b/config/config.go
index cf5cced44..487784292 100644
--- a/config/config.go
+++ b/config/config.go
@@ -35,6 +35,10 @@ type BootstrapPeer struct {
 	PeerID  string // until multiaddr supports ipfs, use another field.
 }
 
+func (bp *BootstrapPeer) String() string {
+	return bp.Address + "/" + bp.PeerID
+}
+
 // Config is used to load IPFS config files.
 type Config struct {
 	Identity  Identity         // local node's peer identity
-- 
GitLab