Monday, 5 June, 2023 UTC


Summary

I am very disappointed in how the official React documentation recommends we build URL strings to query APIs in JavaScript.
I was reading about custom hooks and came across this:
fetch(`/api/cities?country=${country}`)
  .then(response => response.json())
  .then(json => {
    if (!ignore) {
      setCities(json);
    }
  });
This code works correctly in most expected cases—for example, if “country” is an ISO 3316-1 country code. These are made up strictly of alphanumeric characters. They will survive query string serialization with no changes.
But it’ll fall apart rather quickly if you throw other things at it.
Break bad query constructions.
This is a problem because basic “backtick strings” (correctly known as template literals) aren’t designed for creating strings to serialize and pass to other systems. They work best when you think of them instead as a way to print things.
As an example of how they’re unsuitable for this, in something that looks a lot like a classic SQL injection, “country” could be something like us&deleteAllData=true. This means:
  1. the query sent to the server is /api/cities?country=us&deleteAllData=true, and
  2. the server’s request will have two parameters: “country” and “deleteAllData”.
Of course, the query string is completely under the control of the client. An API that has a flag called “deleteAllData” — particularly on a GET request — is likely poorly designed.
But none of that changes the fact that this is the wrong way to do it. (And when was the last time you worked with a well-designed API, anyway?)
Some other strings might not be security issues but get handled incorrectly nonetheless. For example, “&” and “=” are perfectly valid characters to use in a query string. But if they’re put together using a standard backtick string, they’ll be misunderstood on the other end as dividing search parameters and their values.
What if it’s not even a string?
In fact, because it’s not TypeScript, we don’t even have any level of assurance that “country” is a string.
Very interesting things can happen if it’s not. For example, arrays will be printed joined with commas, like “a,b,c”. If one of your array elements has a comma in it, it’ll be indistinguishable from the element separators:
const a = ["a,b", "c", "d"]
`${a}` // 'a,b,c,d'
`${a}`.split(','); // [ 'a', 'b', 'c', 'd' ]
(If you do want to pass an array, many query string parsing libraries will treat repeated keys as individual elements of an array. For example, “a=b&a=c” will give you the array “[‘b’, ‘c’]” for “a”.)
Let’s do it a little better.
There is a simple, built-in answer. Unfortunately, it does have some caveats.
JavaScript includes URLSearchParams, which does take care of many problems. For example, instead of this:
`/api/cities?country=${country}`
You can write this:
'/api/cities?' +
  new URLSearchParams([
    [ 'country', country ]
  ])
URLSearchParams’ toString method will take care of several things for you, such as escaping parameter values correctly. (It’s called implicitly here.)
If “country” is, you’ll get country=us%26deleteAllData%3Dtrue. This will come out correctly on the other end as a single country parameter, albeit one that doesn’t make much sense.
URLSearchParams does fall down in other cases when parameter values aren’t strings. For example, if one of your parameters isn’t a string but undefined, URLSearchParams will literally put the string “undefined” into your query.
Arrays are also a problem, concatenating values with commas much like backtick strings do. If you might have array values, you’ll have to split those up.
I strongly recommend leveraging TypeScript to track types throughout your codebase to ensure you don’t trip over these kinds of issues. Also, use something like Zod validation to ensure things are the types you expect when they come from external sources.
If you do make sure you only feed URLSearchParams strings, you’ll be in good shape. And please remember: string formatting tools are for printing, not for serialization.
The post JavaScript’s Backtick Strings are Likely the Wrong Tool for Your Job appeared first on Atomic Spin.