22

I was just testing something with AJAX and I found that on success if I alert

alert(decodeURI('%'));

or

alert(encodeURIComponent('%'));

the browser errors out with the following code.

$.ajax({
   type: "POST",
   url: "some.php",
   data: "",
   success: function(html){
         alert(decodeURIComponent('%'));
//           alert(decodeURI('%'));
   }
 });

If I use any other string it works just fine.
Is it something that I missed?

Ruan Mendes
  • 90,375
  • 31
  • 153
  • 217
CristiC
  • 22,068
  • 12
  • 57
  • 89

8 Answers8

27

Recently a decodeURIComponent in my code tripped over the ampersand % and googling led me to this question.

Here's the function I use to handle % which is shorter than the version of Ilia:

function decodeURIComponentSafe(s) {
    if (!s) {
        return s;
    }
    return decodeURIComponent(s.replace(/%(?![0-9][0-9a-fA-F]+)/g, '%25'));
}

It

  • returns the input value unchanged if input is empty
  • replaces every % NOT followed by a two-digit (hex) number with %25
  • returns the decoded string

It also works with the other samples around here:

  • decodeURIComponentSafe("%%20Visitors") // % Visitors
  • decodeURIComponentSafe("%Directory%20Name%") // %Directory Name%
  • decodeURIComponentSafe("%") // %
  • decodeURIComponentSafe("%1") // %1
  • decodeURIComponentSafe("%3F") // ?
Heinrich Ulbricht
  • 10,064
  • 4
  • 54
  • 85
  • 1
    This breaks on some characters (ie: %3F => %25EF instead of ?). Can you update your regex to allow hex numbers at least on the second character like this: **s.replace(/%(?![0-9][\da-f]+)/gi, '%25')** – BornReady Apr 08 '19 at 18:36
  • @BornReady I'm trying to understand your use case. What would be your input to `decodeURIComponentSafe` and what would be your expected result? – Heinrich Ulbricht Apr 19 '19 at 19:28
  • 3
    decodeURIComponentSafe('%3f'). Expected result:?. Actual result: %25ef – BornReady Apr 21 '19 at 14:42
  • 4
    Yeah, this doesn't take unicode into account: `encodeURIComponent("儒")` ends up as "%E5%84%92", which would fail to decode in this function. The regex `/%(?![0-9a-fA-F]+)/g` is better. – Perry Mitchell Oct 10 '21 at 11:08
18

Chrome barfs when trying from the console. It gives an URIError: URI malformed. The % is an escape character, it can't be on its own.

Ruan Mendes
  • 90,375
  • 31
  • 153
  • 217
  • 4
    How about Wikipedia? http://en.wikipedia.org/wiki/Percent_encoding **Percent-encoding the percent character** Because the percent ("%") character serves as the indicator for percent-encoded octets, it must be percent-encoded as "%25" for that octet to be used as data within a URI. – Ruan Mendes Sep 16 '11 at 19:51
  • Ok, but this doesn't needs to hang. Doesn't matter if it is an escape character or not. It should return %. Why urldecode is working for php for the same situation? – CristiC Sep 16 '11 at 20:09
  • @Parkyprg: What do you mean by 'hang'? The browser may stop JavaScript execution because of the `URIError`, but that's not the same as 'hanging'. – gen_Eric Sep 16 '11 at 20:14
  • 1
    PHP is more leniant in its parsing. According to the rules of encoding URIs, a single % is invalid and you should not use it. It assumes that %s that don't form an escape sequence should be treated as literals. I would not rely on this behavior, follow the standard. – Ruan Mendes Sep 16 '11 at 20:16
  • @Rocket: I'm sure he does not mean hang as in `while (true){}`, he just means it stops working. Maybe hangs at that line and doesn't execute the rest'. – Ruan Mendes Sep 16 '11 at 20:18
  • 1
    @Juan: That's what I thought, but "hang" and "lock up" are the wrong terms, so it kinda bugged me. – gen_Eric Sep 16 '11 at 20:23
  • try decodeURIComponent(encodeURIComponent(" % 1")), output = "% 1" – Prathamesh Talathi Nov 19 '19 at 06:10
9

The point is that if you use single % it breaks the logic of decodeURIComponent() function as it expects two-digit data-value followed right after it, for example %20 (space).

There is a hack around. We need to check first if the decodeURIComponent() actually can run on given string and if not return the string as it is.

Example:

function decodeURIComponentSafe(uri, mod) {
    var out = new String(),
        arr,
        i = 0,
        l,
        x;
    typeof mod === "undefined" ? mod = 0 : 0;
    arr = uri.split(/(%(?:d0|d1)%.{2})/);
    for (l = arr.length; i < l; i++) {
        try {
            x = decodeURIComponent(arr[i]);
        } catch (e) {
            x = mod ? arr[i].replace(/%(?!\d+)/g, '%25') : arr[i];
        }
        out += x;
    }
    return out;
}

Running:

decodeURIComponent("%Directory%20Name%")

will result in Uncaught URIError: URI malformed error

while:

decodeURIComponentSafe("%Directory%20Name%") // %Directory%20Name%

will return the initial string.

In case you would want to have a fixed/proper URI and have % turned into %25 you would have to pass 1 as additional parameter to the custom function:

decodeURIComponentSafe("%Directory%20Name%", 1) // "%25Directory%20Name%25"
Ilia Ross
  • 13,086
  • 11
  • 53
  • 88
3

Unfortunately some of the answers here failed to satisfy my code, so I made an alternative solution. If someone comes looking by with the same problem.

You can use a try...catch block to decode safely. If the string is decodable it will decode, if not, it returns the same string as it is already decoded.

function decodeURIComponentSafely(uri) {
    try {
        return decodeURIComponent(uri)
    } catch(e) {
        console.log('URI Component not decodable: ' + uri)
        return uri
    }
}
3

The problem here is you're trying to decode the %. This is not a valid encoded string. I think you want to encode the % instead.

decodeURI('%') // URIError
encodeURI('%') // '%25'
gen_Eric
  • 223,194
  • 41
  • 299
  • 337
1

Both decodeURI('%') and decodeURIcomponent('%') cannot work because the URL is malformed (a single % is not valid as a url or url component)

Uncaught URIError: URI malformed

encodeURIComponent() works

mit
  • 11,083
  • 11
  • 50
  • 74
genesis
  • 50,477
  • 20
  • 96
  • 125
0

I had the same issue as OP and found this useful topic. Had to find a way to check if URI string contained percent sign before using decodeURIComponent().

The piece of code from Ilia Rostovtsev works great except for URI which contains encoded characters like %C3 (where percent sign is starting by [A-F]) because the regex used doesn't handle them (only % followed by a decimal).

I replaced the following line:

   x = mod ? arr[i].replace(/%(?!\d+)/g, '%25') : arr[i];

by

   x = mod ? arr[i].replace(/%(?!\d|[ABCDEF]+)/g, '%25') : arr[i];

Now, it is working as the regex will reject %1A to %9F and also %A1 to %F9

JnK
  • 1
  • 1
-7

The endless-loop or lock up may be due to a bug in jquery.

You can set a breakpoint in jquery at a point which is likely causing the 'lock-up'.

Decode doesn't make sense with just % provided, as percent-encoding is followed by alphanumericals referring to a given character in the ASCII table, and should normally yield an URIError in Opera, Chrome, FF.

Use the browser built in function encodeURI if you are looking for the 'url-encoded' notation of the percent-character:

encodeURI('%')
//>"%25"
Lorenz Lo Sauer
  • 23,698
  • 16
  • 85
  • 87
  • 1
    This has nothing to do with jQuery. – Ivan Sep 16 '11 at 19:50
  • encode/decodeURI and encode/decodeURICompontent are built-in JavaScript commands, and have nothing to do with jQuery. – gen_Eric Sep 16 '11 at 19:50
  • 2
    please read the post first. He does use jquery and has loaded it when doing his experiments. I couldn't reproduce the error! with the native functions. – Lorenz Lo Sauer Sep 16 '11 at 19:53
  • 1
    @Juan Mendes: Parkyprg has clearly loaded jquery, which would be a likely explanation why I couldn't reproduce the error, another would be browser extensions. Nowhere did I make the connection betweeen JS and jquery. – Lorenz Lo Sauer Sep 16 '11 at 19:57
  • 2
    @Lo: jQuery and native functions have nothing to do with other. jQuery is *NOT* the reason you cannot reproduce the error. The problem here is he's trying to decode the `%`, instead of encoding it. You're encoding the `%`, so it works. Try to run `decodeURI('%')` (with or without jQuery), it won't work. – gen_Eric Sep 16 '11 at 19:58
  • 3
    @Lo - xhr2.blogspot.com: I read the post better than you did, jQuery has nothing to do with the problem. Even if did, what does `set a breakpoint in jquery` mean? That you think JS and jQuery are the same thing.... – Ruan Mendes Sep 16 '11 at 19:58
  • 1
    I am talking about decodeURI. obviously you couldn't reproduce the error either. If >70% of the post's content is jquery related, then it is a jquery question. That a 'URIError' is thrown was already made clear at the time of my post. – Lorenz Lo Sauer Sep 16 '11 at 20:04
  • 1
    @Lo - xhr2.blogspot.com: 70% of the post's content is irrelevant! The only part that is relevant is not jQuery! (and it's all JS) – Ruan Mendes Sep 16 '11 at 20:06
  • 1
    @Lo: I beg to differ. Just because there are a few lines of jQuery does not change the fact it's a javascript issue. – Ivan Sep 16 '11 at 20:07
  • 1
    @Lo: What are you talking about? `obviously you couldn't reproduce the error either`? I reproduced it just fine, as I know what's wrong. `If >70% of the post's content is jquery related, then it is a jquery question`? Umm... no. 100% of the content can be jQuery related, but the problem could have just been a normal JavaScript one. jQuery is just a JavaScript library, it's not a language. The problem here has nothing to do with jQuery. – gen_Eric Sep 16 '11 at 20:08
  • @Lo: The URIError has nothing to do with the string's length. It has to do with the fact that `%` not an encoded string. Well, I guess in a sense it's too short, because decodeURI is expecting `%xx`, not just `%`. – gen_Eric Sep 16 '11 at 20:11
  • @Lo: This also isn't an "actual bug in jquery". – gen_Eric Sep 16 '11 at 20:15
  • read the post: 'the browser hangs'[sic!], 'lock up my browser?'[sic!]. Since I have contributed to Chromium, I am generally interested in finding and fixing native browser-bugs. – Lorenz Lo Sauer Sep 16 '11 at 20:15
  • 1
    @Lo: What does that have to do with whether this is a Javascript issue (which it is) or a jQuery issue (which it is not). – Ivan Sep 16 '11 at 20:17
  • @Lo: I doubt this really locked up his browser, the rest of his JavaScript probably didn't execute, so he thought it locked up. If it did lock up it's because of other code on his page (which he didn't post). It bugs me when people think things are browser/jQuery bugs because their code doesn't work (I've seen a lot of questions where people think an error in their code is a jQuery bug). – gen_Eric Sep 16 '11 at 20:20
  • 2
    @Rocket: It's fine. I browsed this question because the subject-line suggested that this may be a JS-engine bug. I checked in FF/Chrome encodeURI / decodeURI and it was not. Before leaving I posted my two cents, which I realize may not have been helpful. – Lorenz Lo Sauer Sep 16 '11 at 20:52