SHA1Generator

URL Encoding Explained: RFC 3986, Percent-Encoding, and Web Security

SHA1Generator Team
9 min read
URL EncodingWeb SecurityRFC 3986HTTPCybersecurity

Every time you click a link, submit an API query, or redirect a user after login, URL encoding works quietly in the background. Yet misunderstandings surrounding RFC 3986, the difference between %20 and +, and inconsistent server decoding cause recurring bugs, broken integrations, and dangerous security vulnerabilities like double-encoding bypasses and path traversal. Here is how percent-encoding actually works and how to handle it safely in modern architectures.

Why Do URLs Need Encoding in the First Place?

The Uniform Resource Identifier (URI) specification was standardized when the Internet moved across heterogeneous networks using strictly 7-bit US-ASCII channels. Under this design, a URL cannot contain arbitrary binary data, control characters (like line breaks), or non-ASCII glyphs such as Japanese kanji, Arabic script, or accented Latin letters.

More fundamentally, URLs have a strict syntax. Characters like /, ?, #, &, and = serve as architectural delimiters. They tell the browser, proxy, and web server where the protocol ends, where the host begins, where the path segments divide, where query parameters start, and where fragment identifiers anchor.

https://api.example.com/search?filter=c++&sort=asc

Without encoding, the parser treats + as spaces or delimiters rather than the literal programming language name "C++". Even worse, if a search query contains &admin=true, an unencoded string changes the parameter structure entirely.

URL encoding (formally termed percent-encoding) solves this identity problem: it converts any character into a standardized, unambiguous ASCII representation that parsers can transmit without mistaking payload data for structural syntax.

The Anatomy of RFC 3986: Reserved vs. Unreserved Characters

The governing standard for modern URLs is IETF RFC 3986. It partitions all possible characters into two distinct classes:

Unreserved Characters

These characters have no syntactic meaning in URI structures. They never require encoding and must never be altered during transmission:

A-Z, a-z, 0-9, -, _, ., ~

Total: 66 characters (alphanumerics, hyphen, underscore, period, and tilde).

Reserved Characters

These characters serve as structural delimiters. If you intend to use them as literal text within a value, you must percent-encode them:

: / ? # [ ] @ ! $ & ' ( ) * + , ; =

Subdivided into generic delimiters (like : / ? #) and sub-delimiters (like & = + $ ,).

How Percent-Encoding Works Step-by-Step

The translation rule is straightforward:

  1. Take the character and determine its binary byte sequence in UTF-8.
  2. Convert each byte into its two-digit hexadecimal representation.
  3. Prepend a percent sign (%) to each byte.

For standard 1-byte ASCII characters, this produces a single triplet:

Literal CharacterASCII DecimalHexadecimalPercent-Encoded Value
Space ( )320x20%20
Exclamation mark (!)330x21%21
Ampersand (&)380x26%26
Forward slash (/)470x2F%2F
Equals sign (=)610x3D%3D

What About Multi-Byte Unicode (Emojis and International Scripts)?

When dealing with characters beyond standard ASCII, modern systems encode the text using UTF-8 first. Since UTF-8 uses between 2 and 4 bytes per character, each byte receives its own percent triplet.

Take the euro symbol (€). In UTF-8, its byte sequence is 0xE2 0x82 0xAC. When percent-encoded, it becomes %E2%82%AC. Similarly, the rocket emoji (🚀), which consists of four bytes (0xF0 0x9F 0x9A 0x80), becomes %F0%9F%9A%80.

The Great Confusion: %20 vs. + (Plus Sign)

One of the most persistent sources of confusion in web engineering is seeing spaces encoded as + in some URLs and %20 in others. They are not interchangeable standards:

RFC 3986 (Universal URI Standard)

Spaces must always be encoded as %20. In RFC 3986, the plus sign + is a valid sub-delimiter with literal meaning.

HTML Form Submissions (application/x-www-form-urlencoded)

Originating from early web browsers submitting HTML forms via GET or POST, spaces are historically encoded as +, while a literal plus is encoded as %2B.

Where bugs bite: If your backend expects a path segment like /users/john+doe, an RFC 3986 parser views that as a user literally named "john+doe". If an application decodes it using a form-urlencoded decoder, the plus vanishes into a space ("john doe"), causing 404 lookups and broken authentication tokens.

JavaScript API Pitfalls: encodeURI vs. encodeURIComponent

JavaScript provides two built-in global functions for encoding. Using the wrong one is one of the most common mistakes in frontend and Node.js development:

FunctionIntended ScopeCharacters Left Unencoded
encodeURI()Complete URLs with protocol, domain, and pathA-Z a-z 0-9 ; , / ? : @ & = + $ - _ . ! ~ * ' ( ) #
encodeURIComponent()Single parameter values or path segmentsA-Z a-z 0-9 - _ . ! ~ * ' ( )

Look at what happens when you use encodeURI() on a query parameter value:

// THE WRONG WAY: encodeURI leaves delimiters unencoded
const query = 'shoes & shirts';
const badUrl = 'https://store.com/search?q=' + encodeURI(query);
// Output: https://store.com/search?q=shoes%20&shirts
// Result: Server sees parameter "q" = "shoes ", and a second parameter "shirts" = undefined!

// THE BETTER WAY: encodeURIComponent encodes the ampersand
const goodUrl = 'https://store.com/search?q=' + encodeURIComponent(query);
// Output: https://store.com/search?q=shoes%20%26%20shirts
// Result: Server correctly receives q = "shoes & shirts"

The Modern Best Practice: URL and URLSearchParams

In modern environments (browsers and Node.js 18+), avoid manual string concatenation altogether. Use the WHATWG standard URL and URLSearchParams classes. They handle RFC-compliant percent-encoding automatically without manual escapes:

const url = new URL('https://api.example.com/v1/items');
url.searchParams.set('category', 'Laptops & Hardware');
url.searchParams.set('tag', 'c++');
url.searchParams.set('page', '1');

console.log(url.toString());
// Output: https://api.example.com/v1/items?category=Laptops+%26+Hardware&tag=c%2B%2B&page=1

Web Security Vulnerabilities Linked to URL Encoding

URL encoding is not an encryption mechanism—it provides zero confidentiality. Any observer can reverse percent-encoding instantly. In fact, improper URL decoding routines on servers are among the most frequent culprits behind high-severity web exploits:

1. Double Encoding Attacks (WAF & Filter Evasion)

Security filters, Web Application Firewalls (WAFs), and reverse proxies inspect incoming requests to block malicious strings like ../ (directory traversal) or <script> (Cross-Site Scripting).

In a double encoding attack, the attacker encodes the percent sign itself (%25).

1. Raw Traversal Sequence: ../
2. Single Percent-Encoded: %2e%2e%2f
3. Double Percent-Encoded: %252e%252e%252f

When the request reaches a WAF, the firewall decodes %252e%252e%252f once into %2e%2e%2f. Because %2e%2e%2f does not contain literal dots or slashes, the WAF allows it through. Then, the application framework (or file-system router) decodes the parameter a second time, turning it into ../ and exposing internal files like /etc/passwd.

2. HTTP Parameter Pollution (HPP)

When an application takes user input and concatenates it into an outgoing backend API request without encoding delimiters, attackers inject additional query parameters:

Input: user123%26role%3Dadmin --> Decoded to: user123&role=admin

If the internal service trusts the query string, the user suddenly gains unauthorized administrative privileges.

3. Proxy vs. Origin Normalization Inconsistencies

Modern cloud infrastructures route traffic through CDNs (Cloudflare, AWS CloudFront) and reverse proxies (Nginx, Envoy) before reaching application containers (Spring Boot, Express, Next.js).

If Nginx decodes %2f (forward slash) before evaluating its access control rules, while your backend microservice evaluates the raw encoded string, an attacker can bypass path-based access controls like /admin/ by requesting /ad%2fmin/ or /%2e/admin.

The Defensive Developer Checklist: 4 Rules for Safe URL Processing

Rule 1: Decode Exactly Once at the Input Boundary

Never call decoding routines inside nested utility functions. Web frameworks (Express, Spring, ASP.NET) automatically decode query parameters and path variables once. If you run decodeURIComponent() again on already-parsed parameters, you open the door to double-encoding vulnerabilities.

Rule 2: Validate and Sanitize After Decoding, Never Before

If you check for disallowed characters (like <script> or ../) on the raw encoded URL before decoding, an attacker will bypass your validation using character variants. Always canonicalize and decode first, then enforce strict validation against an allowlist.

Rule 3: Enforce Consistent Proxy Normalization

Ensure your reverse proxies (e.g., Nginx, Traefik) and application runtimes share identical URL parsing semantics. In Nginx, use proxy_pass http://backend; without URI paths if you want to forward the exact raw request URI to the upstream service without unintended decodes.

Rule 4: Differentiate URL Encoding from HTML Escaping

Percent-encoding ensures safe transport over HTTP; it does not prevent Cross-Site Scripting (XSS). If a query parameter is reflected back into an HTML page, it must also undergo contextual HTML entity encoding or pass through a safe rendering engine.

Need to Encode or Decode URLs Instantly?

Use our free, client-side online utility to encode strings to RFC 3986 format or inspect percent-encoded values safely without sending data to an external server.

Open URL Encoder/Decoder

Frequently Asked Questions

Is URL encoding a type of encryption?

No. URL encoding is a public, deterministic representation scheme designed strictly for protocol interoperability. Anyone can decode it with zero knowledge or secret keys. If you need confidentiality, use TLS/HTTPS in transit and cryptographic encryption like AES or hashing algorithms like SHA-256 for data integrity.

What is the difference between URL encoding and Base64 encoding?

While both convert data into ASCII-safe strings, their targets differ. URL encoding uses percent triplets (%XX) to protect specific URI delimiters. Base64 encoding converts arbitrary 8-bit binary streams (like images, keys, or files) into a 64-character alphabet. In fact, standard Base64 contains characters like +, /, and =, which must themselves be URL-encoded (or replaced with URL-safe Base64) when passed inside query parameters.

Why does my API reject requests with %2F in path parameters?

Many web servers (such as Apache with AllowEncodedSlashes Off) deliberately block requests containing %2F in path segments by default. This is an intentional security safeguard against directory traversal attacks, as early web servers frequently misparsed encoded slashes when verifying file paths.

Which characters never need to be encoded in a URL?

Under RFC 3986, the unreserved set never requires encoding anywhere in a URL: uppercase letters (A-Z), lowercase letters (a-z), digits (0-9), hyphen (-), underscore (_), period (.), and tilde (~).

Summary and Key Takeaways

URL encoding is fundamental to how data moves across the web, yet treating it as a trivial string replacement is where software vulnerabilities begin. By remembering three core principles:

  • Always rely on built-in URI builders like URLSearchParams rather than string concatenation.
  • Respect the boundary between RFC 3986 path percent-encoding and form-urlencoded + spaces.
  • Decode once at ingress, and sanitize input only after it has reached its final canonical state.

You will keep your API integrations reliable and eliminate an entire category of path traversal, injection, and WAF bypass risks.

Related Technical Guides