mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-08 08:04:14 -10:00
Compare commits
12
Commits
v1.6.5
..
fix/sqlite
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0652f2fd3 | ||
|
|
936f067363 | ||
|
|
041c3c7048 | ||
|
|
b48f74af58 | ||
|
|
6ec8849b20 | ||
|
|
db7cf1ed62 | ||
|
|
16e1e77068 | ||
|
|
1cfa694aa2 | ||
|
|
5898877136 | ||
|
|
a7d8cac565 | ||
|
|
0e73afca47 | ||
|
|
432941d694 |
No files matched your search
@@ -43,4 +43,3 @@ See the [wiki](https://sliver.sh/docs?name=Compile+from+Source).
|
||||
|
||||
Sliver is licensed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html), some sub-components may have separate licenses. See their respective subdirectories in this project for details.
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ func PrintCanaries(con *console.SliverClient, canaries []*clientpb.DNSCanary, bu
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{
|
||||
"Sliver Name",
|
||||
"Implant Name",
|
||||
"Domain",
|
||||
"Triggered",
|
||||
"First Trigger",
|
||||
|
||||
@@ -22,7 +22,8 @@ var (
|
||||
// GenerateBeaconCmd - The main command used to generate implant binaries
|
||||
func GenerateBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
if shouldRunGenerateBeaconForm(cmd, con, args) {
|
||||
result, err := forms.GenerateBeaconForm()
|
||||
compiler, _ := compilerTargets(con)
|
||||
result, err := forms.GenerateBeaconForm(compiler)
|
||||
if err != nil {
|
||||
if errors.Is(err, forms.ErrUserAborted) {
|
||||
return
|
||||
|
||||
@@ -91,7 +91,8 @@ var (
|
||||
// GenerateCmd - The main command used to generate implant binaries
|
||||
func GenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
if shouldRunGenerateForm(cmd, con, args) {
|
||||
result, err := forms.GenerateForm()
|
||||
compiler, _ := compilerTargets(con)
|
||||
result, err := forms.GenerateForm(compiler)
|
||||
if err != nil {
|
||||
if errors.Is(err, forms.ErrUserAborted) {
|
||||
return
|
||||
@@ -118,14 +119,15 @@ func GenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
} else {
|
||||
_, err := externalBuild(name, config, save, con)
|
||||
if err != nil {
|
||||
if err == ErrNoExternalBuilder {
|
||||
switch err {
|
||||
case ErrNoExternalBuilder:
|
||||
con.PrintErrorf("There are no external builders currently connected to the server\n")
|
||||
con.PrintErrorf("See 'builders' command for more information\n")
|
||||
} else if err == ErrNoValidBuilders {
|
||||
case ErrNoValidBuilders:
|
||||
con.PrintErrorf("There are external builders connected to the server, but none can build the target you specified\n")
|
||||
con.PrintErrorf("Invalid target %s\n", fmt.Sprintf("%s:%s/%s", config.Format, config.GOOS, config.GOARCH))
|
||||
con.PrintErrorf("See 'builders' command for more information\n")
|
||||
} else {
|
||||
default:
|
||||
con.PrintErrorf("%s\n", err)
|
||||
}
|
||||
return
|
||||
|
||||
@@ -124,7 +124,7 @@ func PrintImplantBuilds(builds *clientpb.ImplantBuilds, filters ImplantBuildFilt
|
||||
config.Format,
|
||||
strings.Join(c2URLs, "\n"),
|
||||
fmt.Sprintf("%v", config.Debug),
|
||||
fmt.Sprintf(config.HTTPC2ConfigName),
|
||||
fmt.Sprintf("%s", config.HTTPC2ConfigName),
|
||||
fmt.Sprintf("%v", builds.ResourceIDs[sliverName].Value),
|
||||
fmt.Sprintf("%v", builds.Staged[sliverName]),
|
||||
})
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package websites
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/flags"
|
||||
"github.com/bishopfox/sliver/client/command/help"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/bishopfox/sliver/protobuf/commonpb"
|
||||
"github.com/rsteube/carapace"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -26,6 +30,41 @@ func Commands(con *console.SliverClient) []*cobra.Command {
|
||||
})
|
||||
carapace.Gen(websitesCmd).PositionalCompletion(WebsiteNameCompleter(con))
|
||||
|
||||
websitesListCmd := &cobra.Command{
|
||||
Use: consts.ListStr,
|
||||
Short: "List configured websites",
|
||||
Long: help.GetHelpFor([]string{consts.WebsitesStr, consts.ListStr}),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ListWebsites(cmd, con, args)
|
||||
},
|
||||
}
|
||||
websitesCmd.AddCommand(websitesListCmd)
|
||||
|
||||
websitesShowCmd := &cobra.Command{
|
||||
Use: "show [name]",
|
||||
Short: "Show contents of a website",
|
||||
Long: help.GetHelpFor([]string{consts.WebsitesStr}),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
ListWebsiteContent(args[0], con)
|
||||
},
|
||||
}
|
||||
carapace.Gen(websitesShowCmd).PositionalCompletion(WebsiteNameCompleter(con))
|
||||
websitesShowCmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
ws, err := con.Rpc.Websites(context.Background(), &commonpb.Empty{})
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveError
|
||||
}
|
||||
out := []string{}
|
||||
for _, w := range ws.Websites {
|
||||
if strings.HasPrefix(w.Name, toComplete) {
|
||||
out = append(out, w.Name)
|
||||
}
|
||||
}
|
||||
return out, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
websitesCmd.AddCommand(websitesShowCmd)
|
||||
|
||||
websitesRmCmd := &cobra.Command{
|
||||
Use: consts.RmStr,
|
||||
Short: "Remove an entire website and all of its contents",
|
||||
|
||||
@@ -121,9 +121,10 @@ func webAddFile(web *clientpb.WebsiteAddContent, webpath string, contentType str
|
||||
}
|
||||
|
||||
web.Contents[webpath] = &clientpb.WebContent{
|
||||
Path: webpath,
|
||||
ContentType: contentType,
|
||||
Content: data,
|
||||
Path: webpath,
|
||||
ContentType: contentType,
|
||||
OriginalFile: filepath.Base(contentPath),
|
||||
Content: data,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ package websites
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/command/settings"
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
@@ -58,11 +57,14 @@ func ListWebsites(cmd *cobra.Command, con *console.SliverClient, args []string)
|
||||
con.PrintInfof("No websites\n")
|
||||
return
|
||||
}
|
||||
con.Println("Websites")
|
||||
con.Println(strings.Repeat("=", len("Websites")))
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(settings.GetTableStyle(con))
|
||||
tw.AppendHeader(table.Row{"Name", "Objects"})
|
||||
for _, site := range websites.Websites {
|
||||
con.Printf("%s%s%s - %d page(s)\n", console.Bold, site.Name, console.Normal, len(site.Contents))
|
||||
tw.AppendRow(table.Row{site.Name, len(site.Contents)})
|
||||
}
|
||||
|
||||
con.Println(tw.Render())
|
||||
}
|
||||
|
||||
// ListWebsiteContent - List the static contents of a website.
|
||||
@@ -91,6 +93,8 @@ func PrintWebsite(web *clientpb.Website, con *console.SliverClient) {
|
||||
"Path",
|
||||
"Content-type",
|
||||
"Size",
|
||||
"Original File",
|
||||
"SHA256",
|
||||
})
|
||||
sortedContents := []*clientpb.WebContent{}
|
||||
for _, content := range web.Contents {
|
||||
@@ -104,6 +108,8 @@ func PrintWebsite(web *clientpb.Website, con *console.SliverClient) {
|
||||
content.Path,
|
||||
content.ContentType,
|
||||
content.Size,
|
||||
content.OriginalFile,
|
||||
content.Sha256,
|
||||
})
|
||||
}
|
||||
con.Println(tw.Render())
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
package console
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
consts "github.com/bishopfox/sliver/client/constants"
|
||||
"github.com/reeflective/console"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func (con *SliverClient) allowServerRootCommands(args []string) ([]string, error) {
|
||||
con.applyCommandFallback(args)
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (con *SliverClient) prepareCompletion(line []rune, cursor int) {
|
||||
if con == nil || con.App == nil {
|
||||
return
|
||||
}
|
||||
|
||||
menu := con.App.ActiveMenu()
|
||||
if menu == nil {
|
||||
return
|
||||
}
|
||||
|
||||
con.setMenuCommandForCompletion(menu, line, cursor)
|
||||
}
|
||||
|
||||
func (con *SliverClient) applyCommandFallback(args []string) {
|
||||
if con == nil || con.App == nil {
|
||||
return
|
||||
}
|
||||
|
||||
con.setMenuCommand(con.App.ActiveMenu(), args)
|
||||
}
|
||||
|
||||
func (con *SliverClient) setMenuCommand(menu *console.Menu, args []string) {
|
||||
if menu == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if menu.Name() != consts.ImplantMenu {
|
||||
if menu.Command == nil && con.serverCmds != nil {
|
||||
menu.Command = con.serverCmds()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if menu.Command == nil {
|
||||
if con.sliverCmds == nil {
|
||||
return
|
||||
}
|
||||
menu.Command = con.sliverCmds()
|
||||
}
|
||||
|
||||
if con.serverCmds == nil {
|
||||
return
|
||||
}
|
||||
|
||||
name := firstNonFlagArg(args)
|
||||
if name == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if rootHasCommand(menu.Command, name) {
|
||||
return
|
||||
}
|
||||
|
||||
serverRoot := con.serverCmds()
|
||||
if serverRoot == nil || !rootHasCommand(serverRoot, name) {
|
||||
return
|
||||
}
|
||||
|
||||
menu.Command = serverRoot
|
||||
}
|
||||
|
||||
func (con *SliverClient) setMenuCommandForCompletion(menu *console.Menu, line []rune, cursor int) {
|
||||
if menu == nil || menu.Name() != consts.ImplantMenu {
|
||||
return
|
||||
}
|
||||
|
||||
if menu.Command == nil {
|
||||
if con.sliverCmds == nil {
|
||||
return
|
||||
}
|
||||
menu.Command = con.sliverCmds()
|
||||
}
|
||||
|
||||
if con.serverCmds == nil {
|
||||
return
|
||||
}
|
||||
|
||||
token := firstTokenFromLine(line, cursor)
|
||||
if token == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if rootHasCommandPrefix(menu.Command, token) {
|
||||
return
|
||||
}
|
||||
|
||||
serverRoot := con.serverCmds()
|
||||
if serverRoot == nil || !rootHasCommandPrefix(serverRoot, token) {
|
||||
return
|
||||
}
|
||||
|
||||
menu.Command = serverRoot
|
||||
hideFilteredCommands(menu, menu.Command)
|
||||
}
|
||||
|
||||
func firstNonFlagArg(args []string) string {
|
||||
for i := 0; i < len(args); i++ {
|
||||
arg := args[i]
|
||||
if arg == "--" {
|
||||
if i+1 < len(args) {
|
||||
return args[i+1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
if strings.HasPrefix(arg, "-") {
|
||||
continue
|
||||
}
|
||||
return arg
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func firstTokenFromLine(line []rune, cursor int) string {
|
||||
if cursor > len(line) {
|
||||
cursor = len(line)
|
||||
}
|
||||
if cursor < 0 {
|
||||
cursor = 0
|
||||
}
|
||||
|
||||
pos := 0
|
||||
for pos < cursor && isSpace(line[pos]) {
|
||||
pos++
|
||||
}
|
||||
start := pos
|
||||
for pos < cursor && !isSpace(line[pos]) {
|
||||
pos++
|
||||
}
|
||||
if start == pos {
|
||||
return ""
|
||||
}
|
||||
return string(line[start:pos])
|
||||
}
|
||||
|
||||
func isSpace(r rune) bool {
|
||||
return r == ' ' || r == '\t' || r == '\n' || r == '\r'
|
||||
}
|
||||
|
||||
func rootHasCommand(root *cobra.Command, name string) bool {
|
||||
if root == nil || name == "" {
|
||||
return false
|
||||
}
|
||||
for _, cmd := range root.Commands() {
|
||||
if cmd.Name() == name || cmd.HasAlias(name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func rootHasCommandPrefix(root *cobra.Command, prefix string) bool {
|
||||
if root == nil || prefix == "" {
|
||||
return false
|
||||
}
|
||||
for _, cmd := range root.Commands() {
|
||||
if strings.HasPrefix(cmd.Name(), prefix) {
|
||||
return true
|
||||
}
|
||||
for _, alias := range cmd.Aliases {
|
||||
if strings.HasPrefix(alias, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hideFilteredCommands(menu *console.Menu, root *cobra.Command) {
|
||||
if menu == nil || root == nil {
|
||||
return
|
||||
}
|
||||
for _, cmd := range root.Commands() {
|
||||
if cmd.Hidden {
|
||||
continue
|
||||
}
|
||||
if filters := menu.ActiveFiltersFor(cmd); len(filters) > 0 {
|
||||
cmd.Hidden = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,6 +98,8 @@ type SliverClient struct {
|
||||
Settings *assets.ClientSettings
|
||||
IsServer bool
|
||||
IsCLI bool
|
||||
serverCmds console.Commands
|
||||
sliverCmds console.Commands
|
||||
|
||||
jsonHandler slog.Handler
|
||||
printf func(format string, args ...any) (int, error)
|
||||
@@ -166,6 +168,8 @@ func NewConsole(isServer bool) *SliverClient {
|
||||
func StartClient(con *SliverClient, rpc rpcpb.SliverRPCClient, serverCmds, sliverCmds console.Commands, run bool, rcScript string) error {
|
||||
con.Rpc = rpc
|
||||
con.IsCLI = !run
|
||||
con.serverCmds = serverCmds
|
||||
con.sliverCmds = sliverCmds
|
||||
|
||||
// The console application needs to query the terminal for cursor positions
|
||||
// when asynchronously printing logs (that is, when no command is running).
|
||||
@@ -184,6 +188,15 @@ func StartClient(con *SliverClient, rpc rpcpb.SliverRPCClient, serverCmds, slive
|
||||
sliver := con.App.Menu(consts.ImplantMenu)
|
||||
sliver.SetCommands(sliverCmds)
|
||||
|
||||
con.App.PreCmdRunLineHooks = append(con.App.PreCmdRunLineHooks, con.allowServerRootCommands)
|
||||
if shell := con.App.Shell(); shell != nil && shell.Completer != nil {
|
||||
baseCompleter := shell.Completer
|
||||
shell.Completer = func(line []rune, cursor int) readline.Completions {
|
||||
con.prepareCompletion(line, cursor)
|
||||
return baseCompleter(line, cursor)
|
||||
}
|
||||
}
|
||||
|
||||
// Events
|
||||
go con.startEventLoop()
|
||||
go core.TunnelLoop(rpc)
|
||||
@@ -250,6 +263,19 @@ func (con *SliverClient) runRCLine(serverCmds, sliverCmds console.Commands, line
|
||||
return fmt.Errorf("parse error: %w", err)
|
||||
}
|
||||
|
||||
if con.serverCmds == nil {
|
||||
con.serverCmds = serverCmds
|
||||
}
|
||||
if con.sliverCmds == nil {
|
||||
con.sliverCmds = sliverCmds
|
||||
}
|
||||
|
||||
menu := con.App.ActiveMenu()
|
||||
if menu != nil {
|
||||
// Reset per line to avoid stale roots when rc scripts switch menus.
|
||||
menu.Command = nil
|
||||
}
|
||||
|
||||
for _, hook := range con.App.PreCmdRunLineHooks {
|
||||
args, err = hook(args)
|
||||
if err != nil {
|
||||
@@ -261,11 +287,15 @@ func (con *SliverClient) runRCLine(serverCmds, sliverCmds console.Commands, line
|
||||
return nil
|
||||
}
|
||||
|
||||
menu := con.App.ActiveMenu()
|
||||
if menu.Name() == consts.ImplantMenu {
|
||||
menu.Command = sliverCmds()
|
||||
} else {
|
||||
menu.Command = serverCmds()
|
||||
menu = con.App.ActiveMenu()
|
||||
if menu == nil {
|
||||
return fmt.Errorf("no active menu")
|
||||
}
|
||||
if menu.Command == nil {
|
||||
con.setMenuCommand(menu, args)
|
||||
}
|
||||
if menu.Command == nil {
|
||||
return fmt.Errorf("no commands available")
|
||||
}
|
||||
|
||||
target, _, _ := menu.Command.Find(args)
|
||||
|
||||
+30
-34
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/charmbracelet/huh"
|
||||
)
|
||||
@@ -23,62 +24,57 @@ type GenerateFormResult struct {
|
||||
}
|
||||
|
||||
// GenerateForm prompts for core generate flags and returns the collected values.
|
||||
func GenerateForm() (*GenerateFormResult, error) {
|
||||
func GenerateForm(compiler *clientpb.Compiler) (*GenerateFormResult, error) {
|
||||
result := &GenerateFormResult{
|
||||
OS: "windows",
|
||||
Arch: "amd64",
|
||||
Format: "exe",
|
||||
C2Type: "mtls",
|
||||
}
|
||||
commonPlatformsOnly := true
|
||||
lastCommonPlatformsOnly := commonPlatformsOnly
|
||||
compilerTargets := compilerTargetList(compiler)
|
||||
targetBindings := &targetOptionBindings{
|
||||
Common: &commonPlatformsOnly,
|
||||
OS: &result.OS,
|
||||
Arch: &result.Arch,
|
||||
}
|
||||
|
||||
form := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewConfirm().
|
||||
Title("Common Platforms Only").
|
||||
Value(&commonPlatformsOnly),
|
||||
huh.NewSelect[string]().
|
||||
Title("Target operating system").
|
||||
Options(
|
||||
huh.NewOption("Windows", "windows"),
|
||||
huh.NewOption("Linux", "linux"),
|
||||
huh.NewOption("macOS", "darwin"),
|
||||
).
|
||||
OptionsFunc(func() []huh.Option[string] {
|
||||
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
|
||||
if commonPlatformsOnly || len(compilerTargets) == 0 {
|
||||
return commonOSOptions()
|
||||
}
|
||||
return osOptionsFromTargets(compilerTargets)
|
||||
}, targetBindings).
|
||||
Value(&result.OS),
|
||||
huh.NewSelect[string]().
|
||||
Title("CPU architecture").
|
||||
OptionsFunc(func() []huh.Option[string] {
|
||||
switch result.OS {
|
||||
case "darwin":
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("arm64", "arm64"),
|
||||
}
|
||||
case "linux":
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("386", "386"),
|
||||
}
|
||||
default:
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("386", "386"),
|
||||
}
|
||||
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
|
||||
if commonPlatformsOnly || len(compilerTargets) == 0 {
|
||||
return commonArchOptions(result.OS)
|
||||
}
|
||||
}, &result.OS).
|
||||
return archOptionsFromTargets(compilerTargets, result.OS)
|
||||
}, targetBindings).
|
||||
Height(3).
|
||||
Value(&result.Arch),
|
||||
huh.NewSelect[string]().
|
||||
Title("Output format").
|
||||
OptionsFunc(func() []huh.Option[string] {
|
||||
options := []huh.Option[string]{
|
||||
huh.NewOption("Executable", "exe"),
|
||||
huh.NewOption("Shared library", "shared"),
|
||||
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
|
||||
if commonPlatformsOnly || len(compilerTargets) == 0 {
|
||||
return commonFormatOptions(result.OS)
|
||||
}
|
||||
if result.OS == "windows" {
|
||||
options = append(options,
|
||||
huh.NewOption("Service", "service"),
|
||||
huh.NewOption("Shellcode", "shellcode"),
|
||||
)
|
||||
}
|
||||
return options
|
||||
}, &result.OS).
|
||||
return formatOptionsFromTargets(compilerTargets, result.OS, result.Arch)
|
||||
}, targetBindings).
|
||||
Height(3).
|
||||
Value(&result.Format),
|
||||
),
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/bishopfox/sliver/util"
|
||||
"github.com/charmbracelet/huh"
|
||||
)
|
||||
@@ -26,62 +27,57 @@ type GenerateBeaconFormResult struct {
|
||||
}
|
||||
|
||||
// GenerateBeaconForm prompts for generate beacon flags and returns the collected values.
|
||||
func GenerateBeaconForm() (*GenerateBeaconFormResult, error) {
|
||||
func GenerateBeaconForm(compiler *clientpb.Compiler) (*GenerateBeaconFormResult, error) {
|
||||
result := &GenerateBeaconFormResult{
|
||||
OS: "windows",
|
||||
Arch: "amd64",
|
||||
Format: "exe",
|
||||
C2Type: "mtls",
|
||||
}
|
||||
commonPlatformsOnly := true
|
||||
lastCommonPlatformsOnly := commonPlatformsOnly
|
||||
compilerTargets := compilerTargetList(compiler)
|
||||
targetBindings := &targetOptionBindings{
|
||||
Common: &commonPlatformsOnly,
|
||||
OS: &result.OS,
|
||||
Arch: &result.Arch,
|
||||
}
|
||||
|
||||
form := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewConfirm().
|
||||
Title("Common Platforms Only").
|
||||
Value(&commonPlatformsOnly),
|
||||
huh.NewSelect[string]().
|
||||
Title("Target operating system").
|
||||
Options(
|
||||
huh.NewOption("Windows", "windows"),
|
||||
huh.NewOption("Linux", "linux"),
|
||||
huh.NewOption("macOS", "darwin"),
|
||||
).
|
||||
OptionsFunc(func() []huh.Option[string] {
|
||||
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
|
||||
if commonPlatformsOnly || len(compilerTargets) == 0 {
|
||||
return commonOSOptions()
|
||||
}
|
||||
return osOptionsFromTargets(compilerTargets)
|
||||
}, targetBindings).
|
||||
Value(&result.OS),
|
||||
huh.NewSelect[string]().
|
||||
Title("CPU architecture").
|
||||
OptionsFunc(func() []huh.Option[string] {
|
||||
switch result.OS {
|
||||
case "darwin":
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("arm64", "arm64"),
|
||||
}
|
||||
case "linux":
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("386", "386"),
|
||||
}
|
||||
default:
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("386", "386"),
|
||||
}
|
||||
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
|
||||
if commonPlatformsOnly || len(compilerTargets) == 0 {
|
||||
return commonArchOptions(result.OS)
|
||||
}
|
||||
}, &result.OS).
|
||||
return archOptionsFromTargets(compilerTargets, result.OS)
|
||||
}, targetBindings).
|
||||
Height(3).
|
||||
Value(&result.Arch),
|
||||
huh.NewSelect[string]().
|
||||
Title("Output format").
|
||||
OptionsFunc(func() []huh.Option[string] {
|
||||
options := []huh.Option[string]{
|
||||
huh.NewOption("Executable", "exe"),
|
||||
huh.NewOption("Shared library", "shared"),
|
||||
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
|
||||
if commonPlatformsOnly || len(compilerTargets) == 0 {
|
||||
return commonFormatOptions(result.OS)
|
||||
}
|
||||
if result.OS == "windows" {
|
||||
options = append(options,
|
||||
huh.NewOption("Service", "service"),
|
||||
huh.NewOption("Shellcode", "shellcode"),
|
||||
)
|
||||
}
|
||||
return options
|
||||
}, &result.OS).
|
||||
return formatOptionsFromTargets(compilerTargets, result.OS, result.Arch)
|
||||
}, targetBindings).
|
||||
Height(3).
|
||||
Value(&result.Format),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
package forms
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
"github.com/charmbracelet/huh"
|
||||
)
|
||||
|
||||
type formatOption struct {
|
||||
format clientpb.OutputFormat
|
||||
label string
|
||||
value string
|
||||
}
|
||||
|
||||
type targetOptionBindings struct {
|
||||
Common *bool
|
||||
OS *string
|
||||
Arch *string
|
||||
}
|
||||
|
||||
var formatOptionOrder = []formatOption{
|
||||
{format: clientpb.OutputFormat_EXECUTABLE, label: "Executable", value: "exe"},
|
||||
{format: clientpb.OutputFormat_SHARED_LIB, label: "Shared library", value: "shared"},
|
||||
{format: clientpb.OutputFormat_SERVICE, label: "Service", value: "service"},
|
||||
{format: clientpb.OutputFormat_SHELLCODE, label: "Shellcode", value: "shellcode"},
|
||||
}
|
||||
|
||||
func commonOSOptions() []huh.Option[string] {
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("Windows", "windows"),
|
||||
huh.NewOption("Linux", "linux"),
|
||||
huh.NewOption("macOS", "darwin"),
|
||||
}
|
||||
}
|
||||
|
||||
func commonArchOptions(goos string) []huh.Option[string] {
|
||||
switch goos {
|
||||
case "darwin":
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("arm64", "arm64"),
|
||||
}
|
||||
case "linux":
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("386", "386"),
|
||||
}
|
||||
default:
|
||||
return []huh.Option[string]{
|
||||
huh.NewOption("amd64", "amd64"),
|
||||
huh.NewOption("386", "386"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func commonFormatOptions(goos string) []huh.Option[string] {
|
||||
options := []huh.Option[string]{
|
||||
huh.NewOption("Executable", "exe"),
|
||||
huh.NewOption("Shared library", "shared"),
|
||||
}
|
||||
if goos == "windows" {
|
||||
options = append(options,
|
||||
huh.NewOption("Service", "service"),
|
||||
huh.NewOption("Shellcode", "shellcode"),
|
||||
)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func firstOptionValue(options []huh.Option[string]) (string, bool) {
|
||||
if len(options) == 0 {
|
||||
return "", false
|
||||
}
|
||||
return options[0].Value, true
|
||||
}
|
||||
|
||||
func maybeResetTargets(commonPlatformsOnly bool, lastCommonPlatformsOnly *bool, compilerTargets []*clientpb.CompilerTarget, goos, goarch, format *string) {
|
||||
if lastCommonPlatformsOnly == nil || goos == nil || goarch == nil || format == nil {
|
||||
return
|
||||
}
|
||||
if commonPlatformsOnly == *lastCommonPlatformsOnly {
|
||||
return
|
||||
}
|
||||
*lastCommonPlatformsOnly = commonPlatformsOnly
|
||||
if commonPlatformsOnly {
|
||||
return
|
||||
}
|
||||
|
||||
osOptions := commonOSOptions()
|
||||
if len(compilerTargets) > 0 {
|
||||
osOptions = osOptionsFromTargets(compilerTargets)
|
||||
if len(osOptions) == 0 {
|
||||
osOptions = commonOSOptions()
|
||||
}
|
||||
}
|
||||
if value, ok := firstOptionValue(osOptions); ok {
|
||||
*goos = value
|
||||
}
|
||||
|
||||
archOptions := commonArchOptions(*goos)
|
||||
if len(compilerTargets) > 0 {
|
||||
archOptions = archOptionsFromTargets(compilerTargets, *goos)
|
||||
if len(archOptions) == 0 {
|
||||
archOptions = commonArchOptions(*goos)
|
||||
}
|
||||
}
|
||||
if value, ok := firstOptionValue(archOptions); ok {
|
||||
*goarch = value
|
||||
}
|
||||
|
||||
formatOptions := commonFormatOptions(*goos)
|
||||
if len(compilerTargets) > 0 {
|
||||
formatOptions = formatOptionsFromTargets(compilerTargets, *goos, *goarch)
|
||||
if len(formatOptions) == 0 {
|
||||
formatOptions = commonFormatOptions(*goos)
|
||||
}
|
||||
}
|
||||
if value, ok := firstOptionValue(formatOptions); ok {
|
||||
*format = value
|
||||
}
|
||||
}
|
||||
|
||||
func compilerTargetList(compiler *clientpb.Compiler) []*clientpb.CompilerTarget {
|
||||
if compiler == nil {
|
||||
return nil
|
||||
}
|
||||
targets := make([]*clientpb.CompilerTarget, 0, len(compiler.Targets)+len(compiler.UnsupportedTargets))
|
||||
targets = append(targets, compiler.Targets...)
|
||||
targets = append(targets, compiler.UnsupportedTargets...)
|
||||
return targets
|
||||
}
|
||||
|
||||
func osOptionsFromTargets(targets []*clientpb.CompilerTarget) []huh.Option[string] {
|
||||
if len(targets) == 0 {
|
||||
return nil
|
||||
}
|
||||
seen := map[string]struct{}{}
|
||||
for _, target := range targets {
|
||||
if target.GOOS == "" {
|
||||
continue
|
||||
}
|
||||
seen[target.GOOS] = struct{}{}
|
||||
}
|
||||
values := make([]string, 0, len(seen))
|
||||
for value := range seen {
|
||||
values = append(values, value)
|
||||
}
|
||||
sort.Strings(values)
|
||||
|
||||
options := make([]huh.Option[string], 0, len(values))
|
||||
for _, value := range values {
|
||||
options = append(options, huh.NewOption(osLabel(value), value))
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func archOptionsFromTargets(targets []*clientpb.CompilerTarget, goos string) []huh.Option[string] {
|
||||
if len(targets) == 0 {
|
||||
return nil
|
||||
}
|
||||
seen := map[string]struct{}{}
|
||||
for _, target := range targets {
|
||||
if goos != "" && target.GOOS != goos {
|
||||
continue
|
||||
}
|
||||
if target.GOARCH == "" {
|
||||
continue
|
||||
}
|
||||
seen[target.GOARCH] = struct{}{}
|
||||
}
|
||||
values := make([]string, 0, len(seen))
|
||||
for value := range seen {
|
||||
values = append(values, value)
|
||||
}
|
||||
sort.Strings(values)
|
||||
|
||||
options := make([]huh.Option[string], 0, len(values))
|
||||
for _, value := range values {
|
||||
options = append(options, huh.NewOption(value, value))
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func formatOptionsFromTargets(targets []*clientpb.CompilerTarget, goos, goarch string) []huh.Option[string] {
|
||||
if len(targets) == 0 {
|
||||
return nil
|
||||
}
|
||||
available := map[clientpb.OutputFormat]struct{}{}
|
||||
for _, target := range targets {
|
||||
if goos != "" && target.GOOS != goos {
|
||||
continue
|
||||
}
|
||||
if goarch != "" && target.GOARCH != goarch {
|
||||
continue
|
||||
}
|
||||
available[target.Format] = struct{}{}
|
||||
}
|
||||
|
||||
options := make([]huh.Option[string], 0, len(available))
|
||||
for _, option := range formatOptionOrder {
|
||||
if _, ok := available[option.format]; ok {
|
||||
options = append(options, huh.NewOption(option.label, option.value))
|
||||
}
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func osLabel(value string) string {
|
||||
switch value {
|
||||
case "windows":
|
||||
return "Windows"
|
||||
case "linux":
|
||||
return "Linux"
|
||||
case "darwin":
|
||||
return "macOS"
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ require (
|
||||
github.com/moloch--/sgn v0.0.4
|
||||
github.com/ncruces/go-sqlite3 v0.30.4
|
||||
github.com/reeflective/console v0.1.25
|
||||
github.com/reeflective/readline v1.1.3
|
||||
github.com/reeflective/readline v1.1.4
|
||||
github.com/rsteube/carapace v0.50.2
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cobra v1.10.2
|
||||
@@ -99,8 +99,8 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.65.1 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/carapace-sh/carapace v1.9.0 // indirect
|
||||
github.com/carapace-sh/carapace-shlex v1.1.0 // indirect
|
||||
github.com/carapace-sh/carapace v1.11.0 // indirect
|
||||
github.com/carapace-sh/carapace-shlex v1.1.1 // indirect
|
||||
github.com/catppuccin/go v0.3.0 // indirect
|
||||
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect
|
||||
github.com/charmbracelet/bubbletea v1.3.10 // indirect
|
||||
|
||||
@@ -93,8 +93,12 @@ github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMU
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/carapace-sh/carapace v1.9.0 h1:IuEP6YeeK4fhrH7MoOyL0I3GMhvROerkw6YGxkx8Fbw=
|
||||
github.com/carapace-sh/carapace v1.9.0/go.mod h1:Zs3DpsawpFm+8fEfRVYE9EzMBYH8u7S3Aw9yAAKX0JM=
|
||||
github.com/carapace-sh/carapace v1.11.0 h1:dTUFXvIrUTLd9BGLsfDt4wCgEyPl8bJeZOFDmN7fIfo=
|
||||
github.com/carapace-sh/carapace v1.11.0/go.mod h1:NVkuv1s8wyFvu7d98rdgYwqGKMB6GO1MEUZp5RcWri8=
|
||||
github.com/carapace-sh/carapace-shlex v1.1.0 h1:58QSJTUxghpa/rEn6+Z9UM/Hy0ipVHDUn9S1mp4i+ZE=
|
||||
github.com/carapace-sh/carapace-shlex v1.1.0/go.mod h1:lJ4ZsdxytE0wHJ8Ta9S7Qq0XpjgjU0mdfCqiI2FHx7M=
|
||||
github.com/carapace-sh/carapace-shlex v1.1.1 h1:ccmNeetAYZOk4IcV36youFDsXusT9uCNW2Njkw+QS+Q=
|
||||
github.com/carapace-sh/carapace-shlex v1.1.1/go.mod h1:lJ4ZsdxytE0wHJ8Ta9S7Qq0XpjgjU0mdfCqiI2FHx7M=
|
||||
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
|
||||
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
|
||||
@@ -166,10 +166,14 @@ func dirListHandler(data []byte, resp RPCResponse) {
|
||||
dirList.Exists = true
|
||||
} else {
|
||||
dirList.Exists = false
|
||||
dirList.Response = &commonpb.Response{Err: err.Error()}
|
||||
}
|
||||
dirList.Files = []*sliverpb.FileInfo{}
|
||||
rootDirInfo, err := rootDirEntry.Info()
|
||||
if err == nil && filter == "" {
|
||||
var rootDirInfo fs.FileInfo
|
||||
if rootDirEntry != nil {
|
||||
rootDirInfo, err = rootDirEntry.Info()
|
||||
}
|
||||
if err == nil && rootDirInfo != nil && filter == "" {
|
||||
// We should not get an error because we created the DirEntry object from the FileInfo object
|
||||
dirList.Files = append(dirList.Files, &sliverpb.FileInfo{
|
||||
Name: ".", // Cannot use the name from the FileInfo / DirEntry because that is the name of the directory
|
||||
@@ -193,6 +197,7 @@ func dirListHandler(data []byte, resp RPCResponse) {
|
||||
if err != nil {
|
||||
// Then this is a bad filter, and it will be a bad filter
|
||||
// on every iteration of the loop, so we might as well break now
|
||||
dirList.Response = &commonpb.Response{Err: err.Error()}
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -249,22 +254,26 @@ func getDirList(target string) (string, fs.DirEntry, []fs.DirEntry, error) {
|
||||
// {{end}}
|
||||
return "", nil, nil, err
|
||||
}
|
||||
if rootInfo, err := os.Stat(dir); !os.IsNotExist(err) {
|
||||
/*
|
||||
We could place the entry for the directory itself
|
||||
at the beginning of the returned slice of DirEntry
|
||||
objects, but then it is not clear if that is the
|
||||
root directory or a directory / file in the root
|
||||
directory with the same name as the root directory.
|
||||
|
||||
Using WalkDir is not great here because you cannot
|
||||
tell it to not be recursive, so we will be wasting
|
||||
cycles telling it to skip directories and files
|
||||
*/
|
||||
files, err := os.ReadDir(dir)
|
||||
return dir, fs.FileInfoToDirEntry(rootInfo), files, err
|
||||
rootInfo, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return dir, nil, []fs.DirEntry{}, errors.New("directory does not exist")
|
||||
}
|
||||
return dir, nil, []fs.DirEntry{}, err
|
||||
}
|
||||
return dir, nil, []fs.DirEntry{}, errors.New("directory does not exist")
|
||||
/*
|
||||
We could place the entry for the directory itself
|
||||
at the beginning of the returned slice of DirEntry
|
||||
objects, but then it is not clear if that is the
|
||||
root directory or a directory / file in the root
|
||||
directory with the same name as the root directory.
|
||||
|
||||
Using WalkDir is not great here because you cannot
|
||||
tell it to not be recursive, so we will be wasting
|
||||
cycles telling it to skip directories and files
|
||||
*/
|
||||
files, err := os.ReadDir(dir)
|
||||
return dir, fs.FileInfoToDirEntry(rootInfo), files, err
|
||||
}
|
||||
|
||||
func rmHandler(data []byte, resp RPCResponse) {
|
||||
|
||||
+1027
-1007
File diff suppressed because it is too large.
Load diff
@@ -586,6 +586,9 @@ message WebContent {
|
||||
string ContentType = 4;
|
||||
uint64 Size = 5 [ jstype = JS_STRING ];
|
||||
|
||||
string OriginalFile = 6;
|
||||
string Sha256 = 7;
|
||||
|
||||
bytes Content = 9;
|
||||
}
|
||||
|
||||
@@ -1316,4 +1319,4 @@ message CertificateData {
|
||||
|
||||
message CertificateInfo {
|
||||
repeated CertificateData info = 1;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v6.33.2
|
||||
// protoc v3.15.8
|
||||
// source: commonpb/common.proto
|
||||
|
||||
package commonpb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v6.33.2
|
||||
// protoc v3.15.8
|
||||
// source: dnspb/dns.proto
|
||||
|
||||
package dnspb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v6.33.2
|
||||
// protoc v3.15.8
|
||||
// source: rpcpb/services.proto
|
||||
|
||||
package rpcpb
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v6.33.2
|
||||
// protoc v3.15.8
|
||||
// source: sliverpb/sliver.proto
|
||||
|
||||
package sliverpb
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/bishopfox/sliver/server/assets"
|
||||
"github.com/bishopfox/sliver/server/log"
|
||||
@@ -47,6 +48,13 @@ var (
|
||||
ErrInvalidDialect = errors.New("invalid SQL Dialect")
|
||||
|
||||
databaseConfigLog = log.NamedLogger("config", "database")
|
||||
|
||||
defaultSQLitePragmas = map[string]string{
|
||||
"journal_mode": "WAL", // reduce writer blocking, better concurrency
|
||||
"busy_timeout": "5000", // wait for locks instead of failing fast (ms)
|
||||
"synchronous": "NORMAL", // faster WAL syncs while retaining durability
|
||||
"temp_store": "MEMORY", // keep temp structures off disk for quicker startup
|
||||
}
|
||||
)
|
||||
|
||||
// GetDatabaseConfigPath - File path to config.json
|
||||
@@ -66,7 +74,8 @@ type DatabaseConfig struct {
|
||||
Host string `json:"host"`
|
||||
Port uint16 `json:"port"`
|
||||
|
||||
Params map[string]string `json:"params"`
|
||||
Params map[string]string `json:"params"`
|
||||
Pragmas map[string]string `json:"pragmas"`
|
||||
|
||||
MaxIdleConns int `json:"max_idle_conns"`
|
||||
MaxOpenConns int `json:"max_open_conns"`
|
||||
@@ -80,7 +89,7 @@ func (c *DatabaseConfig) DSN() (string, error) {
|
||||
switch c.Dialect {
|
||||
case Sqlite:
|
||||
filePath := filepath.Join(assets.GetRootAppDir(), "sliver.db")
|
||||
params := encodeParams(c.Params)
|
||||
params := encodeSQLiteParams(c.Params, c.Pragmas)
|
||||
return fmt.Sprintf("file:%s?%s", filePath, params), nil
|
||||
case MySQL:
|
||||
user := url.QueryEscape(c.Username)
|
||||
@@ -112,6 +121,44 @@ func encodeParams(rawParams map[string]string) string {
|
||||
return params.Encode()
|
||||
}
|
||||
|
||||
func encodeSQLiteParams(rawParams map[string]string, pragmas map[string]string) string {
|
||||
if rawParams == nil {
|
||||
rawParams = map[string]string{}
|
||||
}
|
||||
params := url.Values{}
|
||||
|
||||
// Preserve any user-provided parameters first.
|
||||
for key, value := range rawParams {
|
||||
params.Add(key, value)
|
||||
}
|
||||
|
||||
// Apply safer defaults only when the user has not set any custom pragma.
|
||||
if _, ok := rawParams["_pragma"]; !ok {
|
||||
selectedPragmas := pragmas
|
||||
if len(selectedPragmas) == 0 {
|
||||
selectedPragmas = defaultSQLitePragmas
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(selectedPragmas))
|
||||
for key := range selectedPragmas {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, key := range keys {
|
||||
value := selectedPragmas[key]
|
||||
params.Add("_pragma", fmt.Sprintf("%s(%s)", key, value))
|
||||
}
|
||||
}
|
||||
|
||||
// Encourage shared cache to play nicer with WAL and multiple connections.
|
||||
if _, ok := rawParams["cache"]; !ok {
|
||||
params.Add("cache", "shared")
|
||||
}
|
||||
|
||||
return params.Encode()
|
||||
}
|
||||
|
||||
// Save - Save config file to disk
|
||||
func (c *DatabaseConfig) Save() error {
|
||||
configPath := GetDatabaseConfigPath()
|
||||
@@ -161,6 +208,8 @@ func GetDatabaseConfig() *DatabaseConfig {
|
||||
config.MaxOpenConns = 1
|
||||
}
|
||||
|
||||
ensureSQLiteDefaults(config)
|
||||
|
||||
err := config.Save() // This updates the config with any missing fields
|
||||
if err != nil {
|
||||
databaseConfigLog.Errorf("Failed to save default config %s", err)
|
||||
@@ -171,9 +220,29 @@ func GetDatabaseConfig() *DatabaseConfig {
|
||||
func getDefaultDatabaseConfig() *DatabaseConfig {
|
||||
return &DatabaseConfig{
|
||||
Dialect: Sqlite,
|
||||
Pragmas: defaultSQLitePragmas,
|
||||
MaxIdleConns: 10,
|
||||
MaxOpenConns: 100,
|
||||
|
||||
LogLevel: "warn",
|
||||
}
|
||||
}
|
||||
|
||||
func ensureSQLiteDefaults(c *DatabaseConfig) {
|
||||
if c.Dialect != Sqlite {
|
||||
return
|
||||
}
|
||||
if c.Params == nil {
|
||||
c.Params = map[string]string{}
|
||||
}
|
||||
if _, ok := c.Params["_pragma"]; ok {
|
||||
// User provided explicit pragmas; leave Pragmas untouched to avoid confusion.
|
||||
return
|
||||
}
|
||||
if len(c.Pragmas) == 0 {
|
||||
c.Pragmas = defaultSQLitePragmas
|
||||
}
|
||||
if _, ok := c.Params["cache"]; !ok {
|
||||
c.Params["cache"] = "shared"
|
||||
}
|
||||
}
|
||||
+12
-2
@@ -789,8 +789,18 @@ func AddContent(pbWebContent *clientpb.WebContent, webContentDir string) (*clien
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
dbWebContent.ContentType = pbWebContent.ContentType
|
||||
dbWebContent.Size = pbWebContent.Size
|
||||
if pbWebContent.ContentType != "" {
|
||||
dbWebContent.ContentType = pbWebContent.ContentType
|
||||
}
|
||||
if pbWebContent.Size != 0 {
|
||||
dbWebContent.Size = pbWebContent.Size
|
||||
}
|
||||
if pbWebContent.OriginalFile != "" {
|
||||
dbWebContent.OriginalFile = pbWebContent.OriginalFile
|
||||
}
|
||||
if pbWebContent.Sha256 != "" {
|
||||
dbWebContent.Sha256 = pbWebContent.Sha256
|
||||
}
|
||||
|
||||
dbModelWebContent := models.WebContentFromProtobuf(dbWebContent)
|
||||
err = Session().Save(&dbModelWebContent).Error
|
||||
|
||||
+20
-14
@@ -67,9 +67,11 @@ type WebContent struct {
|
||||
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
|
||||
WebsiteID uuid.UUID `gorm:"type:uuid;"`
|
||||
|
||||
Path string `gorm:"primaryKey"`
|
||||
Size uint64
|
||||
ContentType string
|
||||
Path string `gorm:"primaryKey"`
|
||||
Size uint64
|
||||
ContentType string
|
||||
OriginalFile string
|
||||
Sha256 string
|
||||
}
|
||||
|
||||
// BeforeCreate - GORM hook to automatically set values
|
||||
@@ -81,12 +83,14 @@ func (wc *WebContent) BeforeCreate(tx *gorm.DB) (err error) {
|
||||
// ToProtobuf - Converts to protobuf object
|
||||
func (wc *WebContent) ToProtobuf(content *[]byte) *clientpb.WebContent {
|
||||
return &clientpb.WebContent{
|
||||
ID: wc.ID.String(),
|
||||
WebsiteID: wc.WebsiteID.String(),
|
||||
Path: wc.Path,
|
||||
Size: uint64(wc.Size),
|
||||
ContentType: wc.ContentType,
|
||||
Content: *content,
|
||||
ID: wc.ID.String(),
|
||||
WebsiteID: wc.WebsiteID.String(),
|
||||
Path: wc.Path,
|
||||
Size: uint64(wc.Size),
|
||||
ContentType: wc.ContentType,
|
||||
OriginalFile: wc.OriginalFile,
|
||||
Sha256: wc.Sha256,
|
||||
Content: *content,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,10 +99,12 @@ func WebContentFromProtobuf(pbWebContent *clientpb.WebContent) WebContent {
|
||||
websiteUUID, _ := uuid.FromString(pbWebContent.WebsiteID)
|
||||
|
||||
return WebContent{
|
||||
ID: siteUUID,
|
||||
WebsiteID: websiteUUID,
|
||||
Path: pbWebContent.Path,
|
||||
Size: pbWebContent.Size,
|
||||
ContentType: pbWebContent.ContentType,
|
||||
ID: siteUUID,
|
||||
WebsiteID: websiteUUID,
|
||||
Path: pbWebContent.Path,
|
||||
Size: pbWebContent.Size,
|
||||
ContentType: pbWebContent.ContentType,
|
||||
OriginalFile: pbWebContent.OriginalFile,
|
||||
Sha256: pbWebContent.Sha256,
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ package website
|
||||
*/
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -87,14 +89,30 @@ func AddContent(name string, pbWebContent *clientpb.WebContent) error {
|
||||
pbWebContent.WebsiteID = website.ID
|
||||
}
|
||||
|
||||
if pbWebContent.Size == 0 && len(pbWebContent.Content) > 0 {
|
||||
pbWebContent.Size = uint64(len(pbWebContent.Content))
|
||||
}
|
||||
if pbWebContent.OriginalFile == "" {
|
||||
pbWebContent.OriginalFile = filepath.Base(pbWebContent.Path)
|
||||
}
|
||||
|
||||
if len(pbWebContent.Content) > 0 {
|
||||
sha := sha256.Sum256(pbWebContent.Content)
|
||||
pbWebContent.Sha256 = hex.EncodeToString(sha[:])
|
||||
}
|
||||
|
||||
webContent, err := db.AddContent(pbWebContent, webContentDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write content to disk
|
||||
webContentPath := filepath.Join(webContentDir, webContent.ID)
|
||||
return os.WriteFile(webContentPath, pbWebContent.Content, 0600)
|
||||
// Write content to disk when provided (metadata-only updates skip disk writes)
|
||||
if len(pbWebContent.Content) > 0 {
|
||||
webContentPath := filepath.Join(webContentDir, webContent.ID)
|
||||
return os.WriteFile(webContentPath, pbWebContent.Content, 0600)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveContent - Remove website content for a path
|
||||
|
||||
+5
-1
@@ -111,8 +111,12 @@ func (t TokenSlice) WordbreakPrefix() string {
|
||||
break
|
||||
}
|
||||
|
||||
if token.Type == WORDBREAK_TOKEN {
|
||||
if !found && token.Type == WORDBREAK_TOKEN {
|
||||
found = true
|
||||
if token.Value == "@" {
|
||||
// Seems although `@` is a wordbreak, it weirdly is not part of the prefix.
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ package shlex
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
const BASH_WORDBREAKS = " \t\r\n" + `"'><=;|&(:`
|
||||
const BASH_WORDBREAKS = " \t\r\n" + `"'@><=;|&(:`
|
||||
|
||||
type WordbreakType int
|
||||
|
||||
|
||||
+27
-3
@@ -437,6 +437,15 @@ func (a Action) Timeout(d time.Duration, alternative Action) Action {
|
||||
})
|
||||
}
|
||||
|
||||
// Unique ensures the Action only contains unique values.
|
||||
func (a Action) Unique() Action {
|
||||
return ActionCallback(func(c Context) Action {
|
||||
invoked := a.Invoke(c)
|
||||
invoked.action.rawValues = invoked.action.rawValues.Unique()
|
||||
return invoked.ToA()
|
||||
})
|
||||
}
|
||||
|
||||
// UniqueList wraps the Action in an ActionMultiParts with given divider.
|
||||
func (a Action) UniqueList(divider string) Action {
|
||||
return ActionMultiParts(divider, func(c Context) Action {
|
||||
@@ -513,9 +522,14 @@ func (a Action) UidF(f func(s string, uc uid.Context) (*url.URL, error)) Action
|
||||
}
|
||||
|
||||
// Usage sets the usage.
|
||||
func (a Action) Usage(usage string, args ...interface{}) Action {
|
||||
func (a Action) Usage(usage string, args ...any) Action {
|
||||
return a.UsageF(func() string {
|
||||
return fmt.Sprintf(usage, args...)
|
||||
if len(args) == 0 {
|
||||
return usage
|
||||
}
|
||||
return func(u string, a ...any) string { // avoids failed inferred printf check: non-constant format string
|
||||
return fmt.Sprintf(u, a...)
|
||||
}(usage, args...)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -548,6 +562,9 @@ func (a Action) Query(scheme, host, path string, opts ...string) Action {
|
||||
values.Set(opts[i], opts[i+1])
|
||||
}
|
||||
}
|
||||
if c.Value != "" {
|
||||
values.Set("C_VALUE", c.Value)
|
||||
}
|
||||
query.RawQuery = values.Encode()
|
||||
}
|
||||
a.meta.Queries.Add(query.String())
|
||||
@@ -558,10 +575,17 @@ func (a Action) Query(scheme, host, path string, opts ...string) Action {
|
||||
// QueryF TODO experimental
|
||||
func (a Action) QueryF(f func(s string, uc uid.Context) (*url.URL, error)) Action { // TODO remove the string
|
||||
return ActionCallback(func(c Context) Action {
|
||||
query, err := f(c.Value, c)
|
||||
query, err := f("", c) // TODO string parameter from uid isn't really needed here
|
||||
if err != nil {
|
||||
return ActionMessage(err.Error())
|
||||
}
|
||||
|
||||
if c.Value != "" {
|
||||
values := query.Query()
|
||||
values.Set("C_VALUE", c.Value)
|
||||
query.RawQuery = values.Encode()
|
||||
}
|
||||
|
||||
a.meta.Queries.Add(query.String())
|
||||
return a
|
||||
})
|
||||
|
||||
+3
-4
@@ -2,6 +2,7 @@
|
||||
package carapace
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"os"
|
||||
|
||||
"github.com/carapace-sh/carapace/internal/shell"
|
||||
@@ -79,9 +80,7 @@ func (c Carapace) FlagCompletion(actions ActionMap) {
|
||||
if e.flag == nil {
|
||||
e.flag = actions
|
||||
} else {
|
||||
for name, action := range actions {
|
||||
e.flag[name] = action
|
||||
}
|
||||
maps.Copy(e.flag, actions)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +125,7 @@ func IsCallback() bool {
|
||||
// func TestCarapace(t *testing.T) {
|
||||
// carapace.Test(t)
|
||||
// }
|
||||
func Test(t interface{ Error(args ...interface{}) }) {
|
||||
func Test(t interface{ Error(args ...any) }) {
|
||||
for _, e := range storage.check() {
|
||||
t.Error(e)
|
||||
}
|
||||
|
||||
+20
-32
@@ -8,6 +8,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/carapace-sh/carapace/internal/common"
|
||||
@@ -135,6 +136,10 @@ func ActionExecute(cmd *cobra.Command) Action {
|
||||
// ActionDirectories completes directories.
|
||||
func ActionDirectories() Action {
|
||||
return ActionCallback(func(c Context) Action {
|
||||
cDir, err := c.Abs(c.Dir)
|
||||
if err != nil {
|
||||
return ActionMessage(err.Error())
|
||||
}
|
||||
return actionPath([]string{""}, true).
|
||||
MultiParts("/").
|
||||
StyleF(style.ForPath).
|
||||
@@ -145,19 +150,17 @@ func ActionDirectories() Action {
|
||||
}
|
||||
return url.Parse("file://" + uid.PathEscape(abs))
|
||||
}).
|
||||
QueryF(func(s string, uc uid.Context) (*url.URL, error) {
|
||||
abs, err := c.Abs(c.Dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return url.Parse(fmt.Sprintf("file://%v?directories=true", uid.PathEscape(abs)))
|
||||
})
|
||||
Query("file", "directories", "", "C_DIR", cDir)
|
||||
}).Tag("directories")
|
||||
}
|
||||
|
||||
// ActionFiles completes files with optional suffix filtering.
|
||||
func ActionFiles(suffix ...string) Action {
|
||||
return ActionCallback(func(c Context) Action {
|
||||
cDir, err := c.Abs(c.Dir)
|
||||
if err != nil {
|
||||
return ActionMessage(err.Error())
|
||||
}
|
||||
return actionPath(suffix, false).
|
||||
MultiParts("/").
|
||||
StyleF(style.ForPath).
|
||||
@@ -168,23 +171,9 @@ func ActionFiles(suffix ...string) Action {
|
||||
}
|
||||
return url.Parse("file://" + uid.PathEscape(abs))
|
||||
}).
|
||||
QueryF(func(s string, uc uid.Context) (*url.URL, error) {
|
||||
abs, err := c.Abs(c.Dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query := &url.URL{
|
||||
Scheme: "file",
|
||||
Path: abs,
|
||||
}
|
||||
if len(suffix) > 0 {
|
||||
values := query.Query()
|
||||
values.Set("suffix", strings.Join(suffix, ","))
|
||||
query.RawQuery = values.Encode()
|
||||
}
|
||||
return query, nil
|
||||
})
|
||||
// Query("file", "", c.Dir, "suffix", strings.Join(suffix, ","))
|
||||
Query("file", "files", "",
|
||||
"C_DIR", cDir,
|
||||
"suffix", strings.Join(suffix, ","))
|
||||
}).Tag("files")
|
||||
}
|
||||
|
||||
@@ -247,7 +236,7 @@ func ActionStyledValuesDescribed(values ...string) Action {
|
||||
}
|
||||
|
||||
// ActionMessage displays a help messages in places where no completions can be generated.
|
||||
func ActionMessage(msg string, args ...interface{}) Action {
|
||||
func ActionMessage(msg string, args ...any) Action {
|
||||
return ActionCallback(func(c Context) Action {
|
||||
if len(args) > 0 {
|
||||
msg = fmt.Sprintf(msg, args...)
|
||||
@@ -477,14 +466,15 @@ func ActionExecutables(dirs ...string) Action {
|
||||
}
|
||||
manDescriptions := man.Descriptions(c.Value) // TODO allow additional descriptions to be registered somewhere for carapace-bin (key, value,...)
|
||||
batch := Batch()
|
||||
for i := len(dirs) - 1; i >= 0; i-- {
|
||||
batch = append(batch, actionDirectoryExecutables(dirs[i], c.Value, manDescriptions))
|
||||
for _, dir := range dirs {
|
||||
batch = append(batch, actionDirectoryExecutables(dir, c.Value, manDescriptions))
|
||||
}
|
||||
return batch.ToA().
|
||||
UidF(func(s string, uc uid.Context) (*url.URL, error) {
|
||||
return &url.URL{Scheme: "cmd", Host: uid.PathEscape(s)}, nil
|
||||
}).
|
||||
Query("cmd", "", "", "directories", strings.Join(dirs, ", "))
|
||||
Query("cmd", "", "", "directories", strings.Join(dirs, ", ")).
|
||||
Unique()
|
||||
}).Tag("executables")
|
||||
}
|
||||
|
||||
@@ -553,10 +543,8 @@ func ActionCommands(cmd *cobra.Command) Action {
|
||||
return ActionCallback(func(c Context) Action {
|
||||
if len(c.Args) > 0 {
|
||||
for _, subCommand := range cmd.Commands() {
|
||||
for _, name := range append(subCommand.Aliases, subCommand.Name()) {
|
||||
if name == c.Args[0] { // cmd.Find is too lenient
|
||||
return ActionCommands(subCommand).Shift(1)
|
||||
}
|
||||
if slices.Contains(append(subCommand.Aliases, subCommand.Name()), c.Args[0]) { // cmd.Find is too lenient
|
||||
return ActionCommands(subCommand).Shift(1)
|
||||
}
|
||||
}
|
||||
return ActionMessage("unknown subcommand %#v for %#v", c.Args[0], cmd.Name())
|
||||
|
||||
+26
@@ -1,6 +1,8 @@
|
||||
package carapace
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/carapace-sh/carapace/internal/common"
|
||||
"github.com/carapace-sh/carapace/pkg/style"
|
||||
)
|
||||
@@ -42,3 +44,27 @@ func Diff(original, new Action) Action {
|
||||
return mergedBatch.ToA()
|
||||
})
|
||||
}
|
||||
|
||||
// TODO experimental - needs different signature
|
||||
// TODO return patch format
|
||||
func DiffPatch(original, new Action, c Context) []string {
|
||||
diff := Diff(original, new).Invoke(c)
|
||||
patch := make([]string, 0)
|
||||
for _, v := range diff.action.rawValues {
|
||||
s := v.Value
|
||||
if v.Description != "" {
|
||||
s += fmt.Sprintf(" (%v)", v.Description)
|
||||
}
|
||||
|
||||
switch v.Style {
|
||||
case style.Red:
|
||||
s = "- " + s
|
||||
case style.Green:
|
||||
s = "+ " + s
|
||||
default:
|
||||
s = " " + s
|
||||
}
|
||||
patch = append(patch, s)
|
||||
}
|
||||
return patch
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
go 1.19
|
||||
go 1.24
|
||||
|
||||
use (
|
||||
.
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
github.com/carapace-sh/carapace-shlex v1.0.0/go.mod h1:lJ4ZsdxytE0wHJ8Ta9S7Qq0XpjgjU0mdfCqiI2FHx7M=
|
||||
github.com/carapace-sh/carapace-pflag v1.1.0 h1:uB33wch8ADjP878NadQnbZ5vyROcj/Ac+wUQKMcIrQM=
|
||||
github.com/carapace-sh/carapace-pflag v1.1.0/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
+1
-1
@@ -20,7 +20,7 @@ func (m Mock) WorkDir() string {
|
||||
|
||||
type t interface {
|
||||
Name() string
|
||||
Fatal(...interface{})
|
||||
Fatal(...any)
|
||||
}
|
||||
|
||||
func NewMock(t t) *Mock {
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
// FromInvokedAction provides access to RawValues within an InvokedAction.
|
||||
// It is intended for testing purposes in Sandbox (circumventing dependency issues).
|
||||
var FromInvokedAction func(action interface{}) (Meta, RawValues)
|
||||
var FromInvokedAction func(action any) (Meta, RawValues)
|
||||
|
||||
// RawValue represents a completion candidate.
|
||||
type RawValue struct {
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/carapace-sh/carapace/pkg/xdg"
|
||||
)
|
||||
|
||||
type configMap map[string]interface{}
|
||||
type configMap map[string]any
|
||||
|
||||
func (c configMap) Keys() []string {
|
||||
keys := make([]string, 0)
|
||||
@@ -52,7 +52,7 @@ var config = struct {
|
||||
Styles: make(configMap),
|
||||
}
|
||||
|
||||
func RegisterStyle(name string, i interface{}) {
|
||||
func RegisterStyle(name string, i any) {
|
||||
config.Styles[name] = i
|
||||
}
|
||||
|
||||
|
||||
+5
-1
@@ -81,7 +81,11 @@ func CoverDir() string {
|
||||
return os.Getenv(CARAPACE_COVERDIR) // custom env for GOCOVERDIR so that it works together with `-coverprofile`
|
||||
}
|
||||
|
||||
func isGoRun() bool { return strings.HasPrefix(os.Args[0], os.TempDir()+"/go-build") }
|
||||
func isGoRun() bool {
|
||||
// go 1.24+: /home/rsteube/.cache/go-build/a7/a7883331b606dc3250005f1abdf4d17dea05c9eccf4ece0df5518311a118d211-d/example
|
||||
// go 1.23-: /tmp/go-build1035720725/b001/exe/example
|
||||
return strings.Contains(os.Args[0], "/go-build")
|
||||
}
|
||||
|
||||
func Match() string { // see match.Match
|
||||
return os.Getenv(CARAPACE_MATCH)
|
||||
|
||||
+2
-2
@@ -20,8 +20,8 @@ func Descriptions(s string) (descriptions map[string]string) {
|
||||
}
|
||||
|
||||
r := regexp.MustCompile(`^(?P<name>[^ ]+) [^-]+- (?P<description>.*)$`)
|
||||
lines := strings.Split(string(output), "\n")
|
||||
for _, line := range lines {
|
||||
lines := strings.SplitSeq(string(output), "\n")
|
||||
for line := range lines {
|
||||
if matches := r.FindStringSubmatch(line); len(matches) > 2 {
|
||||
descriptions[matches[1]] = matches[2]
|
||||
}
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ func (f FlagSet) IsShorthandSeries(arg string) bool {
|
||||
func (f FlagSet) IsMutuallyExclusive(flag *pflag.Flag) bool {
|
||||
if groups, ok := flag.Annotations["cobra_annotation_mutually_exclusive"]; ok {
|
||||
for _, group := range groups {
|
||||
for _, name := range strings.Split(group, " ") {
|
||||
for name := range strings.SplitSeq(group, " ") {
|
||||
if other := f.Lookup(name); other != nil && other.Changed && other != flag {
|
||||
return true
|
||||
}
|
||||
|
||||
+9
-9
@@ -11,7 +11,7 @@ import (
|
||||
// Snippet creates the bash completion script.
|
||||
func Snippet(cmd *cobra.Command) string {
|
||||
result := fmt.Sprintf(`#!/bin/bash
|
||||
_%v_completion() {
|
||||
_%[1]v_completion() {
|
||||
export COMP_LINE
|
||||
export COMP_POINT
|
||||
export COMP_TYPE
|
||||
@@ -19,12 +19,12 @@ _%v_completion() {
|
||||
|
||||
local nospace data compline="${COMP_LINE:0:${COMP_POINT}}"
|
||||
|
||||
if echo ${compline}"''" | xargs echo 2>/dev/null > /dev/null; then
|
||||
data=$(echo ${compline}"''" | xargs %v _carapace bash)
|
||||
elif echo ${compline} | sed "s/\$/'/" | xargs echo 2>/dev/null > /dev/null; then
|
||||
data=$(echo ${compline} | sed "s/\$/'/" | xargs %v _carapace bash)
|
||||
else
|
||||
data=$(echo ${compline} | sed 's/$/"/' | xargs %v _carapace bash)
|
||||
data=$(echo "${compline}''" | xargs %[2]v _carapace bash 2>/dev/null)
|
||||
if [ $? -eq 1 ]; then
|
||||
data=$(echo "${compline}'" | xargs %[2]v _carapace bash 2>/dev/null)
|
||||
if [ $? -eq 1 ]; then
|
||||
data=$(echo "${compline}\"" | xargs %[2]v _carapace bash 2>/dev/null)
|
||||
fi
|
||||
fi
|
||||
|
||||
IFS=$'\001' read -r -d '' nospace data <<<"${data}"
|
||||
@@ -36,8 +36,8 @@ _%v_completion() {
|
||||
[[ "${COMPREPLY[*]}" == "" ]] && COMPREPLY=() # fix for mapfile creating a non-empty array from empty command output
|
||||
}
|
||||
|
||||
complete -o noquote -F _%v_completion %v
|
||||
`, cmd.Name(), uid.Executable(), uid.Executable(), uid.Executable(), cmd.Name(), cmd.Name())
|
||||
complete -o noquote -F _%[1]v_completion %[1]v
|
||||
`, cmd.Name(), uid.Executable())
|
||||
|
||||
return result
|
||||
}
|
||||
+32
-19
@@ -8,28 +8,41 @@ import (
|
||||
)
|
||||
|
||||
func Snippet(cmd *cobra.Command) string {
|
||||
result := fmt.Sprintf(`local function %v_completion(word, word_index, line_state, match_builder)
|
||||
local compline = string.sub(line_state:getline(), 1, line_state:getcursor())
|
||||
result := fmt.Sprintf(`local function %[1]s_completion(word, word_index, line_state, match_builder)
|
||||
match_builder:setnosort()
|
||||
match_builder:setvolatile()
|
||||
os.setenv('CARAPACE_COMPLINE', line_state:getline():sub(1, line_state:getcursor()))
|
||||
|
||||
local output = io.popen("env CARAPACE_COMPLINE=" .. string.format("%%q", compline) .. " %v _carapace cmd-clink \"\""):read("*a")
|
||||
for line in string.gmatch(output, '[^\r\n]+') do
|
||||
local matches = {}
|
||||
for m in string.gmatch(line, '[^\t]+') do
|
||||
table.insert(matches, m)
|
||||
local file, pclose = io.popenyield('%[2]s _carapace cmd-clink %[1]s')
|
||||
|
||||
if not file then
|
||||
return false
|
||||
end
|
||||
match_builder:addmatch({
|
||||
match = matches[1],
|
||||
display = matches[2],
|
||||
description = matches[3],
|
||||
type = "word",
|
||||
appendchar = matches[4],
|
||||
suppressappend = false
|
||||
})
|
||||
end
|
||||
return true
|
||||
|
||||
for line in file:lines() do
|
||||
local matches = string.explode(line, '\t')
|
||||
|
||||
if matches[1] then
|
||||
match_builder:addmatch({
|
||||
match = matches[1],
|
||||
display = matches[2],
|
||||
description = matches[3],
|
||||
type = 'word',
|
||||
appendchar = matches[4] or ''
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
if pclose then
|
||||
pclose()
|
||||
else
|
||||
file:close()
|
||||
end
|
||||
|
||||
return not match_builder:isempty()
|
||||
end
|
||||
|
||||
clink.argmatcher("%v"):addarg({nowordbreakchars="'‵+;,", %v_completion}):loop(1)
|
||||
`, cmd.Name(), uid.Executable(), cmd.Name(), cmd.Name())
|
||||
clink.argmatcher(50, '%[1]s', '%[1]s.exe'):addarg({nowordbreakchars="'`+"`"+`=+;,", %[1]s_completion}):loop(1)
|
||||
`, cmd.Name(), uid.Executable())
|
||||
return result
|
||||
}
|
||||
+11
-15
@@ -10,23 +10,19 @@ import (
|
||||
|
||||
// Snippet creates the fish completion script.
|
||||
func Snippet(cmd *cobra.Command) string {
|
||||
return fmt.Sprintf(`function _%v_quote_suffix
|
||||
if not commandline -cp | xargs echo 2>/dev/null >/dev/null
|
||||
if commandline -cp | sed 's/$/"/'| xargs echo 2>/dev/null >/dev/null
|
||||
echo '"'
|
||||
else if commandline -cp | sed "s/\$/'/"| xargs echo 2>/dev/null >/dev/null
|
||||
echo "'"
|
||||
return fmt.Sprintf(`function _%[1]v_completion
|
||||
set --local data
|
||||
IFS='' set data (echo (commandline -cp)'' | sed "s/ \$/ ''/" | xargs %[2]v _carapace fish 2>/dev/null)
|
||||
if [ $status -eq 1 ]
|
||||
IFS='' set data (echo (commandline -cp)"'" | sed "s/ \$/ ''/" | xargs %[2]v _carapace fish 2>/dev/null)
|
||||
if [ $status -eq 1 ]
|
||||
IFS='' set data (echo (commandline -cp)'"' | sed "s/ \$/ ''/" | xargs %[2]v _carapace fish 2>/dev/null)
|
||||
end
|
||||
else
|
||||
echo ""
|
||||
end
|
||||
echo $data
|
||||
end
|
||||
|
||||
function _%v_callback
|
||||
commandline -cp | sed "s/\$/"(_%v_quote_suffix)"/" | sed "s/ \$/ ''/" | xargs %v _carapace fish
|
||||
end
|
||||
|
||||
complete -e '%v'
|
||||
complete -c '%v' -f -a '(_%v_callback)' -r
|
||||
`, cmd.Name(), cmd.Name(), cmd.Name(), uid.Executable(), cmd.Name(), cmd.Name(), cmd.Name())
|
||||
complete -e '%[1]v'
|
||||
complete -c '%[1]v' -f -a '(_%[1]v_completion)' -r
|
||||
`, cmd.Name(), uid.Executable())
|
||||
}
|
||||
+1
-1
@@ -36,7 +36,7 @@ func (nd *namedDirectories) Replace(s string) string {
|
||||
|
||||
func init() {
|
||||
if hashDirs := env.Hashdirs(); hashDirs != "" {
|
||||
for _, line := range strings.Split(hashDirs, "\n") {
|
||||
for line := range strings.SplitSeq(hashDirs, "\n") {
|
||||
if splitted := strings.SplitN(line, "=", 2); len(splitted) == 2 {
|
||||
NamedDirectories[splitted[0]] = splitted[1]
|
||||
}
|
||||
|
||||
+12
-11
@@ -11,17 +11,18 @@ import (
|
||||
// Snippet creates the zsh completion script
|
||||
func Snippet(cmd *cobra.Command) string {
|
||||
return fmt.Sprintf(`#compdef %v
|
||||
function _%v_completion {
|
||||
local words=${words[@]:0:$CURRENT}
|
||||
function _%[1]v_completion {
|
||||
local compline=${words[@]:0:$CURRENT}
|
||||
local IFS=$'\n'
|
||||
local lines
|
||||
|
||||
# shellcheck disable=SC2086,SC2154,SC2155
|
||||
if echo ${words}"''" | xargs echo 2>/dev/null > /dev/null; then
|
||||
local lines="$(echo ${words}"''" | CARAPACE_COMPLINE="${words}" CARAPACE_ZSH_HASH_DIRS="$(hash -d)" xargs %v _carapace zsh )"
|
||||
elif echo ${words} | sed "s/\$/'/" | xargs echo 2>/dev/null > /dev/null; then
|
||||
local lines="$(echo ${words} | sed "s/\$/'/" | CARAPACE_COMPLINE="${words}" CARAPACE_ZSH_HASH_DIRS="$(hash -d)" xargs %v _carapace zsh)"
|
||||
else
|
||||
local lines="$(echo ${words} | sed 's/$/"/' | CARAPACE_COMPLINE="${words}" CARAPACE_ZSH_HASH_DIRS="$(hash -d)" xargs %v _carapace zsh)"
|
||||
lines="$(echo "${compline}''" | CARAPACE_COMPLINE="${compline}" CARAPACE_ZSH_HASH_DIRS="$(hash -d)" xargs %[2]v _carapace zsh 2>/dev/null)"
|
||||
if [ $? -eq 1 ]; then
|
||||
lines="$(echo "${compline}'" | CARAPACE_COMPLINE="${compline}" CARAPACE_ZSH_HASH_DIRS="$(hash -d)" xargs %[2]v _carapace zsh 2>/dev/null)"
|
||||
if [ $? -eq 1 ]; then
|
||||
lines="$(echo "${compline}\"" | CARAPACE_COMPLINE="${compline}" CARAPACE_ZSH_HASH_DIRS="$(hash -d)" xargs %[2]v _carapace zsh 2>/dev/null)"
|
||||
fi
|
||||
fi
|
||||
|
||||
local zstyle message data
|
||||
@@ -41,7 +42,7 @@ function _%v_completion {
|
||||
[[ ${#valuesArr[@]} -gt 1 ]] && _describe -t "${tag}" "${tag}" displaysArr valuesArr -Q -S ''
|
||||
done <<<"${data}"
|
||||
}
|
||||
compquote '' 2>/dev/null && _%v_completion
|
||||
compdef _%v_completion %v
|
||||
`, cmd.Name(), cmd.Name(), uid.Executable(), uid.Executable(), uid.Executable(), cmd.Name(), cmd.Name(), cmd.Name())
|
||||
compquote '' 2>/dev/null && _%[1]v_completion
|
||||
compdef _%[1]v_completion %[1]v
|
||||
`, cmd.Name(), uid.Executable())
|
||||
}
|
||||
+2
-2
@@ -172,7 +172,7 @@ func (ia InvokedAction) ToMultiPartsA(dividers ...string) Action {
|
||||
})
|
||||
}
|
||||
|
||||
func (ia InvokedAction) Query(q string) InvokedAction {
|
||||
func (ia InvokedAction) QueryS(q string) InvokedAction {
|
||||
ia.action.meta.Queries.Add(q)
|
||||
return ia
|
||||
}
|
||||
@@ -182,7 +182,7 @@ func (ia InvokedAction) value(shell string, value string) string {
|
||||
}
|
||||
|
||||
func init() {
|
||||
common.FromInvokedAction = func(i interface{}) (common.Meta, common.RawValues) {
|
||||
common.FromInvokedAction = func(i any) (common.Meta, common.RawValues) {
|
||||
if invoked, ok := i.(InvokedAction); ok {
|
||||
return invoked.action.meta, invoked.action.rawValues
|
||||
}
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ import (
|
||||
// func init() {
|
||||
// Register("carapace", &Carapace)
|
||||
// }
|
||||
func Register(name string, i interface{}) { config.RegisterStyle(name, i) }
|
||||
func Register(name string, i any) { config.RegisterStyle(name, i) }
|
||||
|
||||
// Set a style
|
||||
//
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ func SGR(s string) string { return Parse(s).SGR() }
|
||||
|
||||
func Parse(s string) ui.Style {
|
||||
stylings := make([]ui.Styling, 0)
|
||||
for _, word := range strings.Split(s, " ") {
|
||||
for word := range strings.SplitSeq(s, " ") {
|
||||
if styling := ui.ParseStyling(word); styling != nil {
|
||||
stylings = append(stylings, styling)
|
||||
}
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ func ConfigDirs() (dirs []string, err error) {
|
||||
|
||||
if v, ok := os.LookupEnv("XDG_CONFIG_DIRS"); ok {
|
||||
xdgDirs := make([]string, 0)
|
||||
for _, dir := range strings.Split(v, string(os.PathSeparator)) {
|
||||
for dir := range strings.SplitSeq(v, string(os.PathSeparator)) {
|
||||
if filepath.IsAbs(dir) {
|
||||
xdgDirs = append(xdgDirs, dir)
|
||||
}
|
||||
|
||||
Generated
Vendored
+8
-11
@@ -76,13 +76,10 @@ func toSubstr(s string, args ...string) string {
|
||||
if pos < 0 {
|
||||
// if pos is negative (counts from the end) add it
|
||||
// to length to get first character offset
|
||||
pos = len(s) + pos
|
||||
|
||||
// if negative offset exceeds the length of the string
|
||||
// start from 0
|
||||
if pos < 0 {
|
||||
pos = 0
|
||||
}
|
||||
pos = max(
|
||||
// if negative offset exceeds the length of the string
|
||||
// start from 0
|
||||
len(s)+pos, 0)
|
||||
}
|
||||
|
||||
if len(args) == 1 {
|
||||
@@ -122,9 +119,9 @@ func replaceAll(s string, args ...string) string {
|
||||
case 0:
|
||||
return s
|
||||
case 1:
|
||||
return strings.Replace(s, args[0], "", -1)
|
||||
return strings.ReplaceAll(s, args[0], "")
|
||||
default:
|
||||
return strings.Replace(s, args[0], args[1], -1)
|
||||
return strings.ReplaceAll(s, args[0], args[1])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,8 +156,8 @@ func replaceSuffix(s string, args ...string) string {
|
||||
if len(args) != 2 {
|
||||
return s
|
||||
}
|
||||
if strings.HasSuffix(s, args[0]) {
|
||||
s = strings.TrimSuffix(s, args[0])
|
||||
if before, ok := strings.CutSuffix(s, args[0]); ok {
|
||||
s = before
|
||||
s = s + args[1]
|
||||
}
|
||||
return s
|
||||
|
||||
Generated
Vendored
-4
@@ -235,10 +235,6 @@ func acceptOneHash(r rune, i int) bool {
|
||||
return r == '#' && i == 1
|
||||
}
|
||||
|
||||
func acceptNone(r rune, i int) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func acceptNotClosing(r rune, i int) bool {
|
||||
return r != '}'
|
||||
}
|
||||
|
||||
Generated
Vendored
+2
-2
@@ -3,7 +3,7 @@ package envsubst
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/carapace-sh/carapace/third_party/github.com/drone/envsubst/parse"
|
||||
)
|
||||
@@ -38,7 +38,7 @@ func Parse(s string) (t *Template, err error) {
|
||||
// ParseFile creates a new shell format template and parses the template
|
||||
// definition from the named file.
|
||||
func ParseFile(path string) (*Template, error) {
|
||||
b, err := ioutil.ReadFile(path)
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Generated
Vendored
+2
-2
@@ -79,14 +79,14 @@ var featureForName = map[string]feature{
|
||||
// fields are silently ignored.
|
||||
func parseLsColor(s string) *colorist {
|
||||
lc := &colorist{make(map[feature]string), make(map[string]string)}
|
||||
for _, spec := range strings.Split(s, ":") {
|
||||
for spec := range strings.SplitSeq(s, ":") {
|
||||
words := strings.Split(spec, "=")
|
||||
if len(words) != 2 {
|
||||
continue
|
||||
}
|
||||
key, value := words[0], words[1]
|
||||
filterValues := []string{}
|
||||
for _, splitValue := range strings.Split(value, ";") {
|
||||
for splitValue := range strings.SplitSeq(value, ";") {
|
||||
if strings.Count(splitValue, "0") == len(splitValue) {
|
||||
continue
|
||||
}
|
||||
|
||||
Generated
Vendored
+1
-2
@@ -1,11 +1,10 @@
|
||||
//go:build !solaris
|
||||
// +build !solaris
|
||||
|
||||
package lscolors
|
||||
|
||||
import "os"
|
||||
|
||||
func isDoor(info os.FileInfo) bool {
|
||||
func isDoor(_ os.FileInfo) bool {
|
||||
// Doors are only supported on Solaris.
|
||||
return false
|
||||
}
|
||||
Generated
Vendored
-1
@@ -1,5 +1,4 @@
|
||||
//go:build !windows && !plan9
|
||||
// +build !windows,!plan9
|
||||
|
||||
package lscolors
|
||||
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -2,7 +2,7 @@ package lscolors
|
||||
|
||||
import "os"
|
||||
|
||||
func isMultiHardlink(info os.FileInfo) bool {
|
||||
func isMultiHardlink(_ os.FileInfo) bool {
|
||||
// Windows supports hardlinks, but it is not exposed directly. We omit the
|
||||
// implementation for now.
|
||||
// TODO: Maybe implement it?
|
||||
|
||||
Generated
Vendored
+1
-3
@@ -5,8 +5,6 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const sgrPrefix = "\033["
|
||||
|
||||
var sgrStyling = map[int]Styling{
|
||||
0: Reset,
|
||||
1: Bold,
|
||||
@@ -73,7 +71,7 @@ func StylingFromSGR(s string) Styling {
|
||||
|
||||
func getSGRCodes(s string) []int {
|
||||
var codes []int
|
||||
for _, part := range strings.Split(s, ";") {
|
||||
for part := range strings.SplitSeq(s, ";") {
|
||||
if part == "" {
|
||||
codes = append(codes, 0)
|
||||
} else {
|
||||
|
||||
Generated
Vendored
+3
-3
@@ -44,8 +44,8 @@ func (s Style) SGR() string {
|
||||
|
||||
// MergeFromOptions merges all recognized values from a map to the current
|
||||
// Style.
|
||||
func (s *Style) MergeFromOptions(options map[string]interface{}) error {
|
||||
assignColor := func(val interface{}, colorField *Color) string {
|
||||
func (s *Style) MergeFromOptions(options map[string]any) error {
|
||||
assignColor := func(val any, colorField *Color) string {
|
||||
if val == "default" {
|
||||
*colorField = nil
|
||||
return ""
|
||||
@@ -58,7 +58,7 @@ func (s *Style) MergeFromOptions(options map[string]interface{}) error {
|
||||
}
|
||||
return "valid color string"
|
||||
}
|
||||
assignBool := func(val interface{}, attrField *bool) string {
|
||||
assignBool := func(val any, attrField *bool) string {
|
||||
if b, ok := val.(bool); ok {
|
||||
*attrField = b
|
||||
} else {
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -144,7 +144,7 @@ func ParseStyling(s string) Styling {
|
||||
return parseOneStyling(s)
|
||||
}
|
||||
var joint jointStyling
|
||||
for _, subs := range strings.Split(s, " ") {
|
||||
for subs := range strings.SplitSeq(s, " ") {
|
||||
parsed := parseOneStyling(subs)
|
||||
if parsed == nil {
|
||||
return nil
|
||||
|
||||
Generated
Vendored
-1
@@ -1,5 +1,4 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package ps
|
||||
|
||||
|
||||
Generated
Vendored
+1
-2
@@ -1,5 +1,4 @@
|
||||
//go:build freebsd
|
||||
// +build freebsd
|
||||
|
||||
package ps
|
||||
|
||||
@@ -188,7 +187,7 @@ func processes() ([]Process, error) {
|
||||
count := int(length / uint64(procinfo_len))
|
||||
|
||||
// parse buf to procs
|
||||
for i := 0; i < count; i++ {
|
||||
for i := range count {
|
||||
b := buf[i*procinfo_len : i*procinfo_len+procinfo_len]
|
||||
k, err := parse_kinfo_proc(b)
|
||||
if err != nil {
|
||||
|
||||
Generated
Vendored
-1
@@ -1,5 +1,4 @@
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package ps
|
||||
|
||||
|
||||
Generated
Vendored
+1
-2
@@ -1,5 +1,4 @@
|
||||
//go:build solaris
|
||||
// +build solaris
|
||||
|
||||
package ps
|
||||
|
||||
@@ -88,7 +87,7 @@ func (p *UnixProcess) Refresh() error {
|
||||
}
|
||||
|
||||
func toString(array []byte, len int) string {
|
||||
for i := 0; i < len; i++ {
|
||||
for i := range len {
|
||||
if array[i] == 0 {
|
||||
return string(array[:i])
|
||||
}
|
||||
|
||||
Generated
Vendored
-1
@@ -1,5 +1,4 @@
|
||||
//go:build linux || solaris
|
||||
// +build linux solaris
|
||||
|
||||
package ps
|
||||
|
||||
|
||||
Generated
Vendored
-1
@@ -1,5 +1,4 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package ps
|
||||
|
||||
|
||||
-3
@@ -60,9 +60,6 @@ func (c *Console) complete(input []rune, pos int) readline.Completions {
|
||||
comps = comps.Usage("%s", completions.Usage)
|
||||
comps = c.justifyCommandComps(comps)
|
||||
|
||||
// Display extension and alias arguments as a list (one per line)
|
||||
comps = comps.DisplayList("extension arguments", "alias arguments")
|
||||
|
||||
// If any errors arose from the completion call itself.
|
||||
if err != nil {
|
||||
comps = readline.CompleteMessage("failed to load config: " + err.Error())
|
||||
|
||||
Vendored
+4
-4
@@ -113,8 +113,8 @@ github.com/bahlo/generic-list-go
|
||||
# github.com/buger/jsonparser v1.1.1
|
||||
## explicit; go 1.13
|
||||
github.com/buger/jsonparser
|
||||
# github.com/carapace-sh/carapace v1.9.0
|
||||
## explicit; go 1.16
|
||||
# github.com/carapace-sh/carapace v1.11.0
|
||||
## explicit; go 1.24
|
||||
github.com/carapace-sh/carapace
|
||||
github.com/carapace-sh/carapace/internal/cache
|
||||
github.com/carapace-sh/carapace/internal/common
|
||||
@@ -157,7 +157,7 @@ github.com/carapace-sh/carapace/third_party/github.com/elves/elvish/pkg/cli/lsco
|
||||
github.com/carapace-sh/carapace/third_party/github.com/elves/elvish/pkg/ui
|
||||
github.com/carapace-sh/carapace/third_party/github.com/mitchellh/go-ps
|
||||
github.com/carapace-sh/carapace/third_party/golang.org/x/sys/execabs
|
||||
# github.com/carapace-sh/carapace-shlex v1.1.0
|
||||
# github.com/carapace-sh/carapace-shlex v1.1.1
|
||||
## explicit; go 1.15
|
||||
github.com/carapace-sh/carapace-shlex
|
||||
# github.com/catppuccin/go v0.3.0
|
||||
@@ -589,7 +589,7 @@ github.com/reeflective/console/internal/completion
|
||||
github.com/reeflective/console/internal/line
|
||||
github.com/reeflective/console/internal/strutil
|
||||
github.com/reeflective/console/internal/ui
|
||||
# github.com/reeflective/readline v1.1.3 => github.com/moloch--/readline v0.0.0-20260104185423-d3ac1d6c561d
|
||||
# github.com/reeflective/readline v1.1.4 => github.com/moloch--/readline v0.0.0-20260104185423-d3ac1d6c561d
|
||||
## explicit; go 1.23.6
|
||||
github.com/reeflective/readline
|
||||
github.com/reeflective/readline/inputrc
|
||||
|
||||
Reference in new issue
Block a user