الأدوات · النصوص · ترميز وفك ترميز الروابط · الأدلة · How to Pass a URL Inside Another URL Without Breaking It
How to Pass a URL Inside Another URL Without Breaking It
A login page that sends people back to https://app.example.com/dashboard?tab=reports&range=30d after they sign in needs that whole address as the value of one parameter. Pasted in raw, as https://example.com/login?redirect_uri=https://app.example.com/dashboard?tab=reports&range=30d, the outer URL reads it as two parameters: redirect_uri becomes https://app.example.com/dashboard?tab=reports, and range=30d turns into a parameter of the login page. The visitor lands on the dashboard without the 30-day range. The fix is to encode the inner URL as one component before it goes in.
Which characters actually collide
The query string of a URL starts at its first ?. After that, a parser that follows the WHATWG URL standard (browsers, and Node's URL and URLSearchParams) treats a second ? as ordinary text. So ?redirect_uri=https://app.example.com/dashboard?tab=reports, with no & in it, comes back as one key whose value is the full inner URL. I checked that with new URL(...).searchParams before writing this, because it is easy to assume the opposite.
Two characters do break it. & separates one parameter from the next, so the first & inside the inner URL ends redirect_uri early. # starts the fragment, so anything after it leaves the query string altogether: add #chart to the inner URL and the parser hands it to the outer URL's hash, not to redirect_uri. A + is a quieter problem: many server frameworks decode query values as form data and turn it into a space. Whether a given URL survives raw depends on what its query happens to contain today, which is exactly why you should not rely on it.
The fix: encode the whole inner URL as a component
Paste only the inner URL, https://app.example.com/dashboard?tab=reports&range=30d, into the URL encoder in Encode mode. The tool runs encodeURIComponent on it and returns https%3A%2F%2Fapp.example.com%2Fdashboard%3Ftab%3Dreports%26range%3D30d. Every :, /, ?, = and & is now a percent sequence, so nothing in the value looks like structure to the outer URL. Put that after redirect_uri= and the outer parser sees one parameter. The application that reads it gets the original address back, because reading a query value decodes it once.
The function matters. encodeURI is built for a whole address and leaves &, ?, = and # alone on purpose, so running the inner URL through it returns the string unchanged and fixes nothing. In code, use encodeURIComponent, or let URLSearchParams build the query for you, which does the escaping when it serializes.
Where this comes up
OAuth redirect_uri and callback parameters, a "share this page" link that carries the current page's address, a sign-out link that says where to go next, a tracking redirect that forwards the visitor. In each case the inner URL is a value, not part of the outer URL's structure, and the same rule holds: encode it as a component on the way in, decode it once on the way out.
Debugging one that already went wrong
When a redirect lands on the wrong page, copy the redirect_uri value out of the address bar or the server log and paste it into the same tool in Decode mode. It runs decodeURIComponent and shows the URL the system actually received. If the decoded value stops at an &, the sender forgot to encode. If it still contains %3A and %2F after one decode, it was encoded twice. If the text came from a form or a search URL that writes spaces as +, tick "Read + as a space" first; it is off by default because in a percent-encoded component a plus is a plus. A broken percent sequence makes the tool show an error instead of a guess.
%XX sequences, encoding it again turns each % into %25 (a%20b becomes a%2520b). Decode it to plain text first, then encode it once.For why this tool writes a space as %20 and not +, see the three correct ways to encode a space. For the full list of what gets escaped, see the how-to guide. Both directions run in your browser, which helps when the URL you are nesting carries a token you would rather not paste into someone else's server.