Skip to content

Commit d57e6bb

Browse files
committed
refactor: support percent-encoded /unix paths
This is a PoC that aims to support multiformats/multiaddr#174 while not breaking existing Kubo users. See TODO in daemon.go – likely we want to move this to https://github.com/multiformats/go-multiaddr
1 parent df2d1c7 commit d57e6bb

File tree

4 files changed

+94
-30
lines changed

4 files changed

+94
-30
lines changed

client/rpc/api.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/ipfs/go-cid"
1919
legacy "github.com/ipfs/go-ipld-legacy"
2020
ipfs "github.com/ipfs/kubo"
21+
daemon "github.com/ipfs/kubo/cmd/ipfs/kubo"
2122
iface "github.com/ipfs/kubo/core/coreiface"
2223
caopts "github.com/ipfs/kubo/core/coreiface/options"
2324
"github.com/ipfs/kubo/misc/fsutil"
@@ -109,6 +110,7 @@ func NewApi(a ma.Multiaddr) (*HttpApi, error) {
109110
return nil, err
110111
}
111112
if network == "unix" {
113+
address = daemon.NormalizeUnixMultiaddr(address)
112114
transport.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) {
113115
return net.Dial("unix", address)
114116
}

cmd/ipfs/kubo/daemon.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import (
99
"net"
1010
"net/http"
1111
_ "net/http/pprof"
12+
"net/url"
1213
"os"
14+
"path/filepath"
1315
"regexp"
1416
"runtime"
1517
"sort"
@@ -704,6 +706,24 @@ take effect.
704706
return errs
705707
}
706708

709+
// TODO: should a version of this live in https://github.com/multiformats/go-multiaddr
710+
// so we dont need to duplicate code here and in client/rpc/api.go ?
711+
func NormalizeUnixMultiaddr(address string) string {
712+
// Support legacy and modern /unix addrs
713+
// https://github.com/multiformats/multiaddr/pull/174
714+
socketPath, err := url.PathUnescape(address)
715+
if err != nil {
716+
return address // nil, fmt.Errorf("failed to unescape /unix socket path: %w", err)
717+
}
718+
// Ensure the path is absolute
719+
if !strings.HasPrefix(socketPath, string(filepath.Separator)) {
720+
socketPath = string(filepath.Separator) + socketPath
721+
}
722+
// Normalize path
723+
socketPath = filepath.Clean(socketPath)
724+
return socketPath
725+
}
726+
707727
// serveHTTPApi collects options, creates listener, prints status message and starts serving requests.
708728
func serveHTTPApi(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, error) {
709729
cfg, err := cctx.GetConfig()
@@ -730,6 +750,9 @@ func serveHTTPApi(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, error
730750
}
731751

732752
for _, addr := range apiAddrs {
753+
if strings.HasPrefix(addr, "/unix/") {
754+
addr = NormalizeUnixMultiaddr(addr)
755+
}
733756
apiMaddr, err := ma.NewMultiaddr(addr)
734757
if err != nil {
735758
return nil, fmt.Errorf("serveHTTPApi: invalid API address: %q (err: %s)", addr, err)
@@ -919,6 +942,9 @@ func serveHTTPGateway(req *cmds.Request, cctx *oldcmds.Context) (<-chan error, e
919942

920943
gatewayAddrs := cfg.Addresses.Gateway
921944
for _, addr := range gatewayAddrs {
945+
if strings.HasPrefix(addr, "/unix/") {
946+
addr = NormalizeUnixMultiaddr(addr)
947+
}
922948
gatewayMaddr, err := ma.NewMultiaddr(addr)
923949
if err != nil {
924950
return nil, fmt.Errorf("serveHTTPGateway: invalid gateway address: %q (err: %s)", addr, err)

docs/config.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ the local [Kubo RPC API](https://docs.ipfs.tech/reference/kubo/rpc/) (`/api/v0`)
249249
Supported Transports:
250250

251251
* tcp/ip{4,6} - `/ipN/.../tcp/...`
252-
* unix - `/unix/path/to/socket`
252+
* unix - `/unix/path/to/socket` or `/unix/path%2Fto%2Fsocket`
253253

254254
> [!CAUTION]
255255
> **NEVER EXPOSE UNPROTECTED ADMIN RPC TO LAN OR THE PUBLIC INTERNET**
@@ -276,7 +276,7 @@ the local [HTTP gateway](https://specs.ipfs.tech/http-gateways/) (`/ipfs`, `/ipn
276276
Supported Transports:
277277

278278
* tcp/ip{4,6} - `/ipN/.../tcp/...`
279-
* unix - `/unix/path/to/socket`
279+
* unix - `/unix/path/to/socket` or `/unix/path%2Fto%2Fsocket`
280280

281281
Default: `/ip4/127.0.0.1/tcp/8080`
282282

test/cli/rpc_unixsocket_test.go

Lines changed: 64 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ package cli
22

33
import (
44
"context"
5+
"net/url"
56
"path"
7+
"path/filepath"
8+
"strings"
69
"testing"
710

811
rpcapi "github.com/ipfs/kubo/client/rpc"
@@ -13,39 +16,72 @@ import (
1316
)
1417

1518
func TestRPCUnixSocket(t *testing.T) {
16-
node := harness.NewT(t).NewNode().Init()
19+
t.Parallel()
1720

18-
sockDir := node.Dir
19-
sockAddr := path.Join("/unix", sockDir, "sock")
21+
testCases := []struct {
22+
name string
23+
getSockMultiaddr func(sockPath string) (unixMultiaddr string)
24+
}{
25+
{
26+
name: "Legacy /unix: unescaped socket path",
27+
getSockMultiaddr: func(sockDir string) string {
28+
return path.Join("/unix", sockDir, "sock")
29+
},
30+
},
31+
{
32+
name: "Spec-compliant /unix: percent-encoded socket path without leading slash",
33+
getSockMultiaddr: func(sockDir string) string {
34+
sockPath := path.Join(sockDir, "sock")
35+
pathWithoutLeadingSlash := strings.TrimPrefix(sockPath, string(filepath.Separator))
36+
escapedPath := url.PathEscape(pathWithoutLeadingSlash)
37+
return path.Join("/unix", escapedPath)
38+
},
39+
},
40+
{
41+
name: "Spec-compliant /unix: percent-encoded socket path with leading slash",
42+
getSockMultiaddr: func(sockDir string) string {
43+
sockPath := path.Join(sockDir, "sock")
44+
escapedPath := url.PathEscape(sockPath)
45+
return path.Join("/unix", escapedPath)
46+
},
47+
},
48+
}
2049

21-
node.UpdateConfig(func(cfg *config.Config) {
22-
//cfg.Addresses.API = append(cfg.Addresses.API, sockPath)
23-
cfg.Addresses.API = []string{sockAddr}
24-
})
25-
t.Log("Starting daemon with unix socket:", sockAddr)
26-
node.StartDaemon()
50+
for _, tc := range testCases {
51+
t.Run(tc.name, func(t *testing.T) {
52+
node := harness.NewT(t).NewNode().Init()
53+
sockDir := node.Dir
54+
sockAddr := tc.getSockMultiaddr(sockDir)
55+
node.UpdateConfig(func(cfg *config.Config) {
56+
//cfg.Addresses.API = append(cfg.Addresses.API, sockPath)
57+
cfg.Addresses.API = []string{sockAddr}
58+
})
59+
t.Log("Starting daemon with unix socket:", sockAddr)
60+
node.StartDaemon()
2761

28-
unixMaddr, err := multiaddr.NewMultiaddr(sockAddr)
29-
require.NoError(t, err)
62+
unixMaddr, err := multiaddr.NewMultiaddr(sockAddr)
63+
require.NoError(t, err)
3064

31-
apiClient, err := rpcapi.NewApi(unixMaddr)
32-
require.NoError(t, err)
65+
apiClient, err := rpcapi.NewApi(unixMaddr)
66+
require.NoError(t, err)
3367

34-
var ver struct {
35-
Version string
36-
}
37-
err = apiClient.Request("version").Exec(context.Background(), &ver)
38-
require.NoError(t, err)
39-
require.NotEmpty(t, ver)
40-
t.Log("Got version:", ver.Version)
68+
var ver struct {
69+
Version string
70+
}
71+
err = apiClient.Request("version").Exec(context.Background(), &ver)
72+
require.NoError(t, err)
73+
require.NotEmpty(t, ver)
74+
t.Log("Got version:", ver.Version)
4175

42-
var res struct {
43-
ID string
44-
}
45-
err = apiClient.Request("id").Exec(context.Background(), &res)
46-
require.NoError(t, err)
47-
require.NotEmpty(t, res)
48-
t.Log("Got ID:", res.ID)
76+
var res struct {
77+
ID string
78+
}
79+
err = apiClient.Request("id").Exec(context.Background(), &res)
80+
require.NoError(t, err)
81+
require.NotEmpty(t, res)
82+
t.Log("Got ID:", res.ID)
4983

50-
node.StopDaemon()
84+
node.StopDaemon()
85+
})
86+
}
5187
}

0 commit comments

Comments
 (0)