This absolutely fixes Bobby DROP TABLE. The source of truth on how long the user's name is is just the length of the user-supplied slug.
From the XKCD:
Robert'); DROP TABLE Students; --
The issue here is that
');
Is being intepreted as the end of a string; it assumes that there will be something like:
format("SOME_FN('%s');",user_name)
going into SQL, and this fools the system.
SQL solves this already with parameterized queries, and many HTML libraries also solve this in various ways, but if it were instead:
format("SOME_FN(%d:%s)", len(user_name), user_name)
then there is no value you can put in user_name that will let you escape the function call.
Length prefixes are one way of working this, but only scratch the surface of the issue. As others have pointed out, it's also the fact that the control elements are inline with the data.
<p:25><script:14>somethingBad()
Will still run somethingBad(). You are at least sandboxed to the containing element though, so restricting certain elements to only appear in parts of the HTML tree could prevent this (e.g. if all scripts were disallowed in BODY then merely constraining user-generated content to the BODY would work; right now you could still get hit by someone including </body> in their content.