m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-17 15:53:39 -05:00

Add --listen-unsafe=ADDR to allow remote process execution (#3498)

This commit is contained in:
Junegunn Choi
2023-11-05 10:50:11 +09:00
parent 5c3b044740
commit a818653174
5 changed files with 108 additions and 37 deletions

View File

@@ -119,6 +119,7 @@ const usage = `usage: fzf [options]
--print0 Print output delimited by ASCII NUL characters
--sync Synchronous search for multi-staged filtering
--listen[=[ADDR:]PORT] Start HTTP server to receive actions (POST /)
(To allow remote process execution, use --listen-unsafe)
--version Display version information and exit
Environment variables
@@ -334,7 +335,8 @@ type Options struct {
PreviewLabel labelOpts
Unicode bool
Tabstop int
ListenAddr *string
ListenAddr *listenAddress
Unsafe bool
ClearOnExit bool
Version bool
}
@@ -404,6 +406,7 @@ func defaultOptions() *Options {
Tabstop: 8,
BorderLabel: labelOpts{},
PreviewLabel: labelOpts{},
Unsafe: false,
ClearOnExit: true,
Version: false}
}
@@ -1832,14 +1835,21 @@ func parseOptions(opts *Options, allArgs []string) {
nextString(allArgs, &i, "padding required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
case "--tabstop":
opts.Tabstop = nextInt(allArgs, &i, "tab stop required")
case "--listen":
given, addr := optionalNextString(allArgs, &i)
if !given {
addr = defaultListenAddr
case "--listen", "--listen-unsafe":
given, str := optionalNextString(allArgs, &i)
addr := defaultListenAddr
if given {
var err error
err, addr = parseListenAddress(str)
if err != nil {
errorExit(err.Error())
}
}
opts.ListenAddr = &addr
case "--no-listen":
opts.Unsafe = arg == "--listen-unsafe"
case "--no-listen", "--no-listen-unsafe":
opts.ListenAddr = nil
opts.Unsafe = false
case "--clear":
opts.ClearOnExit = true
case "--no-clear":
@@ -1930,7 +1940,19 @@ func parseOptions(opts *Options, allArgs []string) {
} else if match, value := optString(arg, "--tabstop="); match {
opts.Tabstop = atoi(value)
} else if match, value := optString(arg, "--listen="); match {
opts.ListenAddr = &value
err, addr := parseListenAddress(value)
if err != nil {
errorExit(err.Error())
}
opts.ListenAddr = &addr
opts.Unsafe = false
} else if match, value := optString(arg, "--listen-unsafe="); match {
err, addr := parseListenAddress(value)
if err != nil {
errorExit(err.Error())
}
opts.ListenAddr = &addr
opts.Unsafe = true
} else if match, value := optString(arg, "--hscroll-off="); match {
opts.HscrollOff = atoi(value)
} else if match, value := optString(arg, "--scroll-off="); match {

View File

@@ -26,13 +26,12 @@ type getParams struct {
}
const (
crlf = "\r\n"
httpOk = "HTTP/1.1 200 OK" + crlf
httpBadRequest = "HTTP/1.1 400 Bad Request" + crlf
httpUnauthorized = "HTTP/1.1 401 Unauthorized" + crlf
httpReadTimeout = 10 * time.Second
maxContentLength = 1024 * 1024
defaultListenAddr = "localhost:0"
crlf = "\r\n"
httpOk = "HTTP/1.1 200 OK" + crlf
httpBadRequest = "HTTP/1.1 400 Bad Request" + crlf
httpUnauthorized = "HTTP/1.1 401 Unauthorized" + crlf
httpReadTimeout = 10 * time.Second
maxContentLength = 1024 * 1024
)
type httpServer struct {
@@ -41,38 +40,47 @@ type httpServer struct {
responseChannel chan string
}
func parseListenAddress(address string) (error, string, int) {
type listenAddress struct {
host string
port int
}
func (addr listenAddress) IsLocal() bool {
return addr.host == "localhost" || addr.host == "127.0.0.1"
}
var defaultListenAddr = listenAddress{"localhost", 0}
func parseListenAddress(address string) (error, listenAddress) {
parts := strings.SplitN(address, ":", 3)
if len(parts) == 1 {
parts = []string{"localhost", parts[0]}
}
if len(parts) != 2 {
return fmt.Errorf("invalid listen address: %s", address), "", 0
return fmt.Errorf("invalid listen address: %s", address), defaultListenAddr
}
portStr := parts[len(parts)-1]
port, err := strconv.Atoi(portStr)
if err != nil || port < 0 || port > 65535 {
return fmt.Errorf("invalid listen port: %s", portStr), "", 0
return fmt.Errorf("invalid listen port: %s", portStr), defaultListenAddr
}
if len(parts[0]) == 0 {
parts[0] = "localhost"
}
return nil, parts[0], port
return nil, listenAddress{parts[0], port}
}
func startHttpServer(address string, actionChannel chan []*action, responseChannel chan string) (error, int) {
err, host, port := parseListenAddress(address)
if err != nil {
return err, port
}
func startHttpServer(address listenAddress, actionChannel chan []*action, responseChannel chan string) (error, int) {
host := address.host
port := address.port
apiKey := os.Getenv("FZF_API_KEY")
if host != "localhost" && host != "127.0.0.1" && len(apiKey) == 0 {
return fmt.Errorf("FZF_API_KEY is required for remote access"), port
if !address.IsLocal() && len(apiKey) == 0 {
return fmt.Errorf("FZF_API_KEY is required to allow remote access"), port
}
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", host, port))
addrStr := fmt.Sprintf("%s:%d", host, port)
listener, err := net.Listen("tcp", addrStr)
if err != nil {
return fmt.Errorf("failed to listen on %s", address), port
return fmt.Errorf("failed to listen on %s", addrStr), port
}
if port == 0 {
addr := listener.Addr().String()

View File

@@ -235,8 +235,9 @@ type Terminal struct {
margin [4]sizeSpec
padding [4]sizeSpec
unicode bool
listenAddr *string
listenAddr *listenAddress
listenPort *int
listenUnsafe bool
borderShape tui.BorderShape
cleanExit bool
paused bool
@@ -436,6 +437,26 @@ const (
actResponse
)
func processExecution(action actionType) bool {
switch action {
case actTransformBorderLabel,
actTransformHeader,
actTransformPreviewLabel,
actTransformPrompt,
actTransformQuery,
actPreview,
actChangePreview,
actExecute,
actExecuteSilent,
actExecuteMulti,
actReload,
actReloadSync,
actBecome:
return true
}
return false
}
type placeholderFlags struct {
plus bool
preserveSpace bool
@@ -661,6 +682,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
padding: opts.Padding,
unicode: opts.Unicode,
listenAddr: opts.ListenAddr,
listenUnsafe: opts.Unsafe,
borderShape: opts.BorderShape,
borderWidth: 1,
borderLabel: nil,
@@ -3088,8 +3110,18 @@ func (t *Terminal) Loop() {
select {
case event = <-t.eventChan:
needBarrier = !event.Is(tui.Load, tui.One, tui.Zero)
case actions = <-t.serverInputChan:
case serverActions := <-t.serverInputChan:
event = tui.Invalid.AsEvent()
if t.listenAddr == nil || t.listenAddr.IsLocal() || t.listenUnsafe {
actions = serverActions
} else {
for _, action := range serverActions {
if !processExecution(action.t) {
actions = append(actions, action)
}
}
}
needBarrier = false
}
}