mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2025-02-06 22:18:42 +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 {}'
|
||||
```
|
||||
- (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
|
||||
|
||||
0.43.0
|
||||
|
@ -793,14 +793,14 @@ ncurses finder only after the input stream is complete.
|
||||
e.g. \fBfzf --multi | fzf --sync\fR
|
||||
.RE
|
||||
.TP
|
||||
.B "--listen[=HTTP_PORT]"
|
||||
Start HTTP server on the given port. It allows external processes to send
|
||||
actions to perform via POST method. If the port number is omitted or given as
|
||||
0, fzf will choose the port automatically and export it as \fBFZF_PORT\fR
|
||||
environment variable to the child processes started via \fBexecute\fR and
|
||||
\fBexecute-silent\fR actions. 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.
|
||||
.B "--listen[=[ADDR:]PORT]"
|
||||
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
|
||||
given as 0, fzf will automatically choose a port and export it as
|
||||
\fBFZF_PORT\fR environment variable to the child processes. 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.
|
||||
|
||||
e.g.
|
||||
\fB# Start HTTP server on port 6266
|
||||
@ -812,8 +812,12 @@ e.g.
|
||||
# Send action to the server
|
||||
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)"
|
||||
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)'
|
||||
|
||||
# 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
|
||||
--print0 Print output delimited by ASCII NUL characters
|
||||
--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
|
||||
|
||||
Environment variables
|
||||
@ -334,7 +334,7 @@ type Options struct {
|
||||
PreviewLabel labelOpts
|
||||
Unicode bool
|
||||
Tabstop int
|
||||
ListenPort *int
|
||||
ListenAddr *string
|
||||
ClearOnExit bool
|
||||
Version bool
|
||||
}
|
||||
@ -1833,10 +1833,13 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
case "--tabstop":
|
||||
opts.Tabstop = nextInt(allArgs, &i, "tab stop required")
|
||||
case "--listen":
|
||||
port := optionalNumeric(allArgs, &i, 0)
|
||||
opts.ListenPort = &port
|
||||
given, addr := optionalNextString(allArgs, &i)
|
||||
if !given {
|
||||
addr = defaultListenAddr
|
||||
}
|
||||
opts.ListenAddr = &addr
|
||||
case "--no-listen":
|
||||
opts.ListenPort = nil
|
||||
opts.ListenAddr = nil
|
||||
case "--clear":
|
||||
opts.ClearOnExit = true
|
||||
case "--no-clear":
|
||||
@ -1927,8 +1930,7 @@ 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 {
|
||||
port := atoi(value)
|
||||
opts.ListenPort = &port
|
||||
opts.ListenAddr = &value
|
||||
} else if match, value := optString(arg, "--hscroll-off="); match {
|
||||
opts.HscrollOff = atoi(value)
|
||||
} 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")
|
||||
}
|
||||
|
||||
if opts.ListenPort != nil && (*opts.ListenPort < 0 || *opts.ListenPort > 65535) {
|
||||
errorExit("invalid listen port")
|
||||
}
|
||||
|
||||
if len(opts.JumpLabels) == 0 {
|
||||
errorExit("empty jump labels")
|
||||
}
|
||||
|
@ -26,12 +26,13 @@ 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
|
||||
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"
|
||||
)
|
||||
|
||||
type httpServer struct {
|
||||
@ -40,30 +41,52 @@ type httpServer struct {
|
||||
responseChannel chan string
|
||||
}
|
||||
|
||||
func startHttpServer(port int, actionChannel chan []*action, responseChannel chan string) (error, int) {
|
||||
if port < 0 {
|
||||
return nil, port
|
||||
func parseListenAddress(address string) (error, string, int) {
|
||||
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
|
||||
}
|
||||
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 {
|
||||
return fmt.Errorf("port not available: %d", port), port
|
||||
return fmt.Errorf("failed to listen on %s", address), port
|
||||
}
|
||||
if port == 0 {
|
||||
addr := listener.Addr().String()
|
||||
parts := strings.SplitN(addr, ":", 2)
|
||||
parts := strings.Split(addr, ":")
|
||||
if len(parts) < 2 {
|
||||
return fmt.Errorf("cannot extract port: %s", addr), port
|
||||
}
|
||||
var err error
|
||||
port, err = strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
if port, err := strconv.Atoi(parts[len(parts)-1]); err != nil {
|
||||
return err, port
|
||||
}
|
||||
}
|
||||
|
||||
server := httpServer{
|
||||
apiKey: []byte(os.Getenv("FZF_API_KEY")),
|
||||
apiKey: []byte(apiKey),
|
||||
actionChannel: actionChannel,
|
||||
responseChannel: responseChannel,
|
||||
}
|
||||
|
@ -235,6 +235,7 @@ type Terminal struct {
|
||||
margin [4]sizeSpec
|
||||
padding [4]sizeSpec
|
||||
unicode bool
|
||||
listenAddr *string
|
||||
listenPort *int
|
||||
borderShape tui.BorderShape
|
||||
cleanExit bool
|
||||
@ -586,7 +587,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
}
|
||||
var previewBox *util.EventBox
|
||||
// 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()
|
||||
}
|
||||
var renderer tui.Renderer
|
||||
@ -659,7 +660,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
margin: opts.Margin,
|
||||
padding: opts.Padding,
|
||||
unicode: opts.Unicode,
|
||||
listenPort: opts.ListenPort,
|
||||
listenAddr: opts.ListenAddr,
|
||||
borderShape: opts.BorderShape,
|
||||
borderWidth: 1,
|
||||
borderLabel: nil,
|
||||
@ -748,8 +749,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
|
||||
_, t.hasLoadActions = t.keymap[tui.Load.AsEvent()]
|
||||
|
||||
if t.listenPort != nil {
|
||||
err, port := startHttpServer(*t.listenPort, t.serverInputChan, t.serverOutputChan)
|
||||
if t.listenAddr != nil {
|
||||
err, port := startHttpServer(*t.listenAddr, t.serverInputChan, t.serverOutputChan)
|
||||
if err != nil {
|
||||
errorExit(err.Error())
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user