On this page

To carry a complete redirect URL inside another URL, treat the destination as one query parameter value. Define the required result before choosing an encoding operation: after the receiving application reads that value, it should have the intended destination, including any query text and fragment you need to preserve.
A percent-encoded destination can represent that nesting. The practical challenge is identifying which operation constructs the outer query and which operation later reads it. Adding encoding or decoding steps without checking those boundaries makes a mismatch harder to locate.
The JavaScript below is a proposed diagnostic, not an executed demonstration or a guarantee about a runtime. Its comparison provides a pass condition for exact preservation. Use the observations from your environment, together with the API and receiver documentation, to establish the actual behavior.
Treat the destination URL as one parameter value
Start by naming the two addresses in the example:
Outer address:
https://example.test/login
Destination to carry:
https://example.test/report?tag=a&tag=b&q=a%2Bb#summary
The outer address is where the redirect parameter will be placed. The complete report address is the proposed value of that parameter. A percent-encoded nested destination is an appropriate representation to investigate for this task.
Treat every character in the destination as part of your test input. The two tag entries, q=a%2Bb and #summary all belong to the destination you intend to recover. Checking only the report path would leave most of the interesting input unexamined.
Make the intended boundary explicit
If you experiment with directly concatenating the destination after ?redirect=, inspect the resulting parameter structure. Pay particular attention to the destination’s ampersands and fragment delimiter. Do not assume that placing the strings next to each other establishes the containment you intended.
The useful question is whether the receiving parser still treats the complete report address as one value. Record any text that appears outside that value, including additional outer parameters or an outer fragment.
Also decide whether your application requires exact text preservation. If it does, save the original destination unchanged for comparison. If normalization is permitted, document the allowed transformation and its expected result. Without that decision, a changed destination could be accepted during one test and treated as a bug during another.
Build the query with URL and URLSearchParams
For a JavaScript implementation, evaluate the proposed URL and URLSearchParams workflow with the complete destination passed directly to the query-building call:
const redirect = 'https://example.test/report?tag=a&tag=b&q=a%2Bb#summary';
const outer = new URL('https://example.test/login');
outer.searchParams.set('redirect', redirect);
const serialized = outer.href;
const recovered = new URL(serialized).searchParams.get('redirect');
console.log({
original: redirect,
serialized,
recovered,
matchesOriginal: recovered === redirect
});
This sample has not been executed here. For a contract requiring exact preservation, the expected pass condition is matchesOriginal: true; that is a test requirement, not a reported result. Inspect the actual serialized address and recovered value in your chosen environment.
Start with this direct-input candidate because it makes the construction step easy to review: the value supplied to the builder is visibly the original destination. Avoid introducing manual substitutions or an additional encoding operation while establishing this baseline.
If you are considering encodeURIComponent(redirect) before the setter, treat that as a separate candidate requiring API documentation and its own recovery test. The available encoding facts do not establish how that combination behaves, so a particular output should not be assumed.
Encoding an entire assembled address is another operation to evaluate separately. Write down what the next consumer expects: a complete address, an outer query, or the value of one parameter. Choose an operation for that specific input contract, then verify its output rather than relying on how readable the resulting text appears.
Recover the destination and compare the exact string
Use the serialized outer address as the starting point for your recovery check. Comparing an intermediate variable with itself would miss changes introduced during serialization or subsequent handling.
For the proposed sample, retain both redirect and recovered. If they differ, display them side by side and locate the first difference. Avoid immediately decoding either value again: first establish what the receiving operation returned.
Inspect containment after checking the string
Once you have recorded the comparison, inspect the outer address and recovered destination separately. The following table describes the intended result for this test input, rather than claiming observed API output.
| Inspection | Intended result |
|---|---|
| Recovered destination | Matches the complete original string |
| Destination fragment | Retains the supplied #summary text |
| Outer fragment | Has not acquired the destination’s fragment |
Inner tag entries |
Both supplied entries remain within the destination |
Inner q text |
Retains the supplied a%2Bb spelling at this stage |
Use the component-inspection facilities documented for your environment to investigate these points. Keep their observations separate from the initial string comparison.
Finally, repeat the recovery check at the actual receiving application. A local construction test covers only the path exercised by that test. Record what the receiver extracts before any further processing, and decide explicitly how missing or empty redirect values should be handled. This gives you a useful comparison between the constructed input and the application-visible value.
Distinguish necessary nesting from accidental double encoding
Do not diagnose double encoding by counting percent signs alone. Begin with the original input and identify each place where an encoded value is placed inside another value.
A callback parameter inside an already encoded URL is a nesting situation associated with descriptions of double encoding. That situation must be distinguished from applying multiple transformations to the same destination before one query-building step. It does not, by itself, establish the behavior of a JavaScript API combination.
Trace the value at each handoff
For the main example, use a small inspection record:
| Stage | What to record |
|---|---|
| Original destination | The complete input, including q=a%2Bb |
| Value supplied to the builder | Whether it is the original string or a transformed replacement |
| Serialized outer address | The actual representation produced in your environment |
| Value read by the receiver | The text returned before further processing |
If you observe %252B in an enclosing representation, record where it appeared and which input produced it. That spelling alone does not establish whether your construction is correct. The relevant check is whether the receiver recovers the intended destination at the agreed boundary.
Similarly, do not attribute an unexpected result to pre-encoding without comparing the direct-input and pre-encoded candidates under the same test. Record the value supplied in each case and the corresponding recovered value.
Avoid decoding repeatedly until the result looks readable. Under the query convention considered here, + represents a space, while %2B represents a literal plus sign. Those spellings therefore deserve separate inspection. A readability check cannot establish that the intended spelling or interpretation survived. Use the original input and a documented sequence of operations as your reference.
Check plus signs, Unicode and repeated keys
After establishing the baseline, vary one feature of the destination at a time. Keep each original string alongside its serialized and recovered forms. This makes a failure easier to explain than one test containing several unfamiliar characters and transformations.
Use the following destinations as candidate test inputs, without assuming that every receiver accepts them or that every API preserves their spelling:
| Candidate destination | Question to investigate |
|---|---|
https://example.test/report?q=a+b |
Does recovery preserve the literal + character? |
https://example.test/report?q=a%2Bb |
Does recovery preserve the existing %2B text? |
https://example.test/report?q=a b |
Does the input contract permit the space, and what text is recovered? |
https://example.test/report?q=a%20b |
Does the supplied percent-escape spelling survive? |
https://example.test/report?q=café#résumé |
Is the supplied Unicode text retained or transformed? |
https://example.test/report?tag=a&tag=b |
Do both entries remain inside the recovered destination? |
For each permitted input, apply the same construction and comparison used in the baseline sample. If exact preservation is required, equality with that particular input is the pass condition. These cases are a verification plan, not established space or Unicode round-trip guarantees.
Keep preservation and interpretation separate
The supported query convention distinguishes +, which represents a space, from %2B, which represents a literal plus sign. Preserve those as separate test cases. After recording the recovered destination string, investigate how the receiving application’s inner-query parser interprets each one.
Repeated keys also require a location-specific question. Two tag entries inside the destination test the contents of one redirect value. Multiple outer redirect entries test the receiver’s selection or rejection behavior. Inspect that behavior separately: which value does the application use, does it reject ambiguity, and do all receiving layers apply the same rule? Do not infer those answers from the inner tag example.
Separate successful encoding from an accepted redirect
A successful string comparison answers the preservation question defined by your test. It does not establish whether the receiving application accepts the destination or what happens after acceptance.
Keep a separate record for each stage: the destination supplied to the builder, the value extracted by the receiver, the application’s acceptance decision and the resulting behavior. If extraction matches the intended input but the application refuses it, investigate the destination requirements before changing the encoding.
Verify the destination contract
For a generic return destination, ask which addresses and components the application permits. Does it accept the supplied destination? Is its fragment retained? Is normalization intentional? What should happen when the value is absent, empty or repeated? Obtain these answers from the application’s documented requirements and observed behavior.
If the integration uses an OAuth callback, check the provider’s registration requirements separately. Establish whether registration is required, how the submitted callback must relate to the registered value, which components are permitted and how fragments are treated. A generic example of carrying a nested destination supplies no provider-specific answer to those questions.
Before integration, resolve these practical checks:
- Must the destination survive exactly, or is a specified transformation allowed?
- Does the agreed parameter-reading step recover that intended value?
- Are the destination’s query entries and fragment still contained within it?
- Has each extra encoding or decoding operation been justified?
- How does the receiver handle multiple outer redirect values?
- Which application or callback-registration requirements determine acceptance?
Keep the diagnostic small enough to review alongside its observed output. Once you have verified a working case in the actual environment, retain its input and expected recovery result as a regression example. That gives future changes a concrete preservation requirement without treating successful encoding as permission to redirect anywhere.