Skip to content

Handle duplicate headers in Retrofit2Helper #905

@53c70r

Description

@53c70r

A bug in Retrofit2Helper causes a crash when the server sends multiple headers with the same name. This happens because Collectors.toMap is used without a merge function to handle duplicate keys.

Steps to reproduce:

  1. Use the Nextcloud Notes app that uses the SSO library.
  2. Perform an action that results in an HTTP response with duplicate headers (e.g., X-Robots-Tag).
  3. The app will crash with an IllegalStateException: Duplicate key.

Error in Android App:

10-06 16:24:17.514  7178  9545 E NotesRepository: java.lang.Exception: Duplicate key X-Robots-Tag (attempted merging values noindex, nofollow and noindex, nofollow)
    at java.util.stream.Collectors.duplicateKeyException(Collectors.java:135)
    at java.util.stream.Collectors.lambda$uniqKeysMapAccumulator$1(Collectors.java:182)
    at java.util.stream.Collectors$$ExternalSyntheticLambda1.accept(D8$$SyntheticClass:0)
    at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1725)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:513)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:503)
    at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:236)
    at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:705)
    at com.nextcloud.android.sso.helper.Retrofit2Helper$1.lambda$execute$0(Retrofit2Helper.java:57)
    at com.nextcloud.android.sso.helper.Retrofit2Helper$1$$ExternalSyntheticLambda2.apply(D8$$SyntheticClass:0)
    at java.util.Optional.map(Optional.java:260)
    at com.nextcloud.android.sso.helper.Retrofit2Helper$1.execute(Retrofit2Helper.java:55)
    at it.niedermann.owncloud.notes.persistence.NotesServerSyncTask.pushLocalChanges(NotesServerSyncTask.java:130)
    at it.niedermann.owncloud.notes.persistence.NotesServerSyncTask.run(NotesServerSyncTask.java:99)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:524)
    at java.util.concurrent.FutureTask.run(FutureTask.java:317)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1156)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:651)
    at java.lang.Thread.run(Thread.java:1119)

Http request:

Server: nginx
Date: XXX
Content-Type: application/json; charset=utf-8
Content-Length: 502
Connection: close
X-Request-Id: XXX
Cache-Control: no-cache, no-store, must-revalidate
Content-Security-Policy: default-src 'none';base-uri 'none';manifest-src 'self';frame-ancestors 'none'
Feature-Policy: autoplay 'none';camera 'none';fullscreen 'none';geolocation 'none';microphone 'none';payment 'none'
X-Robots-Tag: noindex, nofollow
Content-Encoding: gzip
Referrer-Policy: no-referrer
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Permitted-Cross-Domain-Policies: none
X-Robots-Tag: noindex, nofollow
X-XSS-Protection: 1; mode=block

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions