Why does decodeURIComponent('%') lock up my browser?
Asked Answered
K

8

25

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?

Kreit answered 16/9, 2011 at 19:41 Comment(6)
"The browser", which browser?Abscond
Tested with Chrome and Firefox.Kreit
Are you trying to encode or decode the %? It can be encoded (into %25) using encodeURI or encodeURIComponent, but it cannot be decoded (using either) because it's not a URI.Ph
Also, does it really "lock your browser" or does it just not execute the rest of the JavaScript statements?Ph
Note that when there is an Error/Exception in JavaScipt, it will stop executing. This is not the same as "hanging" (or "locked up") where the browser won't respond to your keyboard or mouse anymore.Ph
That is what every error does.Outspread
A
19

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.

Aronarondel answered 16/9, 2011 at 19:46 Comment(7)
How about Wikipedia? 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.Aronarondel
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?Kreit
@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'.Ph
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.Aronarondel
@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'.Aronarondel
@Juan: That's what I thought, but "hang" and "lock up" are the wrong terms, so it kinda bugged me.Ph
try decodeURIComponent(encodeURIComponent(" % 1")), output = "% 1"Halloo
F
30

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") // ?
Frediafredie answered 22/1, 2019 at 14:8 Comment(4)
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')Betty
@Betty I'm trying to understand your use case. What would be your input to decodeURIComponentSafe and what would be your expected result?Frediafredie
decodeURIComponentSafe('%3f'). Expected result:?. Actual result: %25efBetty
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.Tedium
A
19

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.

Aronarondel answered 16/9, 2011 at 19:46 Comment(7)
How about Wikipedia? 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.Aronarondel
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?Kreit
@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'.Ph
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.Aronarondel
@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'.Aronarondel
@Juan: That's what I thought, but "hang" and "lock up" are the wrong terms, so it kinda bugged me.Ph
try decodeURIComponent(encodeURIComponent(" % 1")), output = "% 1"Halloo
T
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"
Teens answered 15/4, 2017 at 11:53 Comment(2)
decodeURIComponentSafe("%%20Visitors") returns the string as is rather than returning "% Visitors"Cinemascope
try this, decodeURIComponent(encodeURIComponent(" % 1")), output = "% 1"Halloo
P
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'
Ph answered 16/9, 2011 at 20:0 Comment(0)
S
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
    }
}
Sternlight answered 9/6, 2021 at 14:9 Comment(0)
C
2

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

Chair answered 16/9, 2011 at 19:45 Comment(1)
Just to be accurate (PITA), %25 is also not a URI, but is valid data to be used in a URIAronarondel
L
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

Leialeibman answered 29/5, 2020 at 7:49 Comment(0)
P
-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"
Polled answered 16/9, 2011 at 19:48 Comment(16)
This has nothing to do with jQuery.Che
encode/decodeURI and encode/decodeURICompontent are built-in JavaScript commands, and have nothing to do with jQuery.Ph
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.Polled
@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.Polled
@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.Ph
@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....Aronarondel
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.Polled
@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)Aronarondel
@Lo: I beg to differ. Just because there are a few lines of jQuery does not change the fact it's a javascript issue.Che
@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.Ph
@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 %.Ph
@Lo: This also isn't an "actual bug in jquery".Ph
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.Polled
@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).Che
@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).Ph
@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.Polled

© 2022 - 2024 — McMap. All rights reserved.