Skip to content

Commit d5fe12a

Browse files
authored
Merge pull request #10 from ipfs/kevina/multibase4
Changes needed for `--cid-base` option in go-ipfs (simplified vesion)
2 parents fe89746 + bd83ee2 commit d5fe12a

7 files changed

Lines changed: 290 additions & 27 deletions

File tree

.travis.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
sudo: false
2+
3+
4+
language: go
5+
go:
6+
- 'tip'
7+
8+
install:
9+
- go get github.com/whyrusleeping/gx
10+
- go get github.com/whyrusleeping/gx-go
11+
- gx install --global
12+
script:
13+
- gx test -v -race -coverprofile=coverage.txt -covermode=atomic .
14+
15+
after_success:
16+
- bash <(curl -s https://codecov.io/bash)
17+
18+
cache:
19+
directories:
20+
- $GOPATH/src/gx
21+
22+
notifications:
23+
email: false
24+

ci/Jenkinsfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
golang()

cid-fmt/main.go

Lines changed: 67 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package main
22

33
import (
4+
"bufio"
45
"fmt"
6+
"io"
57
"os"
68
"strings"
79

@@ -12,7 +14,9 @@ import (
1214
)
1315

1416
func usage() {
15-
fmt.Fprintf(os.Stderr, "usage: %s [-b multibase-code] [-v cid-version] <fmt-str> <cid> ...\n\n", os.Args[0])
17+
fmt.Fprintf(os.Stderr, "usage: %s [-b multibase-code] [-v cid-version] [--filter] <fmt-str> <cid> ...\n", os.Args[0])
18+
fmt.Fprintf(os.Stderr, "--filter will read from stdin and convert anything that looks like a <cid>\n")
19+
fmt.Fprintf(os.Stderr, " -- including any non-cids that are valid Multihashes).\n")
1620
fmt.Fprintf(os.Stderr, "<fmt-str> is either 'prefix' or a printf style format string:\n%s", cidutil.FormatRef)
1721
os.Exit(2)
1822
}
@@ -24,8 +28,9 @@ func main() {
2428
newBase := mb.Encoding(-1)
2529
var verConv func(cid c.Cid) (c.Cid, error)
2630
args := os.Args[1:]
31+
filter := false
2732
outer:
28-
for {
33+
for len(args) > 0 {
2934
switch args[0] {
3035
case "-b":
3136
if len(args) < 2 {
@@ -52,11 +57,14 @@ outer:
5257
os.Exit(2)
5358
}
5459
args = args[2:]
60+
case "--filter":
61+
filter = true
62+
args = args[1:]
5563
default:
5664
break outer
5765
}
5866
}
59-
if len(args) < 2 {
67+
if len(args) < 1 {
6068
usage()
6169
}
6270
fmtStr := args[0]
@@ -69,41 +77,73 @@ outer:
6977
os.Exit(2)
7078
}
7179
}
72-
for _, cidStr := range args[1:] {
73-
cid, err := c.Decode(cidStr)
74-
if err != nil {
75-
fmt.Fprintf(os.Stdout, "!INVALID_CID!\n")
76-
errorMsg("%s: %v", cidStr, err)
77-
// Don't abort on a bad cid
78-
continue
79-
}
80+
format := func(cid c.Cid, cidStr string) (string, error) {
8081
base := newBase
81-
if newBase == -1 {
82+
if base == -1 {
8283
base, _ = c.ExtractEncoding(cidStr)
8384
}
85+
var err error
8486
if verConv != nil {
8587
cid, err = verConv(cid)
8688
if err != nil {
87-
fmt.Fprintf(os.Stdout, "!ERROR!\n")
88-
errorMsg("%s: %v", cidStr, err)
89-
// Don't abort on a bad conversion
90-
continue
89+
return "", err
90+
}
91+
}
92+
return cidutil.Format(fmtStr, base, cid)
93+
}
94+
if filter {
95+
scanner := bufio.NewScanner(os.Stdin)
96+
for scanner.Scan() {
97+
buf := scanner.Bytes()
98+
for {
99+
i, j, cid, cidStr := cidutil.ScanForCid(buf)
100+
os.Stdout.Write(buf[0:i])
101+
if i == len(buf) {
102+
os.Stdout.Write([]byte{'\n'})
103+
break
104+
}
105+
str, err := format(cid, cidStr)
106+
switch err.(type) {
107+
case cidutil.FormatStringError:
108+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
109+
os.Exit(2)
110+
default:
111+
// just use the orignal sting on non-fatal error
112+
str = cidStr
113+
case nil:
114+
}
115+
io.WriteString(os.Stdout, str)
116+
buf = buf[j:]
91117
}
92118
}
93-
str, err := cidutil.Format(fmtStr, base, cid)
94-
switch err.(type) {
95-
case cidutil.FormatStringError:
119+
if err := scanner.Err(); err != nil {
96120
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
97121
os.Exit(2)
98-
default:
99-
fmt.Fprintf(os.Stdout, "!ERROR!\n")
100-
errorMsg("%s: %v", cidStr, err)
101-
// Don't abort on cid specific errors
102-
continue
103-
case nil:
104-
// no error
105122
}
106-
fmt.Fprintf(os.Stdout, "%s\n", str)
123+
} else {
124+
for _, cidStr := range args[1:] {
125+
cid, err := c.Decode(cidStr)
126+
if err != nil {
127+
fmt.Fprintf(os.Stdout, "!INVALID_CID!\n")
128+
errorMsg("%s: %v", cidStr, err)
129+
// Don't abort on a bad cid
130+
continue
131+
}
132+
str, err := format(cid, cidStr)
133+
switch err.(type) {
134+
case cidutil.FormatStringError:
135+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
136+
os.Exit(2)
137+
default:
138+
fmt.Fprintf(os.Stdout, "!ERROR!\n")
139+
errorMsg("%s: %v", cidStr, err)
140+
// Don't abort on cid specific errors
141+
continue
142+
case nil:
143+
// no error
144+
}
145+
fmt.Fprintf(os.Stdout, "%s\n", str)
146+
}
107147
}
108148
os.Exit(exitCode)
109149
}

cidenc/encoder.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package cidenc
2+
3+
import (
4+
cid "github.com/ipfs/go-cid"
5+
mbase "github.com/multiformats/go-multibase"
6+
)
7+
8+
// Encoder is a basic Encoder that will encode CIDs using a specified
9+
// base and optionally upgrade a CIDv0 to CIDv1
10+
type Encoder struct {
11+
Base mbase.Encoder // The multibase to use
12+
Upgrade bool // If true upgrade CIDv0 to CIDv1 when encoding
13+
}
14+
15+
// Default return a new default encoder
16+
func Default() Encoder {
17+
return Encoder{Base: mbase.MustNewEncoder(mbase.Base58BTC)}
18+
}
19+
20+
// Encode encodes the cid using the parameters of the Encoder
21+
func (enc Encoder) Encode(c cid.Cid) string {
22+
if enc.Upgrade && c.Version() == 0 {
23+
c = cid.NewCidV1(c.Type(), c.Hash())
24+
}
25+
return c.Encode(enc.Base)
26+
}
27+
28+
// Recode reencodes the cid string to match the parameters of the
29+
// encoder
30+
func (enc Encoder) Recode(v string) (string, error) {
31+
skip, err := enc.noopRecode(v)
32+
if skip || err != nil {
33+
return v, err
34+
}
35+
36+
c, err := cid.Decode(v)
37+
if err != nil {
38+
return v, err
39+
}
40+
41+
return enc.Encode(c), nil
42+
}
43+
44+
func (enc Encoder) noopRecode(v string) (bool, error) {
45+
if len(v) < 2 {
46+
return false, cid.ErrCidTooShort
47+
}
48+
ver := cidVer(v)
49+
skip := ver == 0 && !enc.Upgrade || ver == 1 && v[0] == byte(enc.Base.Encoding())
50+
return skip, nil
51+
}
52+
53+
func cidVer(v string) int {
54+
if len(v) == 46 && v[:2] == "Qm" {
55+
return 0
56+
} else {
57+
return 1
58+
}
59+
}

cidenc/encoder_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package cidenc
2+
3+
import (
4+
"testing"
5+
6+
cid "github.com/ipfs/go-cid"
7+
mbase "github.com/multiformats/go-multibase"
8+
)
9+
10+
func TestCidEncoder(t *testing.T) {
11+
cidv0str := "QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n"
12+
cidv1str := "zdj7Wkkhxcu2rsiN6GUyHCLsSLL47kdUNfjbFqBUUhMFTZKBi"
13+
cidb32str := "bafybeihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku"
14+
cidv0, _ := cid.Decode(cidv0str)
15+
cidv1, _ := cid.Decode(cidv1str)
16+
17+
testEncode := func(enc Encoder, cid cid.Cid, expect string) {
18+
actual := enc.Encode(cid)
19+
if actual != expect {
20+
t.Errorf("%+v.Encode(%s): expected %s but got %s", enc, cid, expect, actual)
21+
}
22+
}
23+
24+
testRecode := func(enc Encoder, cid string, expect string) {
25+
actual, err := enc.Recode(cid)
26+
if err != nil {
27+
t.Errorf("%+v.Recode(%s): %s", enc, cid, err)
28+
return
29+
}
30+
if actual != expect {
31+
t.Errorf("%+v.Recode(%s): expected %s but got %s", enc, cid, expect, actual)
32+
}
33+
}
34+
35+
enc := Encoder{Base: mbase.MustNewEncoder(mbase.Base58BTC), Upgrade: false}
36+
testEncode(enc, cidv0, cidv0str)
37+
testEncode(enc, cidv1, cidv1str)
38+
testRecode(enc, cidv0str, cidv0str)
39+
testRecode(enc, cidv1str, cidv1str)
40+
testRecode(enc, cidb32str, cidv1str)
41+
42+
enc = Encoder{Base: mbase.MustNewEncoder(mbase.Base58BTC), Upgrade: true}
43+
testEncode(enc, cidv0, cidv1str)
44+
testEncode(enc, cidv1, cidv1str)
45+
testRecode(enc, cidv0str, cidv1str)
46+
testRecode(enc, cidv1str, cidv1str)
47+
testRecode(enc, cidb32str, cidv1str)
48+
49+
enc = Encoder{Base: mbase.MustNewEncoder(mbase.Base32), Upgrade: false}
50+
testEncode(enc, cidv0, cidv0str)
51+
testEncode(enc, cidv1, cidb32str)
52+
testRecode(enc, cidv0str, cidv0str)
53+
testRecode(enc, cidv1str, cidb32str)
54+
testRecode(enc, cidb32str, cidb32str)
55+
56+
enc = Encoder{Base: mbase.MustNewEncoder(mbase.Base32), Upgrade: true}
57+
testEncode(enc, cidv0, cidb32str)
58+
testEncode(enc, cidv1, cidb32str)
59+
testRecode(enc, cidv0str, cidb32str)
60+
testRecode(enc, cidv1str, cidb32str)
61+
testRecode(enc, cidb32str, cidb32str)
62+
}

format.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,48 @@ func encode(base mb.Encoder, data []byte, strip bool) string {
150150
}
151151
return str
152152
}
153+
154+
// ScanForCid scans bytes for anything resembling a CID. If one is
155+
// found `i` will point to the begging of the cid and `j` to to the
156+
// end and the cid will be returned, otherwise `i` and `j` will point
157+
// the end of the buffer and the cid will be `Undef`.
158+
func ScanForCid(buf []byte) (i, j int, cid c.Cid, cidStr string) {
159+
i = 0
160+
for {
161+
i = j
162+
for i < len(buf) && !asciiIsAlpha(buf[i]) {
163+
i++
164+
}
165+
j = i
166+
if i == len(buf) {
167+
return
168+
}
169+
for j < len(buf) && asciiIsAlpha(buf[j]) {
170+
j++
171+
}
172+
if j-i <= 1 || j-i > 128 || !supported[buf[i]] {
173+
continue
174+
}
175+
var err error
176+
cidStr = string(buf[i:j])
177+
cid, err = c.Decode(cidStr)
178+
if err == nil {
179+
return
180+
}
181+
}
182+
}
183+
184+
var supported = make([]bool, 256)
185+
186+
func init() {
187+
// for now base64 encoding are not supported as they contain non
188+
// alhphanumeric characters
189+
supportedPrefixes := []byte("QfFbBcCvVtThzZ")
190+
for _, b := range supportedPrefixes {
191+
supported[b] = true
192+
}
193+
}
194+
195+
func asciiIsAlpha(b byte) bool {
196+
return ('A' <= b && b <= 'Z') || ('a' <= b && b <= 'z') || ('0' <= b && b <= '9')
197+
}

format_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,35 @@ func testFmt(t *testing.T, cidStr string, newBase mb.Encoding, fmtStr string, re
7272
t.Error(fmt.Sprintf("expected: %s; but got: %s", result, str))
7373
}
7474
}
75+
76+
func TestScanForCid(t *testing.T) {
77+
testStr := []byte(`
78+
/ipfs/QmYFbmndVP7QqAVWyKhpmMuQHMaD88pkK57RgYVimmoh5H 22 45
79+
/ipfs/zdj7WfLr9DhLrb1hsoSi4fSdjjxuZmeqgEtBPWxMLtPbDNbFD/foobar
80+
BAFYBEIETJGSRL3EQPQPCABV3G6IUBYTSIFVQ24XRRHD3JUETSKLTGQ7DJA
81+
skip me (too long): QmYFbmndVP7QqAVWyKhpmMuQHMaD88pkK57RgYVimmoh5H876
82+
skip me (too short): bafybeietjgsrl3eqpqpcabv3g6iubytsifvq
83+
bafybeietjgsrl3eqpqpcabv3g6iubytsifvq24xrrhd3juetskltgq7dja
84+
bafybeietjgsrl3eqpqpcabv3g6iubytsifvq24xrrhd3juetskltgq7dja.
85+
`)
86+
cids := []string{
87+
"QmYFbmndVP7QqAVWyKhpmMuQHMaD88pkK57RgYVimmoh5H",
88+
"zdj7WfLr9DhLrb1hsoSi4fSdjjxuZmeqgEtBPWxMLtPbDNbFD",
89+
"BAFYBEIETJGSRL3EQPQPCABV3G6IUBYTSIFVQ24XRRHD3JUETSKLTGQ7DJA",
90+
"bafybeietjgsrl3eqpqpcabv3g6iubytsifvq24xrrhd3juetskltgq7dja",
91+
"bafybeietjgsrl3eqpqpcabv3g6iubytsifvq24xrrhd3juetskltgq7dja",
92+
}
93+
94+
buf := testStr
95+
idx := 0
96+
offset := 0
97+
for len(buf) > 0 {
98+
_, j, _, cidStr := ScanForCid(buf)
99+
if cidStr != "" && cids[idx] != cidStr {
100+
t.Fatalf("Scan failed, expected %s, got %s (idx=%d offset=%d)", cids[idx], cidStr, idx, offset)
101+
}
102+
buf = buf[j:]
103+
offset += j
104+
idx++
105+
}
106+
}

0 commit comments

Comments
 (0)