├── .travis.yml ├── LICENSE ├── Readme.md ├── base62.go ├── base62_test.go ├── codecov.yml └── go.test.sh /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.8.x 5 | - 1.9.x 6 | 7 | before_install: 8 | - go get -t -v ./... 9 | 10 | script: 11 | - bash go.test.sh 12 | 13 | after_success: 14 | - bash <(curl -s https://codecov.io/bash) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Lytics Inc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # [Base62.go](http://libraries.io/go/github.com%2Fandrew%2Fbase62.go) 2 | 3 | * *Original author*: Andrew Nesbitt 4 | * *Forked from*: https://github.com/andrew/base62.go 5 | 6 | An attempt at a go library to provide Base62 encoding, perfect for URL safe values, and url shorteners. 7 | 8 | 9 | ```go 10 | 11 | // Encode a value 12 | urlVal := "http://www.biglongurl.io/?utm_content=content&utm_campaign=campaign" 13 | encodedUrl := base62.StdEncoding.EncodeToString([]byte(urlVal)) 14 | 15 | 16 | // Unencoded it 17 | byteUrl, err := base62.StdEncoding.DecodeString(encodedUrl) 18 | 19 | 20 | ``` -------------------------------------------------------------------------------- /base62.go: -------------------------------------------------------------------------------- 1 | // heavily influcened by http://golang.org/src/pkg/encoding/base64/base64.go 2 | 3 | // Package base62 implements base62 encoding 4 | package base62 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | "io" 10 | ) 11 | 12 | // Encodings 13 | type Encoding struct { 14 | encode string 15 | decodeMap [256]byte 16 | } 17 | 18 | const encodeStd = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-0123456789." //"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" 19 | 20 | // NewEncoding returns a new Encoding defined by the given alphabet, 21 | // which must be a 62-byte string. 22 | func NewEncoding(encoder string) *Encoding { 23 | e := new(Encoding) 24 | e.encode = encoder 25 | for i := 0; i < len(e.decodeMap); i++ { 26 | e.decodeMap[i] = 0xFF 27 | } 28 | for i := 0; i < len(encoder); i++ { 29 | e.decodeMap[encoder[i]] = byte(i) 30 | } 31 | return e 32 | } 33 | 34 | var StdEncoding = NewEncoding(encodeStd) 35 | 36 | /* 37 | * Encoder 38 | */ 39 | 40 | // Encode encodes src using the encoding enc, writing 41 | // EncodedLen(len(src)) bytes to dst. 42 | // 43 | // The encoding pads the output to a multiple of 4 bytes, 44 | // so Encode is not appropriate for use on individual blocks 45 | // of a large data stream. Use NewEncoder() instead. 46 | func (enc *Encoding) Encode(dst, src []byte) { 47 | if len(src) == 0 { 48 | return 49 | } 50 | 51 | for len(src) > 0 { 52 | dst[0] = 0 53 | dst[1] = 0 54 | dst[2] = 0 55 | dst[3] = 0 56 | 57 | // Unpack 4x 6-bit source blocks into a 4 byte 58 | // destination quantum 59 | switch len(src) { 60 | default: 61 | dst[3] |= src[2] & 0x3F 62 | dst[2] |= src[2] >> 6 63 | fallthrough 64 | case 2: 65 | dst[2] |= (src[1] << 2) & 0x3F 66 | dst[1] |= src[1] >> 4 67 | fallthrough 68 | case 1: 69 | dst[1] |= (src[0] << 4) & 0x3F 70 | dst[0] |= src[0] >> 2 71 | } 72 | 73 | // Encode 6-bit blocks using the base62 alphabet 74 | for j := 0; j < 4; j++ { 75 | dst[j] = enc.encode[dst[j]] 76 | } 77 | 78 | // Pad the final quantum 79 | if len(src) < 3 { 80 | dst[3] = '+' 81 | if len(src) < 2 { 82 | dst[2] = '+' 83 | } 84 | break 85 | } 86 | 87 | src = src[3:] 88 | dst = dst[4:] 89 | } 90 | } 91 | 92 | // EncodeToString returns the base62 encoding of src. 93 | func (enc *Encoding) EncodeToString(src []byte) string { 94 | buf := make([]byte, enc.EncodedLen(len(src))) 95 | enc.Encode(buf, src) 96 | return string(buf) 97 | } 98 | 99 | type encoder struct { 100 | err error 101 | enc *Encoding 102 | w io.Writer 103 | buf [3]byte // buffered data waiting to be encoded 104 | nbuf int // number of bytes in buf 105 | out [1024]byte // output buffer 106 | } 107 | 108 | func (e *encoder) Write(p []byte) (n int, err error) { 109 | if e.err != nil { 110 | return 0, e.err 111 | } 112 | 113 | // Leading fringe. 114 | if e.nbuf > 0 { 115 | var i int 116 | for i = 0; i < len(p) && e.nbuf < 3; i++ { 117 | e.buf[e.nbuf] = p[i] 118 | e.nbuf++ 119 | } 120 | n += i 121 | p = p[i:] 122 | if e.nbuf < 3 { 123 | return 124 | } 125 | e.enc.Encode(e.out[0:], e.buf[0:]) 126 | if _, e.err = e.w.Write(e.out[0:4]); e.err != nil { 127 | return n, e.err 128 | } 129 | e.nbuf = 0 130 | } 131 | 132 | // Large interior chunks. 133 | for len(p) >= 3 { 134 | nn := len(e.out) / 4 * 3 135 | if nn > len(p) { 136 | nn = len(p) 137 | } 138 | nn -= nn % 3 139 | if nn > 0 { 140 | e.enc.Encode(e.out[0:], p[0:nn]) 141 | if _, e.err = e.w.Write(e.out[0 : nn/3*4]); e.err != nil { 142 | return n, e.err 143 | } 144 | } 145 | n += nn 146 | p = p[nn:] 147 | } 148 | 149 | // Trailing fringe. 150 | for i := 0; i < len(p); i++ { 151 | e.buf[i] = p[i] 152 | } 153 | e.nbuf = len(p) 154 | n += len(p) 155 | return 156 | } 157 | 158 | // Close flushes any pending output from the encoder. 159 | // It is an error to call Write after calling Close. 160 | func (e *encoder) Close() error { 161 | // If there's anything left in the buffer, flush it out 162 | if e.err == nil && e.nbuf > 0 { 163 | e.enc.Encode(e.out[0:], e.buf[0:e.nbuf]) 164 | e.nbuf = 0 165 | _, e.err = e.w.Write(e.out[0:4]) 166 | } 167 | return e.err 168 | } 169 | 170 | // NewEncoder returns a new base62 stream encoder. Data written to 171 | // the returned writer will be encoded using enc and then written to w. 172 | // Base62 encodings operate in 4-byte blocks; when finished 173 | // writing, the caller must Close the returned encoder to flush any 174 | // partially written blocks. 175 | func NewEncoder(enc *Encoding, w io.Writer) io.WriteCloser { 176 | return &encoder{enc: enc, w: w} 177 | } 178 | 179 | // EncodedLen returns the length in bytes of the base62 encoding 180 | // of an input buffer of length n. 181 | func (enc *Encoding) EncodedLen(n int) int { return (n + 2) / 3 * 4 } 182 | 183 | /* 184 | * Decoder 185 | */ 186 | 187 | type CorruptInputError error 188 | 189 | // func (e CorruptInputError) String() error { 190 | // return "illegal base62 data at input byte " + strconv.FormatInt(int64(e), 10) 191 | // } 192 | 193 | // decode is like Decode but returns an additional 'end' value, which 194 | // indicates if end-of-message padding was encountered and thus any 195 | // additional data is an error. decode also assumes len(src)%4==0, 196 | // since it is meant for internal use. 197 | func (enc *Encoding) decode(dst, src []byte) (n int, end bool, err error) { 198 | for i := 0; i < len(src)/4 && !end; i++ { 199 | // Decode quantum using the base62 alphabet 200 | var dbuf [4]byte 201 | dlen := 4 202 | 203 | dbufloop: 204 | for j := 0; j < 4; j++ { 205 | in := src[i*4+j] 206 | if in == '+' && j >= 2 && i == len(src)/4-1 { 207 | // We've reached the end and there's 208 | // padding 209 | if src[i*4+3] != '+' { 210 | return n, false, errors.New(fmt.Sprintf("%d", i*4+2)) 211 | } 212 | dlen = j 213 | end = true 214 | break dbufloop 215 | } 216 | dbuf[j] = enc.decodeMap[in] 217 | if dbuf[j] == 0xFF { 218 | return n, false, errors.New(fmt.Sprintf("%d", i*4+j)) 219 | } 220 | } 221 | 222 | // Pack 4x 6-bit source blocks into 3 byte destination 223 | // quantum 224 | switch dlen { 225 | case 4: 226 | dst[i*3+2] = dbuf[2]<<6 | dbuf[3] 227 | fallthrough 228 | case 3: 229 | dst[i*3+1] = dbuf[1]<<4 | dbuf[2]>>2 230 | fallthrough 231 | case 2: 232 | dst[i*3+0] = dbuf[0]<<2 | dbuf[1]>>4 233 | } 234 | n += dlen - 1 235 | } 236 | 237 | return n, end, nil 238 | } 239 | 240 | // Decode decodes src using the encoding enc. It writes at most 241 | // DecodedLen(len(src)) bytes to dst and returns the number of bytes 242 | // written. If src contains invalid base62 data, it will return the 243 | // number of bytes successfully written and CorruptInputError. 244 | func (enc *Encoding) Decode(dst, src []byte) (n int, err error) { 245 | pad := len(src) % 4 246 | if pad != 0 { 247 | for i := 0; i < pad; i++ { 248 | src = append(src, '+') 249 | } 250 | } 251 | 252 | n, _, err = enc.decode(dst, src) 253 | return 254 | } 255 | 256 | // DecodeString returns the bytes represented by the base62 string s. 257 | func (enc *Encoding) DecodeString(s string) ([]byte, error) { 258 | dbuf := make([]byte, enc.DecodedLen(len(s))) 259 | n, err := enc.Decode(dbuf, []byte(s)) 260 | return dbuf[:n], err 261 | } 262 | 263 | type decoder struct { 264 | err error 265 | enc *Encoding 266 | r io.Reader 267 | end bool // saw end of message 268 | buf [1024]byte // leftover input 269 | nbuf int 270 | out []byte // leftover decoded output 271 | outbuf [1024 / 4 * 3]byte 272 | } 273 | 274 | func (d *decoder) Read(p []byte) (n int, err error) { 275 | if d.err != nil { 276 | return 0, d.err 277 | } 278 | 279 | // Use leftover decoded output from last read. 280 | if len(d.out) > 0 { 281 | n = copy(p, d.out) 282 | d.out = d.out[n:] 283 | return n, nil 284 | } 285 | 286 | // Read a chunk. 287 | nn := len(p) / 3 * 4 288 | if nn < 4 { 289 | nn = 4 290 | } 291 | if nn > len(d.buf) { 292 | nn = len(d.buf) 293 | } 294 | nn, d.err = io.ReadAtLeast(d.r, d.buf[d.nbuf:nn], 4-d.nbuf) 295 | d.nbuf += nn 296 | if d.nbuf < 4 { 297 | return 0, d.err 298 | } 299 | 300 | // Decode chunk into p, or d.out and then p if p is too small. 301 | nr := d.nbuf / 4 * 4 302 | nw := d.nbuf / 4 * 3 303 | if nw > len(p) { 304 | nw, d.end, d.err = d.enc.decode(d.outbuf[0:], d.buf[0:nr]) 305 | d.out = d.outbuf[0:nw] 306 | n = copy(p, d.out) 307 | d.out = d.out[n:] 308 | } else { 309 | n, d.end, d.err = d.enc.decode(p, d.buf[0:nr]) 310 | } 311 | d.nbuf -= nr 312 | for i := 0; i < d.nbuf; i++ { 313 | d.buf[i] = d.buf[i+nr] 314 | } 315 | 316 | if d.err == nil { 317 | d.err = err 318 | } 319 | return n, d.err 320 | } 321 | 322 | // NewDecoder constructs a new base62 stream decoder. 323 | func NewDecoder(enc *Encoding, r io.Reader) io.Reader { 324 | return &decoder{enc: enc, r: r} 325 | } 326 | 327 | // DecodedLen returns the maximum length in bytes of the decoded data 328 | // corresponding to n bytes of base62-encoded data. 329 | func (enc *Encoding) DecodedLen(n int) int { return n / 4 * 3 } 330 | -------------------------------------------------------------------------------- /base62_test.go: -------------------------------------------------------------------------------- 1 | package base62 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestBase62(t *testing.T) { 9 | urlVal := "http://www.lytics.io/?utm_content=content&utm_campaign=campaign" 10 | encodedValue := StdEncoding.EncodeToString([]byte(urlVal)) 11 | assert.NotEqual(t, "", encodedValue) 12 | rtVal, err := StdEncoding.DecodeString(encodedValue) 13 | assert.Equal(t, nil, err) 14 | assert.Equal(t, string(rtVal), urlVal) 15 | 16 | intVal := "999" 17 | encodedValue = StdEncoding.EncodeToString([]byte(intVal)) 18 | assert.Equal(t, "OTk4", encodedValue, "%v", encodedValue) 19 | rtVal, err = StdEncoding.DecodeString(encodedValue) 20 | assert.Equal(t, nil, err) 21 | assert.Equal(t, string(rtVal), intVal) 22 | 23 | urlVal = `http://our-uploads.s3.amazonaws.com/file-export/stuff-1427217700-12.csv?AWSAccessKeyId=AAAAIIG7MAQRTHTD7CLP&Expires=1427304113&Signature=VQsRAhgamiw1RVtbrCXOsMu%2BgFo` 24 | encodedValue = StdEncoding.EncodeToString([]byte(urlVal)) 25 | // We are just ensuring it doesn't panic 26 | assert.True(t, len(encodedValue) > 0) 27 | } 28 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "testutils/*" -------------------------------------------------------------------------------- /go.test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | echo "" > coverage.txt 5 | 6 | for d in $(go list ./... | grep -v vendor); do 7 | go test -race -coverprofile=profile.out -covermode=atomic $d 8 | if [ -f profile.out ]; then 9 | cat profile.out >> coverage.txt 10 | rm profile.out 11 | fi 12 | done --------------------------------------------------------------------------------