golang/go · error

gzip.Write: non-Latin-1 header string

Error message

gzip.Write: non-Latin-1 header string

What it means

gzip.Writer.writeString rejects any string containing a NUL byte (0x00) or a rune above 0xff because GZIP header strings (FNAME, FCOMMENT) are NUL-terminated ISO 8859-1 Latin-1 per RFC 1952. A NUL would terminate the string early and a non-Latin-1 rune cannot be represented.

Source

Thrown at src/compress/gzip/gzip.go:121

		return errors.New("gzip.Write: Extra data is too large")
	}
	le.PutUint16(z.buf[:2], uint16(len(b)))
	_, err := z.w.Write(z.buf[:2])
	if err != nil {
		return err
	}
	_, err = z.w.Write(b)
	return err
}

// writeString writes a UTF-8 string s in GZIP's format to z.w.
// GZIP (RFC 1952) specifies that strings are NUL-terminated ISO 8859-1 (Latin-1).
func (z *Writer) writeString(s string) (err error) {
	// GZIP stores Latin-1 strings; error if non-Latin-1; convert if non-ASCII.
	needconv := false
	for _, v := range s {
		if v == 0 || v > 0xff {
			return errors.New("gzip.Write: non-Latin-1 header string")
		}
		if v > 0x7f {
			needconv = true
		}
	}
	if needconv {
		b := make([]byte, 0, len(s))
		for _, v := range s {
			b = append(b, byte(v))
		}
		_, err = z.w.Write(b)
	} else {
		_, err = io.WriteString(z.w, s)
	}
	if err != nil {
		return err
	}
	// GZIP strings are NUL-terminated.

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Sanitize Header.Name and Header.Comment: drop or replace runes > 0xFF and any NUL.
  2. Encode the original filename with percent-encoding or base32 so it stays inside the ASCII subset of Latin-1.
  3. Store the full Unicode filename in a sidecar metadata file instead of the GZIP header.
  4. If you must preserve Unicode, transcode the string to ISO 8859-1 only when every rune is <= 0xFF; otherwise reject upfront.

Example fix

// before
gz.Header.Name = path.Base(userSuppliedFilename) // may contain emoji

// after: keep only Latin-1, substitute everything else
gz.Header.Name = sanitizeLatin1(userSuppliedFilename)

func sanitizeLatin1(s string) string {
    var b strings.Builder
    for _, r := range s {
        if r == 0 || r > 0xff { r = '_' }
        b.WriteRune(r)
    }
    return b.String()
}
Defensive patterns

Strategy: validation

Validate before calling

func latin1Safe(s string) (string, error) {
    for _, r := range s {
        if r == 0 || r > 0xff {
            return "", fmt.Errorf("rune U+%04X not representable in Latin-1", r)
        }
    }
    return s, nil
}

func setGzipName(h *gzip.Header, name string) error {
    safe, err := latin1Safe(name)
    if err != nil { return err }
    h.Name = safe
    return nil
}

Try / catch

if err := setGzipName(&gz.Header, userFilename); err != nil {
    // Fall back to an ASCII slug or store filename out-of-band.
    gz.Header.Name = asciiSlug(userFilename)
}

Prevention

When it happens

Trigger: Setting gz.Header.Name or gz.Header.Comment to a value containing a NUL byte or any rune above U+00FF (e.g., emoji, CJK characters, accented Cyrillic, right-to-left scripts), then calling Write/Flush/Close.

Common situations: Storing user-supplied UTF-8 filenames (Android downloads with CJK names, macOS NFD-decomposed accents) in the GZIP FNAME field; copying a Comment from a JSON or YAML manifest that contains em-dashes, smart quotes, or emoji.

Related errors


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/056bfc1ce0df916f. Report an issue: GitHub.