-
Notifications
You must be signed in to change notification settings - Fork 494
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WebAPI does not properly encode special characters #641
Comments
I'll have to check, but I think this encoding mechanism came straight from steamclient.dll/dylib/so. Interestingly, at least some of the Service APIs / Unified Messages accept binary input in Base64 and Hexadecimal, IIRC. I wonder if AuthenticateUser would... |
Right now I'm using this code that works: await iSteamUserAuth.AuthenticateUser(
encrypted_loginkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(encryptedLoginKey, 0, encryptedLoginKey.Length)),
method: WebRequestMethods.Http.Post,
sessionkey: Encoding.ASCII.GetString(WebUtility.UrlEncodeToBytes(encryptedSessionKey, 0, encryptedSessionKey.Length)),
steamid: steamID
) I've tried using standard I've also tried bunch of other scenarios, such as If you by any chance want to try yourself, you can check my working code in ASF, I couldn't get this function to work through any other combination, but I did succeed doing the same RFC violation that WebAPI does, generating |
Try using |
@xPaw It doesn't work for me using Dictionary<string, string> data = new Dictionary<string, string> {
{ "encrypted_loginkey", HttpUtility.UrlEncode(encryptedLoginKey) },
{ "sessionkey", HttpUtility.UrlEncode(encryptedSessionKey) },
{ "steamid", steamID.ToString() }
}; Did you try to |
Why are you trying to FormUrlEncodedContent it? |
Because you need to encode keys and values as per RFC when using |
This is why I'm trying to make a solution that works with |
Show what modifications you have done in SK, otherwise it's hard to tell what you're trying to do. |
I didn't do any modifications to SK2, maybe let me summarize so I can explain better what I mean:
When sending arguments, either in query string (like in SK2's WebAPI currently violates this RFC, as it does not encode the values, but it does encode the keys. This means that you can't use special characters in values of SK2 WebAPI requests, as they have a potential to blow up the rest of the arguments, for example The proper solution to this problem is just encoding the value as it should be done (and as it's done in Finding a way to send Let me know if I should elaborate on something else or if something is still unclear. |
As an example of flaw of current implementation, find yourself Steam API endpoint that accepts some Just remember to send the request through SK2's |
"I didn't do any modifications to SK2" Were you testing the changes using a HttpClient constructed yourself or passed it through SK's WebAPI? |
Both. Yesterday I was testing with static default SK2 HttpClient, today with my own. I can do a second round with static if you want to, but I don't see how HttpClient can be of problem here unless you manually fiddle with |
I'm asking because you can't pass raw body data to SK's api calls, can you? So if you wanted to test FormUrlEncodedContent, you would either need to modify SK implementation or test it on a separate HttpClient without SK at all. |
Yes, this is what I did, I tested my own SK2 WebAPI doesn't do anything else than parsing your arguments, constructing a dictionary of args and manually constructing http content, there is nothing else that could change the outcome of the request, of course you could come up with abstract thing such as different user-agent, but it's not that, otherwise my implementation wouldn't work either. |
If you look at FormUrlEncodedContent code, it uses Uri.EscapeDataString internally. Said function only expects a string so you can't really pass a binary byte array into it. FormUrlEncodedContent on its own hardly does anything interesting, it's basically the same thing as SteamKit's implementation, it just encodes the keys/values differently. If you use HttpUtility.UrlEncode AND FormUrlEncodedContent, you're effectively encoding it twice, so it will obviously fail. That function may return percent encodings if international support is enabled, but that's just a clusterfuck. https://docs.microsoft.com/en-us/dotnet/api/system.uri?view=netframework-4.7.2#international-resource-identifier-support |
No, it encodes value while SK2 does not. Compare:
And: This is exact reason for difference between those two implementations. Yes, there are other misc things like
And I know that, this is why I tested whole lot of combinations, started from reference that works (encoded bytes into string + lack of encoding in SK2 WebAPI), and went through everything that came to my mind (bytes to hex + encoding, bytes to base64 + encoding, non-encoded ASCII string + encoding). I documented it all in my posts above, and pointed out that as long as there is RFC encoding applied, I can't make it work with any of my inputs. |
So with all of your testing, do you have a solution? If different Valve's APIs expect different encodings, you can't do one-solution-fits-all implementation. |
I have only one solution that would work and satisfy every use case, and it's not pretty. It'd be
This way WebAPI can effectively detect This is a breaking change though for existing |
This is also why I'm opening the issue and not submitting PR, because perhaps there is a better approach to this issue, for example finding out that we could send that |
The good thing is how this can be done without breaking the API, since SK2 can already detect types of arguments from supplied code, and therefore decide to make appropriate operation without user needing to specify what should be encoded and what should not. The only drawback is that breaking change of existing Still, maybe there is some better approach that I'm not aware of. |
If what @xPaw said above is correct, it's likely that WebAPI actually is RFC-compliant. Looking at it again after having a good night's sleep, I think the problem is that you're double-encoding the data.
e.g. Try building your own |
That's not the issue. The order of things which works right now for me is url-encoding the bytes of raw binary (using However, if you want to make encoding your last step (like what you suggest doing with custom I'm not sure why I need to repeat the same stuff over and over again, you can just check for yourself if you think that it's my code doing something wrong, make |
I never said to make encoding the last step, I only said to do it once. UrlEncodeToBytes puts the percent signs inside the byte array. If you decode to ASCII first, the runtime replaces unprintable characters with a question mark, which won’t work. If you get your perfect percent-encoded string, which it sounds like you’re already doing, then passing it to FormUrlEncodedContent will doubly encode it and destroy your value. SteamKit probably could encode binary values as per RFC and it would all still work, but it won’t solve your problem of trying to use FormUrlEncodedContent. FormUrlEncodedContent won’t work for you because it’s designed to take unencoded strings as it’s input, not encoded ones. Unless there’s an option to disable encoding, you’ll need to build your own content/formatter. |
I don't need to use
How many times do I need to repeat that I don't double-encode my data? How many times do I need to repeat that binary->encode->string is not equal to binary->string->encode, and how many times do I need to repeat that I'm trying to solve Maybe I should just stop trying to solve the issue and state what is needed for
Make both work through |
The example you posted was this: new Dictionary<string, string> {
{ "encrypted_loginkey", HttpUtility.UrlEncode(encryptedLoginKey) },
{ "sessionkey", HttpUtility.UrlEncode(encryptedSessionKey) },
{ "steamid", steamID.ToString() }
}; This created a dictionary with correctly URL-encoded values. However, if you then pass that dictionary to
as per your original post. Also,
I thought that was what you were trying to do in the first place? 😕 In any event, I believe all we need to do to fix this is:
|
Guess why I tried that? 🤔
Well, it didn't work, I didn't expect that to work either, but I tried in hope that I'm wrong regardless. And then you say that it's me who is double-encoding the things 🙁.
I was trying to discuss potential solutions to this issue since it's not a trivial one-line fix that wouldn't regress in other place,
Your solution looks exactly the same as mine with detecting |
Throwing my own comedy opinion into the ring: we deprecate the WebAPI helpers in vNext, remove them the next major a couple months down the line. This stuff is just HTTP that anybody can solve with their own Valve's original Web APIs aren't anywhere near RFC compliant - and I wouldn't be really surprised if our approach to ensuring we're encoding arguments correctly will break some other API endpoint we're not thinking about. |
TBH this is what I initially planned to do in my own ASF#1086, but upon closer investigation of edge-cases like these, I'm not so sure if dropping it is beneficial for consumers. I think you need to discuss whether you want to support WebAPI and its quirks, if yes, it's definitely a good idea to keep it around as it's still better than me fiddling with my own Personally I'd like to see it being supported, as it does simplify a lot of things and with recent shared Regardless, thank you a lot for your continuous support, I really appreciate it, even if sometimes I lack patience to show it 😀. |
We do still need WebAPI internally, so we have to keep it alive in some form or another. |
To clarify this, I don't believe there's actually anything wrong here. This isn't "does not properly encode" but rather, "does not automatically encode/escape user input". I don't believe there are any RFC conflicts at this point. It may be that the reserved set for WebHelpers.UrlEncode was only applicable for CDN, in which case we could prefer HttpUtility.UrlEncode. Whether the value is a byte[] or a string, HttpUtility.UrlEncode has the override for both, and takes the correct action. I would vote for lifting out the encoding logic into TryInvokeMember, where we would call UrlEncode on all the values as they're placed into the dictionary (but what about GET encoding requirements vs POST encoding requirements?) |
Expecting from the consumer using WebAPI to manually escape his data when he doesn't even have any say over I didn't see any documentation that mentioned how user is expected to do that amount of low-level stuff, if I did then I'd complain much earlier about that as a design flaw. If you truly want user to encode his strings, then you should accept The proper place to do the encoding is the one creating |
WebAPI is definitely a wrapper that provides value. I don't agree with allowing the user to supply their own entity body, as WebAPI is meant to wrap a specific flow (form data request body or query string with VDF response). GET requests don't involve an entity body. I agree that having to manually escape parameters is not convenient, which is why I agree with making a breaking change to encode string and byte[] parameters in WebAPI. Your code for encoding the parameters is correct, the only difference would be whether it's done in user code or in WebAPI. If nothing else, the documentation may need to state the user needs to encode their input using HttpUtility.UrlEncode. |
I'd rather opt for WebAPI applying the correct logic and not expecting from consumer to do any encoding at all. The fact that I do the encoding myself right now is the side-effect of the fact that WebAPI doesn't. It's not like encoding is really consumer-specific, RFC defines it explicitly, the only modification that is needed apart from encoding everything, is special case for handling This is also why I consider existing code to be fairly decent, it just needs small logic enhancement of encoding everything but |
byte[] types aren't really a special case, they just have be encoded directly from binary to URL encoding (and not through any intermediate encoding such as ASCII which is only 7 bits). There is no case where you do not encode binary data, all parameters need to be encoded. We are in agreement that WebAPI should allow byte[] parameters, and that it should use the correct overload of HttpUtility.UrlEncode that takes binary data and returns the URL encoded string. |
I know they aren't per-se, but they are in our case where we need to handle them in a special way to make the expected use case to work. It's basically a workaround for the consumer to be able to send rawbinary in expected by Steam format, without manually crafting a request or depending on non-documented internal implementation of SK2 like in current case, where consumer is expected to encode value but not the key, and it goes against any sensible expectations. In any case I'm glad we have an agreement upon the solution of this problem 😀. |
This should make it into the 2.2.0 release as a breaking change. The correct encoding will be handled by the library, with the user passing in un-encoded parameters. |
While digging in my own toys, I've found out that current WebAPI code does not properly encode special characters. This is well documented in SK2 code, except it's not universally correct approach. It's totally valid to use characters that can be problematic for the URL, namely
&
as an example. If I use a key-value pair such asname -> bob&
then raw data should be encoded asname=bob%26
, while right now it's encoded asname=bob&
. This isn't just incorrect as it is, it can create issues with other arguments.Moreover, the rabbit hole is much deeper since changing it to proper encoding would actually regress in valid code. I was testing
ISteamUserAuth/AuthenticateUser
API endpoint today and wondering whyFormUrlEncodedContent()
doesn't work nicely with my args, turns out it's because Steam actually expects binary there, and you can't encode those bytes for special characters, as the data will be no longer correct (and this is why myFormUrlEncodedContent()
didn't work).Considering the facts above this can be a problematic issue to fix, since you can't know in advance whether something should be encoded or not. The easiest solution that would not regress and fix the underlying issue would be a check for type of each WebAPI argument, and apply new encoding logic for strings, while keeping current no-encoding logic for
byte[]
. The only problem with this is that it's so abstract to do things this way that it might be better to rewrite entire http content into something likemultipart/form-data
and handle strings and binary in different way - I just didn't test how nicely Steam servers play with it, and it'd still require extra logic like the one above what exactly type the user is supplying, sobyte[]
becomes allowed and encoded as binary.Right now current behaviour of SK2 breaks RFC 1866:
And the most hilarious thing is how making it right would regress in my own working code, and make it impossible to use for binary data.
Let me know what you think, I'm super confused myself.
The text was updated successfully, but these errors were encountered: