-
Notifications
You must be signed in to change notification settings - Fork 0
/
uu.go
131 lines (113 loc) · 3 KB
/
uu.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package uu
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"io"
"strings"
)
// ...
const (
maxBytesPerLine = 45
UUAlphabet = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
)
var (
encoding = base64.NewEncoding(UUAlphabet).WithPadding(base64.NoPadding)
)
// Decoded holds result from uuencoded decode
type Decoded struct {
Data []byte
Filename string
Mode string
}
// Decode decodes UUencoded text
func Decode(data []byte) (*Decoded, error) {
dec := &Decoded{}
if len(data) < 2 {
return dec, errors.New("invalid decode input")
}
rows := strings.Split(string(data), "\n")
if strings.Split(rows[0], " ")[0] != "begin" {
return dec, errors.New("invalid format")
}
if strings.Split(rows[0], " ")[1] == " " || strings.Split(rows[0], " ")[1] == "" {
return dec, errors.New("invalid file permissions")
}
dec.Mode = strings.Split(rows[0], " ")[1]
if strings.Split(rows[0], " ")[2] == " " || strings.Split(rows[0], " ")[2] == "" {
return dec, errors.New("invalid filename")
}
dec.Filename = strings.Split(rows[0], " ")[2]
if rows[len(rows)-2] != "end" {
return dec, errors.New("invalid format: no 'end' marker found")
}
if rows[len(rows)-3] != "`" && rows[len(rows)-3] != " " {
return dec, errors.New("invalid ending format")
}
rows = rows[1 : len(rows)-3]
var err error
dec.Data, err = DecodeBlock(rows)
return dec, err
}
// DecodeBlock decodes a uuencoded text block
func DecodeBlock(rows []string) ([]byte, error) {
data := []byte{}
for i, row := range rows {
res, err := DecodeLine(row)
if err != nil {
return data, fmt.Errorf("DecodeBlock at line %d: %s", i+1, err)
}
data = append(data, res...)
}
return data, nil
}
// DecodeLine decodes a single line of uuencoded text
func DecodeLine(s string) ([]byte, error) {
if len(s) < 2 {
return nil, errors.New("invalid line input")
}
// fix up non-standard padding `, to make golang's base64 not freak out
s = strings.ReplaceAll(s, "`", " ")
data := []byte(s)
l := data[0] - 32 // length
res, err := encoding.DecodeString(s[1:])
if err != nil {
return res, err
}
if len(res) < int(l) {
return nil, errors.New("line decoding failed")
}
return res[0:l], nil
}
// Encode encodes data into uuencoded format, with header and footer
func Encode(data []byte, filename, mode string) ([]byte, error) {
out := []byte{}
out = append(out, fmt.Sprintf("begin %s %s\n", mode, filename)...)
enc, err := EncodeBlock(data)
if err != nil {
return nil, err
}
out = append(out, enc...)
out = append(out, "`\nend\n"...)
return out, nil
}
// EncodeBlock encodes data in raw uunecoded format
func EncodeBlock(data []byte) ([]byte, error) {
out := []byte{}
buf := bytes.NewBuffer(data)
inputBlock := make([]byte, maxBytesPerLine)
for {
n, err := buf.Read(inputBlock)
if n == 0 && err != nil {
if err != io.EOF {
return out, err
}
break
}
out = append(out, byte(n+32)) // length
out = append(out, encoding.EncodeToString(inputBlock[:n])...)
out = append(out, '\n')
}
return out, nil
}