URL Encode / Decode — Go source
Percent-encode or decode URLs and query parameters. Choose component (encodeURIComponent) or full-URI (encodeURI) mode. 100% client-side.
This is the Go implementation — the same logic the interactive tool runs, in a shareable, citable form.
// URL encode / decode — component-level (encodeURIComponent) and full URI (encodeURI).
//
// Language: Go
// CosmoDev polyglot showcase port of the `url-encode` tool.
// Ported from src/tools/UrlEncodeTool.tsx — display source, part of CosmoDev's
// polyglot tool pages.
//
// Explicit percent-encoding matching JavaScript's encodeURIComponent/encodeURI.
// Go strings are UTF-8 byte sequences, so we walk raw bytes: each byte outside
// the safe set becomes %XX (uppercase hex). Component scope leaves
// A-Za-z0-9-_.!~*'() unescaped; full URI scope additionally leaves the
// reserved set ;,/?:@&=+$# unescaped. Decode reverses this, returning an
// error on malformed % sequences; full URI leaves encoded reserved bytes
// intact. UTF-8 validity falls out of converting the decoded byte slice back
// to a string. net/url's QueryEscape uses form-encoding (+ for space), so it
// is not a match — this is hand-rolled for exact parity.
package main
import (
"errors"
"fmt"
"strings"
)
var errMalformed = errors.New("malformed URI sequence")
func isComponentSafe(c byte) bool {
switch {
case 'A' <= c && c <= 'Z', 'a' <= c && c <= 'z', '0' <= c && c <= '9':
return true
}
return strings.IndexByte("-_.!~*'()", c) >= 0
}
func isURIExtra(c byte) bool {
return strings.IndexByte(";,/?:@&=+$#", c) >= 0
}
const hexDigits = "0123456789ABCDEF"
// Encode percent-encodes s. When fullURI is true it matches encodeURI;
// otherwise it matches encodeURIComponent.
func Encode(s string, fullURI bool) string {
var b strings.Builder
for i := 0; i < len(s); i++ {
c := s[i]
if isComponentSafe(c) || (fullURI && isURIExtra(c)) {
b.WriteByte(c)
continue
}
b.WriteByte('%')
b.WriteByte(hexDigits[c>>4])
b.WriteByte(hexDigits[c&0x0f])
}
return b.String()
}
func unhex(c byte) (byte, bool) {
switch {
case '0' <= c && c <= '9':
return c - '0', true
case 'a' <= c && c <= 'f':
return c - 'a' + 10, true
case 'A' <= c && c <= 'F':
return c - 'A' + 10, true
}
return 0, false
}
// unhexPair reads the two hex digits after the '%' at index i.
func unhexPair(s string, i int) (byte, error) {
if i+2 >= len(s) {
return 0, errMalformed
}
hi, ok1 := unhex(s[i+1])
lo, ok2 := unhex(s[i+2])
if !ok1 || !ok2 {
return 0, errMalformed
}
return hi<<4 | lo, nil
}
// Decode reverses percent-encoding. When fullURI is true it matches decodeURI
// (encoded reserved characters are preserved); otherwise decodeURIComponent.
func Decode(s string, fullURI bool) (string, error) {
var out []byte
i := 0
for i < len(s) {
if s[i] != '%' {
out = append(out, s[i])
i++
continue
}
b, err := unhexPair(s, i)
if err != nil {
return "", err
}
if b < 0x80 {
if fullURI && strings.IndexByte(";/?:@&=+$#", b) >= 0 {
out = append(out, s[i], s[i+1], s[i+2]) // keep encoded reserved byte
} else {
out = append(out, b)
}
i += 3
continue
}
var seqLen int
switch {
case b&0xe0 == 0xc0:
seqLen = 2
case b&0xf0 == 0xe0:
seqLen = 3
case b&0xf8 == 0xf0:
seqLen = 4
default:
return "", errMalformed
}
buf := make([]byte, 0, seqLen)
buf = append(buf, b)
for n := 1; n < seqLen; n++ {
pi := i + 3*n
if pi >= len(s) || s[pi] != '%' {
return "", errMalformed
}
cb, err := unhexPair(s, pi)
if err != nil {
return "", err
}
if cb&0xc0 != 0x80 {
return "", errMalformed
}
buf = append(buf, cb)
}
out = append(out, buf...)
i += 3 * seqLen
}
return string(out), nil
}
func main() {
fmt.Println(Encode("hello world & café", false))
out, err := Decode("hello%20world%20%26%20caf%C3%A9", false)
fmt.Println(out, err)
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →