Search code examples
gobase64png

Illegal base64 data at input byte for seemingly valid png


I'm attempting to decode a data URL that was generated from a javascript canvas' toDataURL function.

The following golang application fails with the error illegal base64 data at input byte 129)

package main

import (
    "encoding/base64"
    "fmt"
    "net/url"
    "strings"
)

func main() {
    pngData := "iVBORw0KGgoAAAANSUhEUgAAAF0AAAABCAYAAAC8PaJPAAAABHNCSVQICAgIfAhkiAAAALVJREFUGFdt0MsKQVEYhuG9CeU0VgamihBl6hqMXYoLchduQFuKicyFARPn0/J+9Q2tevrba639H1YcQhhHUTTBACloFZHFw3FJbODj7xdxjSqmqGCHms+3vv8m3nDwWSA+vVcipjFHHlcMcUTBtcuueSHqfgzlVJ7Nn/o99s5Qf3v0sUAHK///JbahmZQ3gy4S96OZc9A90fkdepM6tNTHDOqz6T3NofeTluuqV82oHOrphNEPw3UwfBVmbU4AAAAASUVORK5CYII="
    pngData, err := url.PathUnescape(pngData)

    if err != nil {
        fmt.Printf("Failed to unescape", err.Error())
        return
    }
    pngData = strings.Replace(pngData, "+", "", -1)
    _, err = base64.URLEncoding.WithPadding(base64.NoPadding).DecodeString(pngData)
    if err != nil {
        fmt.Printf("Failed to decode", err.Error())
    }
}

If I pass the value from pngData into a web-based base64 to png converter, it has no problem generating the image. (a horizontal line of white-ish values)

proof of valid base64

I have tried StdEncoding, RawURLEncoding, and their Raw counterparts. I've also tried with or without padding and I've tried the same pngData string with an additional = and without the trailing =.

Any thoughts on why Golang is refusing to decode this data?

Some of the images I get from the canvas decode just fine. But some, like this one, do not.


Solution

  • Steven Penny's answer shows a way to do this, but I have to ask:

    • Why do you call url.PathUnescape? The data contain no path escape characters (no %-encoding). The call is harmless but unnecessary.

    • Why did you use the alternate encoding (URLEncoding)? As we see in the base64 package documentation, the difference between the standard encoding and the alternate encoding is that the alternate encoding uses - and _ in place of + and /. But if we look at the data string, it contains plus signs and slashes, and has no dashes or underscores, so it has clearly been encoded with the standard encoding.

    • Why did you call for base64.NoPadding? The input data ends with =, which is a padding character.

    • Why did you call for base64.NoPadding via base64.URLEncoding.WithPadding(base64.NoPadding)? The documentation shows us that this can be spelled base64.RawURLEncoding.

    • Why did you explicitly ask to strip out + characters (not a good idea) but not / characters?

    If we drop all of those (and split up a long input line for posting purposes) we get this (playground link):

    package main
    
    import (
        "encoding/base64"
        "fmt"
    )
    
    func main() {
        data := "iVBORw0KGgoAAAANSUhEUgAAAF0AAAABCAYAAAC8PaJPAAAABH" +
            "NCSVQICAgIfAhkiAAAALVJREFUGFdt0MsKQVEYhuG9CeU0Vgam" +
            "ihBl6hqMXYoLchduQFuKicyFARPn0/J+9Q2tevrba639H1YcQh" +
            "hHUTTBACloFZHFw3FJbODj7xdxjSqmqGCHms+3vv8m3nDwWSA+" +
            "vVcipjFHHlcMcUTBtcuueSHqfgzlVJ7Nn/o99s5Qf3v0sUAHK/" +
            "//JbahmZQ3gy4S96OZc9A90fkdepM6tNTHDOqz6T3NofeTluuq" +
            "V82oHOrphNEPw3UwfBVmbU4AAAAASUVORK5CYII="
        b, err := base64.StdEncoding.DecodeString(data)
        if err != nil {
            fmt.Printf("Failed to decode: %s\n", err)
        } else {
            fmt.Printf("bytes begin with: %q\n", b[0:4])
        }
    }