🔤 Encoding / Decoding¶
When data crosses the barrier between server and client (HTML, URL, JSON, header, body multipart), you need to encode or decode special characters. If you do it wrong:
- A user's
<script>ends up executing in another browser → XSS. - An
&in a URL breaks the query string. - A
ñincorrectly encoded shows up asñon the client. - A binary without base64 corrupts the JSON.
HIX exposes helpers for each typical case and reuses standard Harbour functions where it makes sense.
HTML¶
UHtmlEncode( cText )¶
Escapes the 5 dangerous characters: & < > " '.
UHtmlEncode( '<script>alert("x")</script>' )
// → "<script>alert("x")</script>"
UHtmlEncode( "Pérez & Sons" )
// → "Pérez & Sons" (& → &)
When to use it¶
Always when you insert user input inside HTML:
LOCAL cName := UPost( "name", "" )
// BAD - user can inject <script>
USendHtml( "<h1>Hello " + cName + "</h1>" )
// GOOD - escaped
USendHtml( "<h1>Hello " + UHtmlEncode( cName ) + "</h1>" )
In
.view.htmltemplates, the engine does not escape automatically - the{{ expr }}rule evaluates the expression and prints it as-is. If what you put in comes from the user, callUHtmlEncodeinside the expression:{{ UHtmlEncode( cName ) }}.
Characters allowed without escaping¶
- Letters and digits.
- Spaces, normal punctuation (
. , ; : ! ?). - Accented characters (
ñ á é í ó ú) - as long as the page is in UTF-8.
The inverse - decoding HTML entities¶
There is no specific helper in HIX. Standard Harbour can do it with
hb_StrReplace by inverting the hash, but you shouldn't need to -
if your DB stores plain text, already-escaped entities won't appear.
URL¶
To encode values in query strings or paths, use Harbour's functions:
hb_URLEncode( "Pérez & Sons" )
// → "P%C3%A9rez%20%26%20Sons"
hb_URLDecode( "P%C3%A9rez%20%26%20Sons" )
// → "Pérez & Sons"
When to use it¶
When you build URLs with dynamic data:
HIX automatically decodes
UGet()/UPost()/UParam()- there's nothing extra to do when reading.
JSON¶
Encode → string¶
Converts hash/array/scalar to a valid JSON string. This is what
USendJson uses internally:
USendJson( { "ok" => .T., "user" => "Carles" } )
// equivalent to:
USetMime( "json" )
UWrite( hb_jsonEncode( { "ok" => .T., "user" => "Carles" } ) )
Supported types¶
| Harbour | JSON |
|---|---|
Hash {=>} |
object {...} |
Array {} |
array [...] |
| String | "..." |
| Number | number |
.T. / .F. |
true / false |
NIL |
null |
| Date | "YYYY-MM-DDTHH:MM:SSZ" ISO 8601 |
Decode → hash/array¶
Returns how many characters it consumed (0 if it failed). More practical from an action:
FUNCTION _ApiCreate()
LOCAL hBody := UJson()
IF hBody == NIL
RETURN USendError( 400, "Body is not JSON" )
ENDIF
USendJson( { "received" => hBody } )
RETURN NIL
UTF-8¶
Explicit conversion¶
| Harbour function | What it does |
|---|---|
hb_StrToUtf8( c ) |
Converts from local codepage → UTF-8 |
hb_Utf8ToStr( c ) |
Converts UTF-8 → local codepage |
hb_cdpSelect( "UTF8" ) |
Changes process codepage |
LOCAL cTexto := "Niño" // local codepage (CP1252 / ISO-8859-1)
LOCAL cUtf8 := hb_StrToUtf8( cTexto ) // "Ni\xc3\xb1o"
When it matters¶
- HIX works in UTF-8 by default - strings that come from the HTTP body, URLs, headers, JSON, are already in UTF-8.
- If you read data from an old DBF with local codepage (CP850, CP1252),
convert it with
hb_StrToUtf8before putting it in the response. UDbf:Row( lToStringWeb := .T. )already does the conversion to UTF-8 if you passlToUtf8in the constructor. See UDbf.
LOCAL oDbf := UDbf():New( "customers", .T., .T. ) // lToUtf8=.T.
oDbf:Open()
USendJson( oDbf:Row( .T. ) ) // already comes in UTF-8
Base64¶
For binary data inside JSON, URL or signed cookies:
Example - image embed in JSON¶
LOCAL cBytes := hb_MemoRead( "logo.png" )
USendJson( { ;
"name" => "logo.png", ;
"mime" => "image/png", ;
"data_b64" => hb_Base64Encode( cBytes ) ;
} )
Example - signed cookie¶
LOCAL cPayload := hb_jsonEncode( { "uid" => 42, "exp" => _Now() + 3600 } )
LOCAL cSig := hb_HMAC( cPayload, "secret-key" )
LOCAL cToken := hb_Base64Encode( cPayload + "." + cSig )
USetCookie( "auth", cToken, 3600 )
URL-safe Base64¶
For tokens in URLs, without + / =:
LOCAL cToken := hb_Base64Encode( cBin )
// Convert to URL-safe
cToken := StrTran( cToken, "+", "-" )
cToken := StrTran( cToken, "/", "_" )
cToken := StrTran( cToken, "=", "" ) // no padding
This is the convention used by JWT. To reverse it, undo the StrTrans and
re-add padding until a multiple of 4.
Quick comparison¶
| Case | Use |
|---|---|
| Insert text into HTML | UHtmlEncode |
| Build URL with data | hb_URLEncode |
| Respond with JSON | USendJson (calls hb_jsonEncode) |
| Read JSON from body | UJson() |
| Text to UTF-8 | hb_StrToUtf8 |
| Binary in ASCII string | hb_Base64Encode |
| Hash signature | hb_HMAC / hb_MD5 / hb_SHA256 |
Common errors¶
| Symptom | Cause |
|---|---|
ñ in the browser |
The server sent CP1252 but the page declares charset=utf-8 |
<script> executes from user input |
You didn't call UHtmlEncode before inlining |
%20 appears literal in a URL |
It was encoded twice (hb_URLEncode on already-encoded data) |
JSON with "\u00f1" instead of ñ |
Incorrect client decoding - JSON with \uXXXX is valid and equivalent |
| Corrupted binary cookie | You forgot hb_Base64Encode - the cookie does not accept raw \0 or ; |
Best practices¶
- Escape at output, not at input. Store the raw text in the DB. Only escape when you build the HTML / URL / JSON output.
- Don't mix encodings.
- Don't reinvent escapes.
UHtmlEncodecovers the 5 standard OWASP characters - don't add more by hand. - JSON is already safe.
hb_jsonEncodeescapes correctly - you don't needUHtmlEncodeon data that's already going through JSON. - Base64 ≠ encryption. Base64 only changes the representation; any attacker can decode it. For secrets, encrypt for real.