Skip to content
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

IllegalArgumentException in Headers$Builder.checkNameAndValue() for content-disposition filename response header #1998

Closed
would-code-again opened this issue Nov 14, 2015 · 13 comments · Fixed by #2587
Labels
bug Bug in existing code
Milestone

Comments

@would-code-again
Copy link

The fix for #891 is causing IllegalArgumentException to be thrown for non ASCII response headers. Most notably, content-disposition, and the filename part. This is basically causing filenames with all the best letters from all the best countries (ü,ä,î,â etc) to crash our app.

The failure mode for us is semi-rare for a variety of reasons, but I imagine this would be far worse for others.

The RFC on the "filename" parameter allows any ISO-8859-1 character: http://tools.ietf.org/html/rfc6266#page-5 so the current behaviour definitely seems to be incorrect.

It makes sense to check request headers. I'm not convinced that it's reasonable for response headers, given that there are plenty of occasions where you just don't control the full stack. Actually, even the unit test that was added in the commit a57aa43 (responseHeaderParsingIsLenient) seems to imply that this is what was intended - must just be a bug.

It would be great if OkHttp could be a little less strict in production situations, but that's sort of unrelated.

Possible fix would be addLenient at https://github.com/square/okhttp/blob/master/okhttp/src/main/java/com/squareup/okhttp/internal/http/FramedTransport.java#L192 but I don't know the ins and outs of this code with enough confidence to make those changes.

java.lang.IllegalArgumentException: Unexpected char 0xf6 at 21 in header value: inline;filename="Andy��IsSad.png"
at com.squareup.okhttp.Headers$Builder.checkNameAndValue(Headers.java:295)
at com.squareup.okhttp.Headers$Builder.add(Headers.java:245)
at com.squareup.okhttp.internal.http.FramedTransport.readNameValueBlock(FramedTransport.java:192)
at com.squareup.okhttp.internal.http.FramedTransport.readResponseHeaders(FramedTransport.java:104)
at com.squareup.okhttp.internal.http.HttpEngine.readNetworkResponse(HttpEngine.java:906)
at com.squareup.okhttp.internal.http.HttpEngine.access$300(HttpEngine.java:92)
at com.squareup.okhttp.internal.http.HttpEngine$NetworkInterceptorChain.proceed(HttpEngine.java:891)
at com.squareup.okhttp.internal.http.HttpEngine.readResponse(HttpEngine.java:749)
at com.squareup.okhttp.Call.getResponse(Call.java:268)
at com.squareup.okhttp.Call$ApplicationInterceptorChain.proceed(Call.java:224)
at com.squareup.okhttp.Call.getResponseWithInterceptorChain(Call.java:195)
at com.squareup.okhttp.Call.execute(Call.java:79)

@elincoln
Copy link

elincoln commented Dec 4, 2015

Definitely an issue for me right now -- in my case, its other servers sending us set-cookie: headers that contain the 0x01 control character.

(EDIT: Reverted to 2.4 to avoid the issue for now.)

@HugoGresse
Copy link
Contributor

any update on this?

@swankjesse
Copy link
Collaborator

Nothing yet. Is it just redirects and cookies?

@swankjesse
Copy link
Collaborator

swankjesse commented May 11, 2016

Yeah, we definitely shouldn’t be throwing an unchecked exception here.

One problem is that the spec wants us to not interpret header fields, but since we’re exposing a Java API we really want strings.

Historically, HTTP has allowed field content with text in the
ISO-8859-1 charset [ISO-8859-1], supporting other charsets only
through use of [RFC2047] encoding. In practice, most HTTP header
field values use only a subset of the US-ASCII charset [USASCII].
Newly defined header fields SHOULD limit their field values to
US-ASCII octets. A recipient SHOULD treat other octets in field
content (obs-text) as opaque data.

Handling ISO-8859-1 feels wrong, particularly since the upside of supporting additional characters has the downside of being locked out of UTF-8. Neither stripping non-ASCII characters nor stripping non-ASCII headers feels appropriate.

@swankjesse swankjesse modified the milestones: 3.4, 3.3 May 21, 2016
@zewenwang
Copy link

Use URLEncoder encode headers before addHeader, it works well for me.

@would-code-again
Copy link
Author

@zewenwang that only works when you're in control of the headers. If you're sending requests to some site that you don't control and receiving troublesome response headers, it's a much less effective strategy.

@rfc2822
Copy link
Contributor

rfc2822 commented May 27, 2016

Same problem here. Just got this (anonymous) Google Play crash report:

java.lang.RuntimeException: An error occurred while executing doInBackground()
    at android.support.v4.content.ModernAsyncTask$3.done(ModernAsyncTask.java:142)
    at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:354)
    at java.util.concurrent.FutureTask.setException(FutureTask.java:223)
    at java.util.concurrent.FutureTask.run(FutureTask.java:242)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
    at java.lang.Thread.run(Thread.java:818)
Caused by: java.lang.IllegalArgumentException: Unexpected char 0x201c at 0 in strict-transport-security value: “max-age=7200″
    at okhttp3.Headers$Builder.checkNameAndValue(Headers.java:320)
    at okhttp3.Headers$Builder.add(Headers.java:270)
    at okhttp3.internal.http.Http2xStream.readHttp2HeadersList(Http2xStream.java:263)
    at okhttp3.internal.http.Http2xStream.readResponseHeaders(Http2xStream.java:149)
    at okhttp3.internal.http.HttpEngine.readNetworkResponse(HttpEngine.java:775)
    at okhttp3.internal.http.HttpEngine.access$200(HttpEngine.java:86)
    at okhttp3.internal.http.HttpEngine$NetworkInterceptorChain.proceed(HttpEngine.java:760)
    at at.bitfire.davdroid.HttpClient$PreemptiveAuthenticationInterceptor.intercept(HttpClient.java:153)
    at okhttp3.internal.http.HttpEngine$NetworkInterceptorChain.proceed(HttpEngine.java:733)
    at at.bitfire.davdroid.HttpClient$UserAgentInterceptor.intercept(HttpClient.java:139)
    at okhttp3.internal.http.HttpEngine$NetworkInterceptorChain.proceed(HttpEngine.java:733)
    at okhttp3.internal.http.HttpEngine.readResponse(HttpEngine.java:613)
    at okhttp3.RealCall.getResponse(RealCall.java:244)
    at okhttp3.RealCall$ApplicationInterceptorChain.proceed(RealCall.java:201)
    at okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.java:212)
    at okhttp3.RealCall$ApplicationInterceptorChain.proceed(RealCall.java:190)
    at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:163)
    at okhttp3.RealCall.execute(RealCall.java:57)
    at at.bitfire.dav4android.DavResource.propfind(DavResource.java:268)
    at at.bitfire.davdroid.ui.setup.DavResourceFinder.getCurrentUserPrincipal(DavResourceFinder.java:329)
    at at.bitfire.davdroid.ui.setup.DavResourceFinder.findInitialConfiguration(DavResourceFinder.java:120)
    at at.bitfire.davdroid.ui.setup.DavResourceFinder.findInitialConfiguration(DavResourceFinder.java:87)
    at at.bitfire.davdroid.ui.setup.DetectConfigurationFragment$ServerConfigurationLoader.loadInBackground(DetectConfigurationFragment.java:142)
    at at.bitfire.davdroid.ui.setup.DetectConfigurationFragment$ServerConfigurationLoader.loadInBackground(DetectConfigurationFragment.java:125)
    at android.support.v4.content.AsyncTaskLoader.onLoadInBackground(AsyncTaskLoader.java:296)
    at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:54)
    at android.support.v4.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:42)
    at android.support.v4.content.ModernAsyncTask$2.call(ModernAsyncTask.java:128)
    at java.util.concurrent.FutureTask.run(FutureTask.java:237)
    ... 3 more

So, an HTTP request execute call has thrown an IllegalArgumentException which I could really not expect, causing DAVdroid to crash.

I suggest to use checked exceptions instead – maybe subclasses of IOException? For instance, CharacterCodingException is used for a similar case and it's an IOException.

swankjesse pushed a commit that referenced this issue May 28, 2016
This doesn't completely support ISO-8859-1 headers; instead they will most likely
be mangled when they are decoded as UTF-8. If we decide we absolutely must support
ISO-8859-1 here we can do that in another change. (I'm not currently enthusiastic
about this idea.)

But this does prevent OkHttp from crashing when it encounters non-ASCII characters
in headers for HTTP/2, SPDY, and cached responses.

Closes: #1998
@brsengar
Copy link

Don't know why but I am still getting the error for some Japanese characters in the release 3.3.1. Had the fix released in 3.3.1?

amartinz pushed a commit to NamelessRom/android_external_okhttp that referenced this issue Jun 30, 2016
This relaxes some validation logic newly introduced in the version
of OkHttp used in N.

This change leaves the character code validation stricter
than it was in M by still preventing control codes like \n, \r,
backspace and delete. It does allow developers to pass
Java characters > 7F to addRequestProperty() and also receive
headers from servers which contain characters > 7F.

Android's HttpURLConnection does not follow the HTTP spec as
it encodes request header values and interprets response
headers as UTF-8 and not ISO-8859-1.

If a server is expecting or sending ISO-8859-1 encoded characters
>7F in headers then these will still be corrupted or misinterpreted
by Android. However, this has been the behavior since L and is not
changed here.

The OkHttp change which caused characters >7F to generate an
IllegalArgumentException and partially reverted here:
square/okhttp#1785

See also: square/okhttp#1998
square/okhttp#2016
for recent upstream bugs.

Bug: 28867041
Bug: https://code.google.com/p/android/issues/detail?id=210205
(cherry picked from commit 75687ca5ae54f417afb4c02ba04767da6786d829)

Change-Id: Ib640b58addff4c0c4eac589c77eb74a6bd6b3ec2
amartinz pushed a commit to NamelessRom/android_external_okhttp that referenced this issue Jun 30, 2016
This reapplies AOSP commit 3c28a13
There is a chance that we may want to revert this change in future.

Previous commit message:

This relaxes some validation logic newly introduced in the version
of OkHttp used in N.

This change leaves the character code validation stricter
than it was in M by still preventing control codes like \n, \r,
backspace and delete. It does allow developers to pass
Java characters > 7F to addRequestProperty() and also receive
headers from servers which contain characters > 7F.

Android's HttpURLConnection does not follow the HTTP spec as
it encodes request header values and interprets response
headers as UTF-8 and not ISO-8859-1.

If a server is expecting or sending ISO-8859-1 encoded characters
>7F in headers then these will still be corrupted or misinterpreted
by Android. However, this has been the behavior since L and is not
changed here.

The OkHttp change which caused characters >7F to generate an
IllegalArgumentException and partially reverted here:
square/okhttp#1785

See also: square/okhttp#1998
square/okhttp#2016
for recent upstream bugs.

Bug: 28867041
Bug: https://code.google.com/p/android/issues/detail?id=210205
(cherry picked from commit 75687ca5ae54f417afb4c02ba04767da6786d829)

Change-Id: Id683ec13142f1d7d8792143066f1dd2e5f62cf86
cm-gerrit pushed a commit to CyanogenMod/android_external_okhttp that referenced this issue Aug 24, 2016
This relaxes some validation logic newly introduced in the version
of OkHttp used in N.

This change leaves the character code validation stricter
than it was in M by still preventing control codes like \n, \r,
backspace and delete. It does allow developers to pass
Java characters > 7F to addRequestProperty() and also receive
headers from servers which contain characters > 7F.

Android's HttpURLConnection does not follow the HTTP spec as
it encodes request header values and interprets response
headers as UTF-8 and not ISO-8859-1.

If a server is expecting or sending ISO-8859-1 encoded characters
>7F in headers then these will still be corrupted or misinterpreted
by Android. However, this has been the behavior since L and is not
changed here.

The OkHttp change which caused characters >7F to generate an
IllegalArgumentException and partially reverted here:
square/okhttp#1785

See also: square/okhttp#1998
square/okhttp#2016
for recent upstream bugs.

Bug: 28867041
Bug: https://code.google.com/p/android/issues/detail?id=210205
Change-Id: Ibdf14d819411a12fcc78d012bfca97db048b7e6e
@rowi1de
Copy link

rowi1de commented Sep 27, 2016

@JakeWharton The validation can easily bypassed:

public LoginRequestBuilder addUmlauts(final String umlauts) {
        Map<String, List<String>> currentHeaders = builder.build().headers().toMultimap();
        Map<String, String> newHeaders = new HashMap<String, String>();
        for (Map.Entry<String, List<String>> entrySet : currentHeaders.entrySet()) {
            final String headerName = entrySet.getKey();
            builder.removeHeader(headerName);
            newHeaders.put(headerName, entrySet.getValue().get(0));
        }
        newHeaders.put("Umlauts", umlauts);
        builder.headers(Headers.of(newHeaders));
        return this;
    }

@beefeather
Copy link

I understand the current approach is to accept malformed header (with non-ascii characters), but to never allow generating them.
Could this be just too strict? I find it hardly usable for cookies. One is supposed to accept cookies and send them back. So once you got a (malformed) utf-8 cookie value, you have a problem with sending them back.
Do you think allowing this could become an option? A dirty method with an unpleasant name that swallows bad characters maybe? :)

@blu3-b1rd
Copy link

Updated to the latest version of the lib and still getting same error with the following header:

User-Agent: okhttp/3.3.0 (X325; X325–Locked to Life Wireless; X325; 4.4.2; 3.1.5; 216.48.89.45; alive)

Throwing this exception:

Unexpected char 0x2013 at 24 in User-Agent value: okhttp/3.3.0 (X325; X325–Locked to Life Wireless; X325; 4.4.2; 3.1.5; 216.48.89.45; alive)

@manjunath143
Copy link

I am also facing same error. I have updated to latest version still issue persist. @swankjesse do we have any workaround or any fix for this ?. Thank you for help advance.

@swankjesse
Copy link
Collaborator

@manjunath143 please open a pull request with an executable test case that demonstrates the failure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Bug in existing code
Projects
None yet
Development

Successfully merging a pull request may close this issue.