mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2024-05-31 14:40:48 +00:00
Allow accepting remote connections
Close #3498 # FZF_API_KEY is required for a non-localhost listen address FZF_API_KEY=xxx fzf --listen 0.0.0.0:6266
This commit is contained in:
parent
70c19ccf16
commit
3f78d76da1
|
@ -11,6 +11,12 @@ CHANGELOG
|
||||||
fzf --preview='fzf-preview.sh {}'
|
fzf --preview='fzf-preview.sh {}'
|
||||||
```
|
```
|
||||||
- (Experimental) Sixel and Kitty image support now also available on Windows
|
- (Experimental) Sixel and Kitty image support now also available on Windows
|
||||||
|
- HTTP server can be configured to accept remote connections
|
||||||
|
```sh
|
||||||
|
# FZF_API_KEY is required for a non-localhost listen address
|
||||||
|
export FZF_API_KEY="$(head -c 32 /dev/urandom | base64)"
|
||||||
|
fzf --listen 0.0.0.0:6266
|
||||||
|
```
|
||||||
- Bug fixes
|
- Bug fixes
|
||||||
|
|
||||||
0.43.0
|
0.43.0
|
||||||
|
|
|
@ -793,14 +793,14 @@ 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[=HTTP_PORT]"
|
.B "--listen[=[ADDR:]PORT]"
|
||||||
Start HTTP server on the given port. It allows external processes to send
|
Start HTTP server and listen on the given address. It allows external processes
|
||||||
actions to perform via POST method. If the port number is omitted or given as
|
to send actions to perform via POST method. If the port number is omitted or
|
||||||
0, fzf will choose the port automatically and export it as \fBFZF_PORT\fR
|
given as 0, fzf will automatically choose a port and export it as
|
||||||
environment variable to the child processes started via \fBexecute\fR and
|
\fBFZF_PORT\fR environment variable to the child processes. If
|
||||||
\fBexecute-silent\fR actions. If \fBFZF_API_KEY\fR environment variable is
|
\fBFZF_API_KEY\fR environment variable is set, the server would require sending
|
||||||
set, the server would require sending an API key with the same value in the
|
an API key with the same value in the \fBx-api-key\fR HTTP header.
|
||||||
\fBx-api-key\fR HTTP header.
|
\fBFZF_API_KEY\fR is required for a non-localhost listen address.
|
||||||
|
|
||||||
e.g.
|
e.g.
|
||||||
\fB# Start HTTP server on port 6266
|
\fB# Start HTTP server on port 6266
|
||||||
|
@ -812,8 +812,12 @@ e.g.
|
||||||
# Send action to the server
|
# Send action to the server
|
||||||
curl -XPOST localhost:6266 -d 'reload(seq 100)+change-prompt(hundred> )'
|
curl -XPOST localhost:6266 -d 'reload(seq 100)+change-prompt(hundred> )'
|
||||||
|
|
||||||
# Start HTTP server on port 6266 and send an authenticated action
|
# Start HTTP server on port 6266 with remote connections allowed
|
||||||
|
# * Listening on non-localhost address requires using an API key
|
||||||
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
|
||||||
|
|
||||||
|
# Send an authenticated action
|
||||||
curl -XPOST localhost:6266 -H "x-api-key: $FZF_API_KEY" -d 'change-query(yo)'
|
curl -XPOST localhost:6266 -H "x-api-key: $FZF_API_KEY" -d 'change-query(yo)'
|
||||||
|
|
||||||
# Choose port automatically and export it as $FZF_PORT to the child process
|
# Choose port automatically and export it as $FZF_PORT to the child process
|
||||||
|
|
|
@ -118,7 +118,7 @@ const usage = `usage: fzf [options]
|
||||||
--read0 Read input delimited by ASCII NUL characters
|
--read0 Read input delimited by ASCII NUL characters
|
||||||
--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[=HTTP_PORT] Start HTTP server to receive actions (POST /)
|
--listen[=[ADDR:]PORT] Start HTTP server to receive actions (POST /)
|
||||||
--version Display version information and exit
|
--version Display version information and exit
|
||||||
|
|
||||||
Environment variables
|
Environment variables
|
||||||
|
@ -334,7 +334,7 @@ type Options struct {
|
||||||
PreviewLabel labelOpts
|
PreviewLabel labelOpts
|
||||||
Unicode bool
|
Unicode bool
|
||||||
Tabstop int
|
Tabstop int
|
||||||
ListenPort *int
|
ListenAddr *string
|
||||||
ClearOnExit bool
|
ClearOnExit bool
|
||||||
Version bool
|
Version bool
|
||||||
}
|
}
|
||||||
|
@ -1833,10 +1833,13 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||||
case "--tabstop":
|
case "--tabstop":
|
||||||
opts.Tabstop = nextInt(allArgs, &i, "tab stop required")
|
opts.Tabstop = nextInt(allArgs, &i, "tab stop required")
|
||||||
case "--listen":
|
case "--listen":
|
||||||
port := optionalNumeric(allArgs, &i, 0)
|
given, addr := optionalNextString(allArgs, &i)
|
||||||
opts.ListenPort = &port
|
if !given {
|
||||||
|
addr = defaultListenAddr
|
||||||
|
}
|
||||||
|
opts.ListenAddr = &addr
|
||||||
case "--no-listen":
|
case "--no-listen":
|
||||||
opts.ListenPort = nil
|
opts.ListenAddr = nil
|
||||||
case "--clear":
|
case "--clear":
|
||||||
opts.ClearOnExit = true
|
opts.ClearOnExit = true
|
||||||
case "--no-clear":
|
case "--no-clear":
|
||||||
|
@ -1927,8 +1930,7 @@ 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 {
|
||||||
port := atoi(value)
|
opts.ListenAddr = &value
|
||||||
opts.ListenPort = &port
|
|
||||||
} 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 {
|
||||||
|
@ -1958,10 +1960,6 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||||
errorExit("tab stop must be a positive integer")
|
errorExit("tab stop must be a positive integer")
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.ListenPort != nil && (*opts.ListenPort < 0 || *opts.ListenPort > 65535) {
|
|
||||||
errorExit("invalid listen port")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(opts.JumpLabels) == 0 {
|
if len(opts.JumpLabels) == 0 {
|
||||||
errorExit("empty jump labels")
|
errorExit("empty jump labels")
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,12 +26,13 @@ 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 {
|
||||||
|
@ -40,30 +41,52 @@ type httpServer struct {
|
||||||
responseChannel chan string
|
responseChannel chan string
|
||||||
}
|
}
|
||||||
|
|
||||||
func startHttpServer(port int, actionChannel chan []*action, responseChannel chan string) (error, int) {
|
func parseListenAddress(address string) (error, string, int) {
|
||||||
if port < 0 {
|
parts := strings.SplitN(address, ":", 3)
|
||||||
return nil, port
|
if len(parts) == 1 {
|
||||||
|
parts = []string{"localhost", parts[0]}
|
||||||
|
}
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return fmt.Errorf("invalid listen address: %s", address), "", 0
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
if len(parts[0]) == 0 {
|
||||||
|
parts[0] = "localhost"
|
||||||
|
}
|
||||||
|
return nil, 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
|
||||||
}
|
}
|
||||||
|
|
||||||
listener, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", 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
|
||||||
|
}
|
||||||
|
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", host, port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("port not available: %d", port), port
|
return fmt.Errorf("failed to listen on %s", address), port
|
||||||
}
|
}
|
||||||
if port == 0 {
|
if port == 0 {
|
||||||
addr := listener.Addr().String()
|
addr := listener.Addr().String()
|
||||||
parts := strings.SplitN(addr, ":", 2)
|
parts := strings.Split(addr, ":")
|
||||||
if len(parts) < 2 {
|
if len(parts) < 2 {
|
||||||
return fmt.Errorf("cannot extract port: %s", addr), port
|
return fmt.Errorf("cannot extract port: %s", addr), port
|
||||||
}
|
}
|
||||||
var err error
|
if port, err := strconv.Atoi(parts[len(parts)-1]); err != nil {
|
||||||
port, err = strconv.Atoi(parts[1])
|
|
||||||
if err != nil {
|
|
||||||
return err, port
|
return err, port
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
server := httpServer{
|
server := httpServer{
|
||||||
apiKey: []byte(os.Getenv("FZF_API_KEY")),
|
apiKey: []byte(apiKey),
|
||||||
actionChannel: actionChannel,
|
actionChannel: actionChannel,
|
||||||
responseChannel: responseChannel,
|
responseChannel: responseChannel,
|
||||||
}
|
}
|
||||||
|
|
|
@ -235,6 +235,7 @@ type Terminal struct {
|
||||||
margin [4]sizeSpec
|
margin [4]sizeSpec
|
||||||
padding [4]sizeSpec
|
padding [4]sizeSpec
|
||||||
unicode bool
|
unicode bool
|
||||||
|
listenAddr *string
|
||||||
listenPort *int
|
listenPort *int
|
||||||
borderShape tui.BorderShape
|
borderShape tui.BorderShape
|
||||||
cleanExit bool
|
cleanExit bool
|
||||||
|
@ -586,7 +587,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||||
}
|
}
|
||||||
var previewBox *util.EventBox
|
var previewBox *util.EventBox
|
||||||
// We need to start previewer if HTTP server is enabled even when --preview option is not specified
|
// We need to start previewer if HTTP server is enabled even when --preview option is not specified
|
||||||
if len(opts.Preview.command) > 0 || hasPreviewAction(opts) || opts.ListenPort != nil {
|
if len(opts.Preview.command) > 0 || hasPreviewAction(opts) || opts.ListenAddr != nil {
|
||||||
previewBox = util.NewEventBox()
|
previewBox = util.NewEventBox()
|
||||||
}
|
}
|
||||||
var renderer tui.Renderer
|
var renderer tui.Renderer
|
||||||
|
@ -659,7 +660,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||||
margin: opts.Margin,
|
margin: opts.Margin,
|
||||||
padding: opts.Padding,
|
padding: opts.Padding,
|
||||||
unicode: opts.Unicode,
|
unicode: opts.Unicode,
|
||||||
listenPort: opts.ListenPort,
|
listenAddr: opts.ListenAddr,
|
||||||
borderShape: opts.BorderShape,
|
borderShape: opts.BorderShape,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderLabel: nil,
|
borderLabel: nil,
|
||||||
|
@ -748,8 +749,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||||
|
|
||||||
_, t.hasLoadActions = t.keymap[tui.Load.AsEvent()]
|
_, t.hasLoadActions = t.keymap[tui.Load.AsEvent()]
|
||||||
|
|
||||||
if t.listenPort != nil {
|
if t.listenAddr != nil {
|
||||||
err, port := startHttpServer(*t.listenPort, t.serverInputChan, t.serverOutputChan)
|
err, port := startHttpServer(*t.listenAddr, t.serverInputChan, t.serverOutputChan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorExit(err.Error())
|
errorExit(err.Error())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user