Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
fix: defensive JSON parsing for params + IAM (SDK-4478, SDK-4494) #2638
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
Uh oh!
There was an error while loading. Please reload this page.
fix: defensive JSON parsing for params + IAM (SDK-4478, SDK-4494) #2638
Changes from all commits
7e31e169c16721debc1d2fb45e97File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
There are no files selected for viewing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟣 Pre-existing issue worth flagging while in this area: the new try/catch only wraps the top-level
JSONObject(payload ?: "")parse, but the same uncaught-JSONException-kills-the-refresh-loop failure mode can still occur from the field-extraction calls below it. ThesafeBool/safeString/safeLong/expandJSONObjecthelpers inJSONObjectExtensions.ktuse strictgetXxx(notoptXxx) and throw on type mismatches, e.g. a 2xx body like{"outcomes":"forbidden"},{"fcm":[]}, or{"enterp":"yes"}. Trigger plausibility is much narrower than the HTML/captive-portal case this PR targets (would require a server-side schema regression or a proxy that forges valid JSON with wrong types), but since the title frames the fix as protecting the loop from uncaughtJSONException, it'd be a small extension to either widen the try/catch through thereturn ParamsObject(...)block or have thesafe*helpers fall back tooptXxx.Extended reasoning...
Bug
After this PR,
ParamsBackendService.fetchParams(ParamsBackendService.kt:43-58) catchesJSONExceptiononly around the top-levelJSONObject(payload ?: "")construction. Once that succeeds and the try/catch is exited, the rest of the function still touchesresponseJsonthrough helpers that throw raworg.json.JSONExceptionon type mismatches:responseJson.expandJSONObject("outcomes")/("fcm")/("logging_config")—JSONObjectExtensions.kt:148-155callsgetJSONObject(name), which throws if the value at the key is a String, Number, Array, or null.responseJson.safeBool("enterp")/("fba")/etc —JSONObjectExtensions.kt:59-65callsgetBoolean(name), which throws on values like"yes",1, ornull.responseJson.safeLong("oprepo_execution_interval")—JSONObjectExtensions.kt:29-35callsgetLong(name), throws on non-coercible values.responseJson.safeString(...)— callsgetString(name), throws onJSONObject.NULL.The caller
ConfigModelStoreListener.fetchParams(ConfigModelStoreListener.kt:116) catches onlyBackendException. AJSONExceptionpropagates out of the do-while retry loop and out of thesuspendifyOnIOcoroutine — exactly the failure mode the PR's title claims to address ("handle non-JSON android_params.js response without killing refresh loop").Step-by-step proof
Consider a 2xx response body
{"outcomes":"forbidden"}:response.isSuccessis true → noBackendExceptionthrown at line 41.payload = "{\"outcomes\":\"forbidden\"}".JSONObject(payload)succeeds — the body is syntactically valid JSON. The new try/catch is exited cleanly.responseJson.expandJSONObject("outcomes")checkshas("outcomes")→ true. Then callsthis.getJSONObject("outcomes"). The value is"forbidden"(String), not a JSONObject →org.json.JSON.typeMismatch(...)throwsJSONException.fetchParams. The caller'scatch (ex: BackendException)does not match.suspendifyOnIO/suspendifyWithCompletion(ThreadUtils.kt) catchesExceptiononly to log it — the coroutine still terminates and no further retries fire for the rest of the session.Same outcome for
{"fcm":[]},{"enterp":"yes"},{"enterp":1},{"logging_config":null}, etc.Why I'm filing as pre_existing
The refutation makes a fair scope argument: this hazard predates the PR (the
safe*helpers andexpandJSONObjectalready used strictgetXxxon master), and the SDK-4478 incident is about HTML bodies — a top-level parse failure that the PR fully addresses. The trigger for the remaining hazard (a server returning syntactically-valid JSON with semantically-wrong types, or a proxy doing the same) is materially less plausible than the HTML interception case.That said, the PR's title and description frame the fix as defending the refresh loop against uncaught
JSONExceptiongenerally, and the same exception class can still kill the loop from one line below the new catch. Worth surfacing as a code-review note while the author is in this code; not a blocker.Suggested fix
Either approach is one-line-ish:
responseJsonthrough thereturn ParamsObject(...)(i.e. move thereturninside thetryblock, with thecatchrethrowingBackendExceptionas it does today).safe*helpers inJSONObjectExtensions.ktto useoptInt/optLong/optBoolean/optString/optJSONObject— but that has wider blast radius across the codebase, so option 1 is more targeted.Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.