inancgumus/learngo · error

not png

Error message

not png

What it means

transfer() reads the first pngSignLen bytes from the reader and requires them to equal the PNG file signature (\x89PNG\r\n\x1a\n). If the prefix does not match, the input is not a PNG image, so it refuses to continue and returns this error. This guards io.Copy downstream from treating non-PNG data as an image.

Source

Thrown at interfaces/16-io-compose/main.go:70

	const pngSign = "\x89PNG\r\n\x1a\n"
	const pngSignLen = 8

	var memory bytes.Buffer

	// limit what copy() reads.
	// lr := io.LimitReader(r, pngSignLen)
	// if n, err = io.Copy(&memory, lr); err != nil {
	// return n, err
	// }

	// same as above. behind the scenes CopyN() calls LimitReader.
	if n, err = io.CopyN(&memory, r, pngSignLen); err != nil {
		return n, err
	}

	// check the png signature.
	if !bytes.HasPrefix(memory.Bytes(), []byte(pngSign)) {
		return n, errors.New("not png")
	}

	// stitch the PNG signature (memory) and the response body reader together.
	// then copy them successively to the writer (*os.File).
	return io.Copy(w, io.MultiReader(&memory, r))
}

View on GitHub (pinned to 3c475a78e5)

Solutions

  1. Verify the input source is actually a PNG (open it locally or check magic bytes before calling).
  2. Check the URL/endpoint returned 200 with image/png content, not an error page.
  3. If other formats should be accepted, remove or broaden the signature check in transfer.
  4. Handle the error at the caller and surface a clear 'input is not a PNG' message to the user.

Example fix

// before
if !bytes.HasPrefix(memory.Bytes(), []byte(pngSign)) {
    return n, errors.New("not png")
}
// after
if !bytes.HasPrefix(memory.Bytes(), []byte(pngSign)) {
    return n, fmt.Errorf("not png: got header %q", memory.Bytes()[:n])
}
Defensive patterns

Strategy: validation

Validate before calling

func looksLikePNG(r io.Reader) (bool, error) {
    header := make([]byte, 8)
    if _, err := io.ReadFull(r, header); err != nil {
        return false, err
    }
    return bytes.Equal(header, []byte("\x89PNG\r\n\x1a\n")), nil
}

Type guard

func isPNGHeader(b []byte) bool {
    return len(b) >= 8 && bytes.Equal(b[:8], []byte("\x89PNG\r\n\x1a\n"))
}

Try / catch

if n, err := transfer(w, r); err != nil {
    if err.Error() == "not png" {
        http.Error(w, "input is not a PNG image", http.StatusUnsupportedMediaType)
        return
    }
    return n, err
}

Prevention

When it happens

Trigger: Calling transfer (via main) with a reader whose first bytes are not the 8-byte PNG signature — e.g. a JPEG/GIF file, an HTML error page, an empty body, or a text file.

Common situations: Downloading an image URL that returned a 404 HTML page instead of a PNG; piping the wrong local file; a server content-type mismatch where the extension says .png but the payload is another format.

Related errors


AI-assisted analysis of inancgumus/learngo@3c475a78e5 (2026-09-02). Data as JSON: /api/errors/407a48fbcc037f45. Report an issue: GitHub.