How to Fix Double-Encoded Cyrillic URLs in WordPress (Viber + iOS Bug)

How to Fix Double-Encoded Cyrillic URLs in WordPress (Viber + iOS Bug)

TLDR

If you have WordPress + Cyrillic in URLs + users opening links via Viber on iOS → broken links, then:

  • Detect iOS user-agent
  • Look for %25 in REQUEST_URI
  • Decode twice
  • Redirect once to the decoded version

You probably saw it: you send someone a WordPress link with Cyrillic characters, they open it from Viber on an iPhone or iPad, and… the URL breaks. It has extra %25… parts. Basically, it’s double encoded. Frustrating, but there’s a neat fix.

I ran into this recently. Here’s what I discovered + how to patch it.

What’s going on ?

  • When a link has Cyrillic (or other non-ASCII) characters, it is URL-encoded once, converting each character into %D0… etc.
  • Viber on iOS seems to encode again, so % becomes %25, turning %D0%BF… into %25D0%25B….
  • WordPress doesn’t recognize that double-encoded string, so you get 404s or “not found” errors.

Why only on iOS / Viber ?

  • Android / many browsers handle the URL just once: encode → decode correctly.
  • iOS + Viber seems to introduce this extra layer.
  • The user-agent on the request offers a way to detect it (iPhone / iPad).

The solution: Normalize the request URI

I found out a small function in a Stack Overflow post comment by Bobby Iliev that does exactly what I needed.

  1. Detect if the visitor is on iPhone or iPad (via $_SERVER['HTTP_USER_AGENT']).
  2. Check if the request URI has %25 → a sign of double encoding.
  3. If so, decode it twice to get back the correct Cyrillic path.
  4. Redirect the request (301) to that decoded URI. Add a redirected=1 parameter to avoid infinite loops.
  5. Here’s the code:
add_action('init', 'normalize_request_uri_for_ios');

function normalize_request_uri_for_ios() {
    $user_agent = $_SERVER['HTTP_USER_AGENT'];
    
    // Check if the user is on an iPhone or iPad
    if ((strpos($user_agent, 'iPhone') !== false || strpos($user_agent, 'iPad') !== false)) {
        // Only process the URL if it contains '%25', indicating double encoding
        if (strpos($_SERVER['REQUEST_URI'], '%25') !== false) {
            // Decode the URL twice to fix the double-encoding issue
            $decoded_uri = urldecode(urldecode($_SERVER['REQUEST_URI']));

            // Prevent infinite redirects by checking if the URL is already decoded
            if ($_SERVER['REQUEST_URI'] !== $decoded_uri && !isset($_GET['redirected'])) {
                // Add a query parameter to prevent repeated redirects
                $new_uri = add_query_arg('redirected', '1', $decoded_uri);
                wp_safe_redirect($new_uri, 301);
                exit;
            }
        }
    }
}

Step-by-step: How to apply this in your WordPress site

  1. Open your theme’s functions.php, or better yet, a site-specific plugin.
  2. Paste the function above.
  3. Make sure it runs early enough—init is good. Don’t hook it too late.
  4. Test: send a Cyrillic link (for example /привет) via Viber → open on iPhone. It should redirect automatically to proper form.
  5. Check also that other functionality isn’t broken (query params, POST requests, etc.).

What to watch out for ?

  • Redirect loops: That’s why the redirected=1 flag is important. Without it, you might bounce around.
  • SSL / domain issues: If you have canonical redirects or HTTPS forced, this redirect should be compatible.
  • Caching / rewrites: Some caching layers or URL rewrite rules might conflict. Test with caching off first.
  • User-agents spoofed or missing: If for whatever reason HTTP_USER_AGENT is absent or manipulated, detection might fail. But in most normal cases, it works.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *