cgi

package module
v0.0.0-...-adf3505 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 17, 2026 License: BSD-3-Clause Imports: 3 Imported by: 0

README

go-ruby-cgi/cgi

cgi — go-ruby-cgi

Docs License Go Coverage

A pure-Go (no cgo) reimplementation of the escaping and query-parsing surface of Ruby's CGI utility methods — the deterministic, interpreter-independent core of MRI 4.0.5's CGI.escape / CGI.unescape, the HTML-entity helpers, the URI-component helpers, the element helpers and CGI.parse. Every method matches the system ruby byte-for-byte, without any Ruby runtime.

It is the CGI backend for go-embedded-ruby, but is a standalone, reusable module with no dependency on the Ruby runtime — a sibling of go-ruby-yaml (the Psych emitter/loader), go-ruby-regexp (the Onigmo engine) and go-ruby-erb (the ERB compiler).

What it is — and isn't. URL/form escaping, HTML-entity coding and query parsing are pure, deterministic string transforms that need no interpreter, so they live here as pure Go. The request/response cycle, cookies, multipart form handling and the HTML-generation DSL — everything that needs a live server or CGI environment — is out of scope; this library is the compute core only.

Ruby 4.0 note. In MRI 4.0 the cgi library was slimmed to cgi/escape: CGI.escape, CGI.unescape, CGI.escapeHTML, CGI.unescapeHTML, CGI.escapeURIComponent, CGI.unescapeURIComponent, CGI.escapeElement and CGI.unescapeElement are the default surface, while CGI.parse now ships in the separate cgi gem. This package provides all of them (ParseQuery for CGI.parse).

Features

Faithful port of the CGI utility methods, validated against the ruby binary on every platform that has one:

  • Escape / Unescapeapplication/x-www-form-urlencoded: every byte outside the unreserved set A–Z a–z 0–9 - . _ ~ is percent-encoded (uppercase hex), a space becomes +, and Unescape decodes +→space and %XX, leaving malformed escapes verbatim (it never raises).
  • EscapeURIComponent / UnescapeURIComponent — like the above but a space is %20 (and Unescape does not treat + as a space).
  • EscapeHTML / UnescapeHTML — encodes & < > " ' (the apostrophe as &#39;); decoding recognises those five names plus &apos;, decimal &#NN; and hex &#xHH; / &#XHH; numeric entities (emitting the raw UTF-8 bytes of the code point, surrogates included, exactly as MRI does), and rejects unknown or overflowing entities verbatim.
  • EscapeElement / UnescapeElement — HTML-(un)escape only the start/end tags of named elements, matching the element name case-insensitively at a word boundary.
  • ParseQueryCGI.parse: split on & and ;, form-decode keys and values, accumulate repeated keys, and return an empty slice for a bare key.

CGO-free, dependency-free, 100% test coverage, gofmt + go vet clean, and green across the six 64-bit Go targets (amd64, arm64, riscv64, loong64, ppc64le, s390x) and three operating systems (Linux, macOS, Windows).

Install

go get github.com/go-ruby-cgi/cgi

Usage

package main

import (
	"fmt"

	"github.com/go-ruby-cgi/cgi"
)

func main() {
	fmt.Println(cgi.Escape("a b&c"))                 // a+b%26c
	fmt.Println(cgi.Unescape("a+b%26c"))             // a b&c
	fmt.Println(cgi.EscapeURIComponent("a b"))       // a%20b
	fmt.Println(cgi.EscapeHTML(`<a href="x">&'`))    // &lt;a href=&quot;x&quot;&gt;&amp;&#39;
	fmt.Println(cgi.UnescapeHTML("&#9731; &amp;"))   // ☃ &
	fmt.Println(cgi.ParseQuery("a=1&b=2&a=3"))       // map[a:[1 3] b:[2]]
}

API

// Form (application/x-www-form-urlencoded) encoding — CGI.escape / CGI.unescape.
func Escape(s string) string
func Unescape(s string) string

// URI-component encoding — CGI.escapeURIComponent / CGI.unescapeURIComponent.
func EscapeURIComponent(s string) string
func UnescapeURIComponent(s string) string

// HTML-entity coding — CGI.escapeHTML / CGI.unescapeHTML.
func EscapeHTML(s string) string
func UnescapeHTML(s string) string

// Element-tag (un)escaping — CGI.escapeElement / CGI.unescapeElement.
func EscapeElement(s string, elements ...string) string
func UnescapeElement(s string, elements ...string) string

// Query parsing — CGI.parse.
func ParseQuery(query string) map[string][]string

Tests & coverage

The suite pairs deterministic, ruby-free golden tests — which alone hold coverage at 100%, so the qemu cross-arch and Windows lanes pass the gate — with a differential MRI oracle: a corpus is run through the system ruby (CGI.escape, CGI.unescapeHTML, CGI.parse, …) and compared byte-for-byte with this package. The oracle binmodes both stdin and stdout so Windows text-mode never rewrites a byte, gates on RUBY_VERSION >= "4.0", and skips itself where ruby is absent (and where the cgi gem is not installed, for the CGI.parse case).

COVERPKG=$(go list ./... | paste -sd, -)
go test -race -coverpkg="$COVERPKG" -coverprofile=cover.out ./...
go tool cover -func=cover.out | tail -1   # 100.0%

License

BSD-3-Clause — see LICENSE. Copyright the go-ruby-cgi/cgi authors.

WebAssembly

Being pure Go (CGO=0), this library also compiles to WebAssembly — both GOOS=js GOARCH=wasm (browser / Node.js) and GOOS=wasip1 GOARCH=wasm (WASI). CI builds both targets on every push, alongside the six 64-bit native/qemu arches.

GOOS=js     GOARCH=wasm go build ./...   # browser / Node
GOOS=wasip1 GOARCH=wasm go build ./...   # WASI (wasmtime, wasmer, wasmedge, …)

Documentation

Overview

Package cgi is a pure-Go (no cgo) reimplementation of the deterministic escaping and query-parsing surface of Ruby's CGI utility methods, byte-for-byte compatible with MRI 4.0.5 (the cgi/escape default + the cgi gem's CGI.parse).

It is the CGI backend for go-embedded-ruby, but is a standalone, reusable module with no dependency on the Ruby runtime — a sibling of go-ruby-yaml, go-ruby-regexp and go-ruby-erb.

Only the pure-compute surface lives here: URL/form escaping (Escape / Unescape), the URI-component variants (EscapeURIComponent / UnescapeURIComponent), the HTML-entity helpers (EscapeHTML / UnescapeHTML, with numeric &#NN; / &#xHH; decoding), the element helpers (EscapeElement / UnescapeElement), and query parsing (ParseQuery). The request/response/session machinery that needs a live server is out of scope.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Escape

func Escape(s string) string

Escape implements Ruby's CGI.escape: application/x-www-form-urlencoded encoding. Every byte outside the unreserved set is percent-encoded, and a space becomes '+'.

CGI.escape("a b&c") # => "a+b%26c"

func EscapeElement

func EscapeElement(s string, elements ...string) string

EscapeElement implements Ruby's CGI.escapeElement(string, *elements). It HTML-escapes (per EscapeHTML) only the start and end tags of the named elements, leaving the rest of the string untouched. Element names are matched case-insensitively at a word boundary, so EscapeElement("<A><AB>", "A") only touches the "<A>". When no elements are given the string is returned verbatim.

EscapeElement(`<BR><A HREF="url"></A>`, "A")
  // => `<BR>&lt;A HREF=&quot;url&quot;&gt;&lt;/A&gt;`

func EscapeHTML

func EscapeHTML(s string) string

EscapeHTML implements Ruby's CGI.escapeHTML: it replaces '&', '<', '>', '"' and a single quote with their HTML entities (the single quote becoming "&#39;").

It mirrors the table-driven native path of MRI's cgi/escape C extension: a single pass over the bytes copies each verbatim run in bulk and splices in the entity for each escapable byte, straight into one output buffer. Inputs with nothing to escape return the original string with no allocation at all.

CGI.escapeHTML(`<a href="x">&'`) # => `&lt;a href=&quot;x&quot;&gt;&amp;&#39;`

func EscapeURIComponent

func EscapeURIComponent(s string) string

EscapeURIComponent implements Ruby's CGI.escapeURIComponent (added in the 3.5/4.0 era). It is like Escape but encodes a space as "%20" rather than '+'.

CGI.escapeURIComponent("a b") # => "a%20b"

func ParseQuery

func ParseQuery(query string) map[string][]string

ParseQuery implements Ruby's CGI.parse(query). The query string is split on '&' and ';'; each pair is split on its first '='; key and value are Unescape-d (form decoding, so '+' is a space and "%XX" is decoded). Repeated keys accumulate their values in order. A pair with no '=' (e.g. "k") yields an empty value slice for that key. Ruby's CGI does NOT special-case "[]" in keys — the brackets are part of the key name verbatim.

ParseQuery("a=1&b=2&a=3") // => map[string][]string{"a":{"1","3"}, "b":{"2"}}
ParseQuery("x[]=1&x[]=2") // => map[string][]string{"x[]":{"1","2"}}
ParseQuery("k")           // => map[string][]string{"k":{}}

func Unescape

func Unescape(s string) string

Unescape implements Ruby's CGI.unescape: it decodes "%XX" escapes and turns '+' into a space. Malformed escapes (a '%' not followed by two hex digits) are left exactly as-is — CGI.unescape never raises.

CGI.unescape("a+b%26c") # => "a b&c"
CGI.unescape("%zz%2")   # => "%zz%2"

func UnescapeElement

func UnescapeElement(s string, elements ...string) string

UnescapeElement implements Ruby's CGI.unescapeElement(string, *elements). It reverses EscapeElement: it HTML-unescapes (per UnescapeHTML) only the escaped start and end tags of the named elements, leaving the rest untouched. When no elements are given the string is returned verbatim.

UnescapeElement(EscapeHTML(`<BR><A HREF="url"></A>`), "A")
  // => `&lt;BR&gt;<A HREF="url"></A>`

It faithfully reproduces Ruby's regexp

/&lt;\/?(?:E…)\b(?>[^&]+|&(?![gl]t;)\w+;)*(?:&gt;)?/im

by scanning for a tag head, then consuming the tag body (runs of non-'&' text or entity references other than &gt;/&lt;), then an optional closing &gt;, and HTML-unescaping exactly that span.

func UnescapeHTML

func UnescapeHTML(s string) string

UnescapeHTML implements Ruby's CGI.unescapeHTML. It decodes the five named entities (amp, lt, gt, quot, apos), decimal numeric entities ("&#NN;") and hexadecimal numeric entities ("&#xHH;" / "&#XHH;"). A numeric code point is emitted as its raw UTF-8 byte sequence using Ruby's permissive encoder (so surrogate code points yield their three-byte form, as MRI does). Anything not recognised — an unknown name, an empty or overflowing number, a '&' without a terminating ';' — is left exactly as it appears.

Like MRI, the numeric decoding assumes a Unicode (UTF-8) receiver: it always produces the UTF-8 bytes of the code point.

The scan mirrors MRI's C decoder: it jumps '&' to '&' with IndexByte, copies each verbatim run in bulk, and decodes entities straight into the output buffer with no per-entity allocation (named entities resolve to a constant string, numeric ones write their UTF-8 bytes in place).

CGI.unescapeHTML("&amp;&#65;&#x42;") # => "&AB"

func UnescapeURIComponent

func UnescapeURIComponent(s string) string

UnescapeURIComponent implements Ruby's CGI.unescapeURIComponent. It is like Unescape but does NOT turn '+' into a space (only "%XX" is decoded).

CGI.unescapeURIComponent("a%20b+c") # => "a b+c"

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL