From b156e1982c09c29bdc2bc9db2a78ca295ead03a5 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Sun, 1 Jul 2018 23:57:46 -0400 Subject: [PATCH 1/4] Make sure body was successfully parsed as JSON before filtering --- lib/atom-io-client.coffee | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/atom-io-client.coffee b/lib/atom-io-client.coffee index 85e20ca1..af578a87 100644 --- a/lib/atom-io-client.coffee +++ b/lib/atom-io-client.coffee @@ -205,9 +205,13 @@ class AtomIoClient new Promise (resolve, reject) -> request options, (err, res, body) -> - if err + # Interestingly, request does not treat failure to parse JSON as an error + # and returns the raw HTML instead as a string. + # Therefore, make sure the body has been successfully parsed before + # trying to filter package results. + if err or typeof body isnt 'object' error = new Error("Searching for \u201C#{query}\u201D failed.") - error.stderr = err.message + error.stderr = err?.message ? "Please try again later." reject error else resolve( From a202ec1c9481d2e2844b8d183a458fccee72651f Mon Sep 17 00:00:00 2001 From: Winston Liu <50Wliu@users.noreply.github.com> Date: Wed, 4 Jul 2018 00:02:22 -0400 Subject: [PATCH 2/4] Don't ask request to do JSON parsing for us --- lib/atom-io-client.coffee | 45 ++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/lib/atom-io-client.coffee b/lib/atom-io-client.coffee index af578a87..5c613983 100644 --- a/lib/atom-io-client.coffee +++ b/lib/atom-io-client.coffee @@ -69,19 +69,23 @@ class AtomIoClient options = { url: "#{@baseURL}#{path}" headers: {'User-Agent': navigator.userAgent} - json: true gzip: true } request options, (err, res, body) => return callback(err) if err - delete body.versions - cached = - data: body - createdOn: Date.now() - localStorage.setItem(@cacheKeyForPath(path), JSON.stringify(cached)) - callback(err, cached.data) + try + body = JSON.parse(body) + delete body.versions + + cached = + data: body + createdOn: Date.now() + localStorage.setItem(@cacheKeyForPath(path), JSON.stringify(cached)) + callback(err, cached.data) + catch error + callback(error) cacheKeyForPath: (path) -> "settings-view:#{path}" @@ -199,24 +203,25 @@ class AtomIoClient url: "#{@baseURL}packages/search" headers: {'User-Agent': navigator.userAgent} qs: qs - json: true gzip: true } new Promise (resolve, reject) -> request options, (err, res, body) -> - # Interestingly, request does not treat failure to parse JSON as an error - # and returns the raw HTML instead as a string. - # Therefore, make sure the body has been successfully parsed before - # trying to filter package results. - if err or typeof body isnt 'object' + if err error = new Error("Searching for \u201C#{query}\u201D failed.") - error.stderr = err?.message ? "Please try again later." + error.stderr = err.message reject error else - resolve( - body.filter (pkg) -> pkg.releases?.latest? - .map ({readme, metadata, downloads, stargazers_count}) -> - Object.assign metadata, {readme, downloads, stargazers_count} - .sort (a, b) -> b.downloads - a.downloads - ) + try + body = JSON.parse(body) + resolve( + body.filter (pkg) -> pkg.releases?.latest? + .map ({readme, metadata, downloads, stargazers_count}) -> + Object.assign metadata, {readme, downloads, stargazers_count} + .sort (a, b) -> b.downloads - a.downloads + ) + catch e + error = new Error("Searching for \u201C#{query}\u201D failed.") + error.stderr = e.message + '\n' + body + reject error From 039d6c7919bda8c36a74e7586549d911b31be3dc Mon Sep 17 00:00:00 2001 From: Winston Liu <50Wliu@users.noreply.github.com> Date: Sat, 3 Nov 2018 01:04:56 -0400 Subject: [PATCH 3/4] Make sure request returns an error on invalid JSON --- spec/atom-io-client-spec.coffee | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/spec/atom-io-client-spec.coffee b/spec/atom-io-client-spec.coffee index 2e5f8923..770d6d0c 100644 --- a/spec/atom-io-client-spec.coffee +++ b/spec/atom-io-client-spec.coffee @@ -11,15 +11,29 @@ describe "AtomIoClient", -> expect(@client.fetchAndCacheAvatar).not.toHaveBeenCalled() @client.avatar 'test-user', -> - it "fetches api json from cache if the network is unavailable", -> - spyOn(@client, 'online').andReturn(false) - spyOn(@client, 'fetchFromCache').andCallFake (path, opts, cb) -> - cb(null, {}) - spyOn(@client, 'request') - @client.package 'test-package', -> - - expect(@client.fetchFromCache).toHaveBeenCalled() - expect(@client.request).not.toHaveBeenCalled() + describe "request", -> + it "fetches api json from cache if the network is unavailable", -> + spyOn(@client, 'online').andReturn(false) + spyOn(@client, 'fetchFromCache').andCallFake (path, opts, cb) -> + cb(null, {}) + spyOn(@client, 'request') + @client.package 'test-package', -> + + expect(@client.fetchFromCache).toHaveBeenCalled() + expect(@client.request).not.toHaveBeenCalled() + + it "returns an error if the API response is not JSON", -> + jsonParse = JSON.parse + + waitsFor (done) -> + spyOn(JSON, 'parse').andThrow() + @client.request 'path', (error, data) -> + expect(error).not.toBeNull() + done() + + runs -> + # Tests will throw without this as cleanup requires JSON.parse to work + JSON.parse = jsonParse it "handles glob errors", -> spyOn(@client, 'avatarGlob').andReturn "#{__dirname}/**" From 9c82f20ee180b0cf5f7d18710a65e24fdfb6e183 Mon Sep 17 00:00:00 2001 From: Winston Liu <50Wliu@users.noreply.github.com> Date: Sat, 3 Nov 2018 01:07:24 -0400 Subject: [PATCH 4/4] :memo: --- lib/atom-io-client.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/atom-io-client.coffee b/lib/atom-io-client.coffee index 5c613983..695b99cf 100644 --- a/lib/atom-io-client.coffee +++ b/lib/atom-io-client.coffee @@ -76,6 +76,8 @@ class AtomIoClient return callback(err) if err try + # NOTE: request's json option does not populate err if parsing fails, + # so we do it manually body = JSON.parse(body) delete body.versions @@ -214,6 +216,8 @@ class AtomIoClient reject error else try + # NOTE: request's json option does not populate err if parsing fails, + # so we do it manually body = JSON.parse(body) resolve( body.filter (pkg) -> pkg.releases?.latest?