Skip to content

Commit d9bca65

Browse files
committed
Added base64 support
1 parent d7b158f commit d9bca65

File tree

10 files changed

+218
-0
lines changed

10 files changed

+218
-0
lines changed

pkg/yqlib/decoder.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const (
1313
YamlInputFormat = 1 << iota
1414
XMLInputFormat
1515
PropertiesInputFormat
16+
Base64InputFormat
1617
)
1718

1819
type Decoder interface {

pkg/yqlib/decoder_base64.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package yqlib
2+
3+
import (
4+
"bytes"
5+
"encoding/base64"
6+
"io"
7+
8+
yaml "gopkg.in/yaml.v3"
9+
)
10+
11+
type base64Decoder struct {
12+
reader io.Reader
13+
finished bool
14+
encoding base64.Encoding
15+
}
16+
17+
func NewBase64Decoder() Decoder {
18+
return &base64Decoder{finished: false, encoding: *base64.StdEncoding}
19+
}
20+
21+
func (dec *base64Decoder) Init(reader io.Reader) {
22+
dec.reader = reader
23+
dec.finished = false
24+
}
25+
26+
func (dec *base64Decoder) Decode(rootYamlNode *yaml.Node) error {
27+
if dec.finished {
28+
return io.EOF
29+
}
30+
base64Reader := base64.NewDecoder(&dec.encoding, dec.reader)
31+
buf := new(bytes.Buffer)
32+
33+
if _, err := buf.ReadFrom(base64Reader); err != nil {
34+
return err
35+
}
36+
if buf.Len() == 0 {
37+
dec.finished = true
38+
return io.EOF
39+
}
40+
rootYamlNode.Kind = yaml.ScalarNode
41+
rootYamlNode.Tag = "!!str"
42+
rootYamlNode.Value = buf.String()
43+
return nil
44+
}

pkg/yqlib/doc/operators/encode-decode.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,16 @@ These operators are useful to process yaml documents that have stringified embed
1515
| CSV | | to_csv/@csv |
1616
| TSV | | to_tsv/@tsv |
1717
| XML | from_xml | to_xml(i)/@xml |
18+
| Base64 | @base64d | @base64 |
1819

1920

2021
CSV and TSV format both accept either a single array or scalars (representing a single row), or an array of array of scalars (representing multiple rows).
2122

2223
XML uses the `--xml-attribute-prefix` and `xml-content-name` flags to identify attributes and content fields.
2324

2425

26+
Base64 assumes [rfc4648](https://rfc-editor.org/rfc/rfc4648.html) encoding. Encoding and decoding both assume that the content is a string.
27+
2528
{% hint style="warning" %}
2629
Note that versions prior to 4.18 require the 'eval/e' command to be specified.&#x20;
2730

@@ -354,3 +357,66 @@ b:
354357
foo: bar
355358
```
356359
360+
## Encode a string to base64
361+
Given a sample.yml file of:
362+
```yaml
363+
coolData: a special string
364+
```
365+
then
366+
```bash
367+
yq '.coolData | @base64' sample.yml
368+
```
369+
will output
370+
```yaml
371+
YSBzcGVjaWFsIHN0cmluZw==
372+
```
373+
374+
## Encode a yaml document to base64
375+
Pipe through @yaml first to convert to a string, then use @base64 to encode it.
376+
377+
Given a sample.yml file of:
378+
```yaml
379+
a: apple
380+
```
381+
then
382+
```bash
383+
yq '@yaml | @base64' sample.yml
384+
```
385+
will output
386+
```yaml
387+
YTogYXBwbGUK
388+
```
389+
390+
## Decode a base64 encoded string
391+
Decoded data is assumed to be a string.
392+
393+
Given a sample.yml file of:
394+
```yaml
395+
coolData: V29ya3Mgd2l0aCBVVEYtMTYg8J+Yig==
396+
```
397+
then
398+
```bash
399+
yq '.coolData | @base64d' sample.yml
400+
```
401+
will output
402+
```yaml
403+
Works with UTF-16 😊
404+
```
405+
406+
## Decode a base64 encoded yaml document
407+
Pipe through `from_yaml` to parse the decoded base64 string as a yaml document.
408+
409+
Given a sample.yml file of:
410+
```yaml
411+
coolData: YTogYXBwbGUK
412+
```
413+
then
414+
```bash
415+
yq '.coolData |= (@base64d | from_yaml)' sample.yml
416+
```
417+
will output
418+
```yaml
419+
coolData:
420+
a: apple
421+
```
422+

pkg/yqlib/doc/operators/headers/encode-decode.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@ These operators are useful to process yaml documents that have stringified embed
1515
| CSV | | to_csv/@csv |
1616
| TSV | | to_tsv/@tsv |
1717
| XML | from_xml | to_xml(i)/@xml |
18+
| Base64 | @base64d | @base64 |
1819

1920

2021
CSV and TSV format both accept either a single array or scalars (representing a single row), or an array of array of scalars (representing multiple rows).
2122

2223
XML uses the `--xml-attribute-prefix` and `xml-content-name` flags to identify attributes and content fields.
2324

25+
26+
Base64 assumes [rfc4648](https://rfc-editor.org/rfc/rfc4648.html) encoding. Encoding and decoding both assume that the content is a string.

pkg/yqlib/doc/usage/base64.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
{% hint style="warning" %}
3+
Note that versions prior to 4.18 require the 'eval/e' command to be specified.&#x20;
4+
5+
`yq e <exp> <file>`
6+
{% endhint %}
7+
8+
## Decode Base64
9+
Decoded data is assumed to be a string.
10+
11+
Given a sample.yml file of:
12+
```yml
13+
V29ya3Mgd2l0aCBVVEYtMTYg8J+Yig==
14+
```
15+
then
16+
```bash
17+
yq -p=props sample.properties
18+
```
19+
will output
20+
```yaml
21+
V29ya3Mgd2l0aCBVVEYtMTYg8J+Yig: =
22+
```
23+

pkg/yqlib/encoder_base64.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package yqlib
2+
3+
import (
4+
"encoding/base64"
5+
"fmt"
6+
"io"
7+
8+
yaml "gopkg.in/yaml.v3"
9+
)
10+
11+
type base64Encoder struct {
12+
encoding base64.Encoding
13+
}
14+
15+
func NewBase64Encoder() Encoder {
16+
return &base64Encoder{encoding: *base64.StdEncoding}
17+
}
18+
19+
func (e *base64Encoder) CanHandleAliases() bool {
20+
return false
21+
}
22+
23+
func (e *base64Encoder) PrintDocumentSeparator(writer io.Writer) error {
24+
return nil
25+
}
26+
27+
func (e *base64Encoder) PrintLeadingContent(writer io.Writer, content string) error {
28+
return nil
29+
}
30+
31+
func (e *base64Encoder) Encode(writer io.Writer, originalNode *yaml.Node) error {
32+
node := unwrapDoc(originalNode)
33+
if guessTagFromCustomType(node) != "!!str" {
34+
return fmt.Errorf("cannot encode %v as base64, can only operate on strings. Please first pipe through another encoding operator to convert the value to a string", node.Tag)
35+
}
36+
_, err := writer.Write([]byte(e.encoding.EncodeToString([]byte(originalNode.Value))))
37+
return err
38+
}

pkg/yqlib/expression_tokeniser.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,9 @@ func initLexer() (*lex.Lexer, error) {
363363
lexer.Add([]byte(`to_xml`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: XMLOutputFormat, indent: 2}))
364364
lexer.Add([]byte(`@xml`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: XMLOutputFormat, indent: 0}))
365365

366+
lexer.Add([]byte(`@base64`), opTokenWithPrefs(encodeOpType, nil, encoderPreferences{format: Base64OutputFormat}))
367+
lexer.Add([]byte(`@base64d`), opTokenWithPrefs(decodeOpType, nil, decoderPreferences{format: Base64InputFormat}))
368+
366369
lexer.Add([]byte(`fromyaml`), opTokenWithPrefs(decodeOpType, nil, decoderPreferences{format: YamlInputFormat}))
367370
lexer.Add([]byte(`fromjson`), opTokenWithPrefs(decodeOpType, nil, decoderPreferences{format: YamlInputFormat}))
368371
lexer.Add([]byte(`fromxml`), opTokenWithPrefs(decodeOpType, nil, decoderPreferences{format: XMLInputFormat}))

pkg/yqlib/operator_encoder_decoder.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ func configureEncoder(format PrinterOutputFormat, indent int) Encoder {
2424
return NewYamlEncoder(indent, false, true, true)
2525
case XMLOutputFormat:
2626
return NewXMLEncoder(indent, XMLPreferences.AttributePrefix, XMLPreferences.ContentName)
27+
case Base64OutputFormat:
28+
return NewBase64Encoder()
2729
}
2830
panic("invalid encoder")
2931
}
@@ -103,6 +105,8 @@ func decodeOperator(d *dataTreeNavigator, context Context, expressionNode *Expre
103105
decoder = NewYamlDecoder()
104106
case XMLInputFormat:
105107
decoder = NewXMLDecoder(XMLPreferences.AttributePrefix, XMLPreferences.ContentName)
108+
case Base64InputFormat:
109+
decoder = NewBase64Decoder()
106110
}
107111

108112
var results = list.New()

pkg/yqlib/operator_encoder_decoder_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,41 @@ var encoderDecoderOperatorScenarios = []expressionScenario{
200200
"D0, P[], (doc)::a: \"<foo>bar</foo>\"\nb:\n foo: bar\n",
201201
},
202202
},
203+
{
204+
description: "Encode a string to base64",
205+
document: "coolData: a special string",
206+
expression: ".coolData | @base64",
207+
expected: []string{
208+
"D0, P[coolData], (!!str)::YSBzcGVjaWFsIHN0cmluZw==\n",
209+
},
210+
},
211+
{
212+
description: "Encode a yaml document to base64",
213+
subdescription: "Pipe through @yaml first to convert to a string, then use @base64 to encode it.",
214+
document: "a: apple",
215+
expression: "@yaml | @base64",
216+
expected: []string{
217+
"D0, P[], (!!str)::YTogYXBwbGUK\n",
218+
},
219+
},
220+
{
221+
description: "Decode a base64 encoded string",
222+
subdescription: "Decoded data is assumed to be a string.",
223+
document: "coolData: V29ya3Mgd2l0aCBVVEYtMTYg8J+Yig==",
224+
expression: ".coolData | @base64d",
225+
expected: []string{
226+
"D0, P[coolData], (!!str)::Works with UTF-16 😊\n",
227+
},
228+
},
229+
{
230+
description: "Decode a base64 encoded yaml document",
231+
subdescription: "Pipe through `from_yaml` to parse the decoded base64 string as a yaml document.",
232+
document: "coolData: YTogYXBwbGUK",
233+
expression: ".coolData |= (@base64d | from_yaml)",
234+
expected: []string{
235+
"D0, P[], (doc)::coolData:\n a: apple\n",
236+
},
237+
},
203238
}
204239

205240
func TestEncoderDecoderOperatorScenarios(t *testing.T) {

pkg/yqlib/printer.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const (
2626
CSVOutputFormat
2727
TSVOutputFormat
2828
XMLOutputFormat
29+
Base64OutputFormat
2930
)
3031

3132
func OutputFormatFromString(format string) (PrinterOutputFormat, error) {

0 commit comments

Comments
 (0)