mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2024-12-23 03:19:01 +00:00
Add --listen-unsafe=ADDR to allow remote process execution (#3498)
This commit is contained in:
parent
5c3b044740
commit
a818653174
@ -16,6 +16,10 @@ CHANGELOG
|
|||||||
# FZF_API_KEY is required for a non-localhost listen address
|
# FZF_API_KEY is required for a non-localhost listen address
|
||||||
export FZF_API_KEY="$(head -c 32 /dev/urandom | base64)"
|
export FZF_API_KEY="$(head -c 32 /dev/urandom | base64)"
|
||||||
fzf --listen 0.0.0.0:6266
|
fzf --listen 0.0.0.0:6266
|
||||||
|
|
||||||
|
# To allow remote process execution, use `--listen-unsafe` instead
|
||||||
|
# (execute, reload, become, preview, change-preview, tranform-*, etc.)
|
||||||
|
fzf --listen-unsafe 0.0.0.0:6266
|
||||||
```
|
```
|
||||||
- Bug fixes
|
- Bug fixes
|
||||||
|
|
||||||
|
@ -793,14 +793,19 @@ ncurses finder only after the input stream is complete.
|
|||||||
e.g. \fBfzf --multi | fzf --sync\fR
|
e.g. \fBfzf --multi | fzf --sync\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.B "--listen[=[ADDR:]PORT]"
|
.B "--listen[=[ADDR:]PORT]" "--listen-unsafe[=[ADDR:]PORT]"
|
||||||
Start HTTP server and listen on the given address. It allows external processes
|
Start HTTP server and listen on the given address. It allows external processes
|
||||||
to send actions to perform via POST method. If the port number is omitted or
|
to send actions to perform via POST method.
|
||||||
given as 0, fzf will automatically choose a port and export it as
|
|
||||||
\fBFZF_PORT\fR environment variable to the child processes. If
|
- If the port number is omitted or given as 0, fzf will automatically choose
|
||||||
\fBFZF_API_KEY\fR environment variable is set, the server would require sending
|
a port and export it as \fBFZF_PORT\fR environment variable to the child processes
|
||||||
an API key with the same value in the \fBx-api-key\fR HTTP header.
|
|
||||||
\fBFZF_API_KEY\fR is required for a non-localhost listen address.
|
- If \fBFZF_API_KEY\fR environment variable is set, the server would require
|
||||||
|
sending an API key with the same value in the \fBx-api-key\fR HTTP header
|
||||||
|
|
||||||
|
- \fBFZF_API_KEY\fR is required for a non-localhost listen address
|
||||||
|
|
||||||
|
- To allow remote process execution, use \fB--listen-unsafe\fR
|
||||||
|
|
||||||
e.g.
|
e.g.
|
||||||
\fB# Start HTTP server on port 6266
|
\fB# Start HTTP server on port 6266
|
||||||
|
@ -119,6 +119,7 @@ const usage = `usage: fzf [options]
|
|||||||
--print0 Print output delimited by ASCII NUL characters
|
--print0 Print output delimited by ASCII NUL characters
|
||||||
--sync Synchronous search for multi-staged filtering
|
--sync Synchronous search for multi-staged filtering
|
||||||
--listen[=[ADDR:]PORT] Start HTTP server to receive actions (POST /)
|
--listen[=[ADDR:]PORT] Start HTTP server to receive actions (POST /)
|
||||||
|
(To allow remote process execution, use --listen-unsafe)
|
||||||
--version Display version information and exit
|
--version Display version information and exit
|
||||||
|
|
||||||
Environment variables
|
Environment variables
|
||||||
@ -334,7 +335,8 @@ type Options struct {
|
|||||||
PreviewLabel labelOpts
|
PreviewLabel labelOpts
|
||||||
Unicode bool
|
Unicode bool
|
||||||
Tabstop int
|
Tabstop int
|
||||||
ListenAddr *string
|
ListenAddr *listenAddress
|
||||||
|
Unsafe bool
|
||||||
ClearOnExit bool
|
ClearOnExit bool
|
||||||
Version bool
|
Version bool
|
||||||
}
|
}
|
||||||
@ -404,6 +406,7 @@ func defaultOptions() *Options {
|
|||||||
Tabstop: 8,
|
Tabstop: 8,
|
||||||
BorderLabel: labelOpts{},
|
BorderLabel: labelOpts{},
|
||||||
PreviewLabel: labelOpts{},
|
PreviewLabel: labelOpts{},
|
||||||
|
Unsafe: false,
|
||||||
ClearOnExit: true,
|
ClearOnExit: true,
|
||||||
Version: false}
|
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)"))
|
nextString(allArgs, &i, "padding required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
|
||||||
case "--tabstop":
|
case "--tabstop":
|
||||||
opts.Tabstop = nextInt(allArgs, &i, "tab stop required")
|
opts.Tabstop = nextInt(allArgs, &i, "tab stop required")
|
||||||
case "--listen":
|
case "--listen", "--listen-unsafe":
|
||||||
given, addr := optionalNextString(allArgs, &i)
|
given, str := optionalNextString(allArgs, &i)
|
||||||
if !given {
|
addr := defaultListenAddr
|
||||||
addr = defaultListenAddr
|
if given {
|
||||||
|
var err error
|
||||||
|
err, addr = parseListenAddress(str)
|
||||||
|
if err != nil {
|
||||||
|
errorExit(err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
opts.ListenAddr = &addr
|
opts.ListenAddr = &addr
|
||||||
case "--no-listen":
|
opts.Unsafe = arg == "--listen-unsafe"
|
||||||
|
case "--no-listen", "--no-listen-unsafe":
|
||||||
opts.ListenAddr = nil
|
opts.ListenAddr = nil
|
||||||
|
opts.Unsafe = false
|
||||||
case "--clear":
|
case "--clear":
|
||||||
opts.ClearOnExit = true
|
opts.ClearOnExit = true
|
||||||
case "--no-clear":
|
case "--no-clear":
|
||||||
@ -1930,7 +1940,19 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
} else if match, value := optString(arg, "--tabstop="); match {
|
} else if match, value := optString(arg, "--tabstop="); match {
|
||||||
opts.Tabstop = atoi(value)
|
opts.Tabstop = atoi(value)
|
||||||
} else if match, value := optString(arg, "--listen="); match {
|
} 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 {
|
} else if match, value := optString(arg, "--hscroll-off="); match {
|
||||||
opts.HscrollOff = atoi(value)
|
opts.HscrollOff = atoi(value)
|
||||||
} else if match, value := optString(arg, "--scroll-off="); match {
|
} else if match, value := optString(arg, "--scroll-off="); match {
|
||||||
|
@ -26,13 +26,12 @@ type getParams struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
crlf = "\r\n"
|
crlf = "\r\n"
|
||||||
httpOk = "HTTP/1.1 200 OK" + crlf
|
httpOk = "HTTP/1.1 200 OK" + crlf
|
||||||
httpBadRequest = "HTTP/1.1 400 Bad Request" + crlf
|
httpBadRequest = "HTTP/1.1 400 Bad Request" + crlf
|
||||||
httpUnauthorized = "HTTP/1.1 401 Unauthorized" + crlf
|
httpUnauthorized = "HTTP/1.1 401 Unauthorized" + crlf
|
||||||
httpReadTimeout = 10 * time.Second
|
httpReadTimeout = 10 * time.Second
|
||||||
maxContentLength = 1024 * 1024
|
maxContentLength = 1024 * 1024
|
||||||
defaultListenAddr = "localhost:0"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type httpServer struct {
|
type httpServer struct {
|
||||||
@ -41,38 +40,47 @@ type httpServer struct {
|
|||||||
responseChannel chan string
|
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)
|
parts := strings.SplitN(address, ":", 3)
|
||||||
if len(parts) == 1 {
|
if len(parts) == 1 {
|
||||||
parts = []string{"localhost", parts[0]}
|
parts = []string{"localhost", parts[0]}
|
||||||
}
|
}
|
||||||
if len(parts) != 2 {
|
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]
|
portStr := parts[len(parts)-1]
|
||||||
port, err := strconv.Atoi(portStr)
|
port, err := strconv.Atoi(portStr)
|
||||||
if err != nil || port < 0 || port > 65535 {
|
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 {
|
if len(parts[0]) == 0 {
|
||||||
parts[0] = "localhost"
|
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) {
|
func startHttpServer(address listenAddress, actionChannel chan []*action, responseChannel chan string) (error, int) {
|
||||||
err, host, port := parseListenAddress(address)
|
host := address.host
|
||||||
if err != nil {
|
port := address.port
|
||||||
return err, port
|
|
||||||
}
|
|
||||||
|
|
||||||
apiKey := os.Getenv("FZF_API_KEY")
|
apiKey := os.Getenv("FZF_API_KEY")
|
||||||
if host != "localhost" && host != "127.0.0.1" && len(apiKey) == 0 {
|
if !address.IsLocal() && len(apiKey) == 0 {
|
||||||
return fmt.Errorf("FZF_API_KEY is required for remote access"), port
|
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 {
|
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 {
|
if port == 0 {
|
||||||
addr := listener.Addr().String()
|
addr := listener.Addr().String()
|
||||||
|
@ -235,8 +235,9 @@ type Terminal struct {
|
|||||||
margin [4]sizeSpec
|
margin [4]sizeSpec
|
||||||
padding [4]sizeSpec
|
padding [4]sizeSpec
|
||||||
unicode bool
|
unicode bool
|
||||||
listenAddr *string
|
listenAddr *listenAddress
|
||||||
listenPort *int
|
listenPort *int
|
||||||
|
listenUnsafe bool
|
||||||
borderShape tui.BorderShape
|
borderShape tui.BorderShape
|
||||||
cleanExit bool
|
cleanExit bool
|
||||||
paused bool
|
paused bool
|
||||||
@ -436,6 +437,26 @@ const (
|
|||||||
actResponse
|
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 {
|
type placeholderFlags struct {
|
||||||
plus bool
|
plus bool
|
||||||
preserveSpace bool
|
preserveSpace bool
|
||||||
@ -661,6 +682,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
padding: opts.Padding,
|
padding: opts.Padding,
|
||||||
unicode: opts.Unicode,
|
unicode: opts.Unicode,
|
||||||
listenAddr: opts.ListenAddr,
|
listenAddr: opts.ListenAddr,
|
||||||
|
listenUnsafe: opts.Unsafe,
|
||||||
borderShape: opts.BorderShape,
|
borderShape: opts.BorderShape,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderLabel: nil,
|
borderLabel: nil,
|
||||||
@ -3088,8 +3110,18 @@ func (t *Terminal) Loop() {
|
|||||||
select {
|
select {
|
||||||
case event = <-t.eventChan:
|
case event = <-t.eventChan:
|
||||||
needBarrier = !event.Is(tui.Load, tui.One, tui.Zero)
|
needBarrier = !event.Is(tui.Load, tui.One, tui.Zero)
|
||||||
case actions = <-t.serverInputChan:
|
case serverActions := <-t.serverInputChan:
|
||||||
event = tui.Invalid.AsEvent()
|
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
|
needBarrier = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user