diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/cache/test | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/cache/test')
66 files changed, 6355 insertions, 0 deletions
diff --git a/dom/cache/test/browser/browser.ini b/dom/cache/test/browser/browser.ini new file mode 100644 index 0000000000..90a7a53a3a --- /dev/null +++ b/dom/cache/test/browser/browser.ini @@ -0,0 +1 @@ +[browser_cache_pb_window.js] diff --git a/dom/cache/test/browser/browser_cache_pb_window.js b/dom/cache/test/browser/browser_cache_pb_window.js new file mode 100644 index 0000000000..40a6da7005 --- /dev/null +++ b/dom/cache/test/browser/browser_cache_pb_window.js @@ -0,0 +1,169 @@ +/* eslint-disable no-shadow */ +var name = "pb-window-cache"; + +function testMatch(browser) { + return SpecialPowers.spawn(browser, [name], function (name) { + return new Promise((resolve, reject) => { + content.caches + .match("http://foo.com") + .then(function (response) { + ok(false, "caches.match() should not return success"); + reject(); + }) + .catch(function (err) { + is( + "SecurityError", + err.name, + "caches.match() should throw SecurityError" + ); + resolve(); + }); + }); + }); +} + +function testHas(browser) { + return SpecialPowers.spawn(browser, [name], function (name) { + return new Promise(function (resolve, reject) { + content.caches + .has(name) + .then(function (result) { + ok(false, "caches.has() should not return success"); + reject(); + }) + .catch(function (err) { + is( + "SecurityError", + err.name, + "caches.has() should throw SecurityError" + ); + resolve(); + }); + }); + }); +} + +function testOpen(browser) { + return SpecialPowers.spawn(browser, [name], function (name) { + return new Promise(function (resolve, reject) { + content.caches + .open(name) + .then(function (c) { + ok(false, "caches.open() should not return success"); + reject(); + }) + .catch(function (err) { + is( + "SecurityError", + err.name, + "caches.open() should throw SecurityError" + ); + resolve(); + }); + }); + }); +} + +function testDelete(browser) { + return SpecialPowers.spawn(browser, [name], function (name) { + return new Promise(function (resolve, reject) { + content.caches + .delete(name) + .then(function (result) { + ok(false, "caches.delete() should not return success"); + reject(); + }) + .catch(function (err) { + is( + "SecurityError", + err.name, + "caches.delete() should throw SecurityError" + ); + resolve(); + }); + }); + }); +} + +function testKeys(browser) { + return SpecialPowers.spawn(browser, [name], function (name) { + return new Promise(function (resolve, reject) { + content.caches + .keys() + .then(function (names) { + ok(false, "caches.keys() should not return success"); + reject(); + }) + .catch(function (err) { + is( + "SecurityError", + err.name, + "caches.keys() should throw SecurityError" + ); + resolve(); + }); + }); + }); +} + +function testOpen_worker(browser) { + return SpecialPowers.spawn(browser, [], function () { + let workerFunctionString = function () { + caches.open("pb-worker-cache").then( + function (cacheObject) { + postMessage(cacheObject.toString()); + }, + function (reason) { + postMessage(reason.name); + } + ); + }.toString(); + let workerBlobURL = content.URL.createObjectURL( + new Blob(["(", workerFunctionString, ")()"], { + type: "application/javascript", + }) + ); + let worker = new content.Worker(workerBlobURL); + content.URL.revokeObjectURL(workerBlobURL); + return new Promise(function (resolve, reject) { + worker.addEventListener("message", function (e) { + let isGood = e.data === "SecurityError"; + ok(isGood, "caches.open() should throw SecurityError from worker"); + isGood ? resolve() : reject(); + }); + }); + }); +} + +function test() { + let privateWin, privateTab; + waitForExplicitFinish(); + SpecialPowers.pushPrefEnv({ + set: [ + ["dom.caches.enabled", true], + ["dom.caches.testing.enabled", true], + ], + }) + .then(() => { + return BrowserTestUtils.openNewBrowserWindow({ private: true }); + }) + .then(pw => { + privateWin = pw; + privateTab = BrowserTestUtils.addTab(pw.gBrowser, "http://example.com/"); + return BrowserTestUtils.browserLoaded(privateTab.linkedBrowser); + }) + .then(tab => { + return Promise.all([ + testMatch(privateTab.linkedBrowser), + testHas(privateTab.linkedBrowser), + testOpen(privateTab.linkedBrowser), + testDelete(privateTab.linkedBrowser), + testKeys(privateTab.linkedBrowser), + testOpen_worker(privateTab.linkedBrowser), + ]); + }) + .then(() => { + return BrowserTestUtils.closeWindow(privateWin); + }) + .then(finish); +} diff --git a/dom/cache/test/marionette/manifest.ini b/dom/cache/test/marionette/manifest.ini new file mode 100644 index 0000000000..c0bf3194b2 --- /dev/null +++ b/dom/cache/test/marionette/manifest.ini @@ -0,0 +1 @@ +[test_caches_delete_cleanup_after_shutdown.py] diff --git a/dom/cache/test/marionette/test_caches_delete_cleanup_after_shutdown.py b/dom/cache/test/marionette/test_caches_delete_cleanup_after_shutdown.py new file mode 100644 index 0000000000..4427d1c9c7 --- /dev/null +++ b/dom/cache/test/marionette/test_caches_delete_cleanup_after_shutdown.py @@ -0,0 +1,181 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +from marionette_driver import Wait +from marionette_harness import MarionetteTestCase + +""" + Currently we expect our size after cleanup to be 98k for the current + database schema and page sizes and constants used in this test. We + set the threshold at 128k so the test doesn't start failing if these + control objects/structures increase somewhat in size, but should still + fail if we fail to delete all of the 5,000 1k files we create on disk + as part of the test. +""" +EXPECTED_CACHEDIR_SIZE_AFTER_CLEANUP = 128 * 1024 # 128KB +CACHE_ID = "data" + + +class CachesDeleteCleanupAtShutdownTestCase(MarionetteTestCase): + + """ + Bug1784700: This test ensures that cache body files get cleaned up + properly after cache has been deleted. Note that body files gets + cleaned up asynchronously which means that these files might still + be around even after Caches.Delete promise gets resolved. Currently, + we would only clean up body files on origin initialization and that's + a firefox is necessary here. + """ + + def setUp(self): + super(CachesDeleteCleanupAtShutdownTestCase, self).setUp() + self.marionette.restart(in_app=False, clean=True) + + def tearDown(self): + self.marionette.restart(in_app=False, clean=True) + super(CachesDeleteCleanupAtShutdownTestCase, self).tearDown() + + def getUsage(self): + return self.marionette.execute_script( + """ + return window.wrappedJSObject.getStorageEstimate(); + """, + new_sandbox=False, + ) + + def doCacheWork(self, n): + # max timeout for this script to execute is 5 minutes + maxTimeout = 5 * 60 * 1000 + + return self.marionette.execute_script( + """ + const [cacheId, n] = arguments; + return window.wrappedJSObject.doCacheWork(cacheId, n); + """, + script_args=( + CACHE_ID, + n, + ), + new_sandbox=False, + script_timeout=maxTimeout, + ) + + def openCache(self): + return self.marionette.execute_async_script( + """ + const [cacheId, resolve] = arguments; + window.wrappedJSObject.openCache(cacheId).then(resolve("success")); + """, + new_sandbox=False, + script_args=(CACHE_ID,), + ) + + def ensureCleanDirectory(self): + with self.marionette.using_context("chrome"): + return self.marionette.execute_script( + """ + let originDir = arguments[0]; + const pathDelimiter = "/"; + + function getRelativeFile(relativePath) { + let file = Services.dirsvc + .get("ProfD", Ci.nsIFile) + .clone(); + + relativePath.split(pathDelimiter).forEach(function(component) { + if (component == "..") { + file = file.parent; + } else { + file.append(component); + } + }); + + return file; + } + + function getCacheDir() { + + const storageDirName = "storage"; + const defaultPersistenceDirName = "default"; + + return getRelativeFile( + `${storageDirName}/${defaultPersistenceDirName}/${originDir}/cache` + ); + } + + const cacheDir = getCacheDir(); + let morgueDir = cacheDir.clone(); + + // morgue directory should be empty + // or atleast directories under morgue should be empty + morgueDir.append("morgue"); + for (let dir of morgueDir.directoryEntries) { + for (let file of dir.directoryEntries) { + return false; + } + } + return true; + """, + script_args=( + self.marionette.absolute_url("")[:-1] + .replace(":", "+") + .replace("/", "+"), + ), + new_sandbox=False, + ) + + def create_and_cleanup_cache(self, ensureCleanCallback, in_app): + # create 640 cache entries + self.doCacheWork(640) + + self.marionette.restart(in_app=in_app) + print("restart successful") + + self.marionette.navigate(self.marionette.absolute_url("dom/cacheUsage.html")) + return ensureCleanCallback() + + def test_ensure_cache_cleanup_after_clean_restart(self): + self.marionette.navigate(self.marionette.absolute_url("dom/cacheUsage.html")) + beforeUsage = self.getUsage() + + def ensureCleanCallback(): + + Wait(self.marionette, timeout=60).until( + lambda x: (self.getUsage() - beforeUsage) + < EXPECTED_CACHEDIR_SIZE_AFTER_CLEANUP, + message="Cache directory is not cleaned up properly", + ) + + return ( + abs(beforeUsage - self.getUsage()) + <= EXPECTED_CACHEDIR_SIZE_AFTER_CLEANUP + and self.ensureCleanDirectory() + ) + + if not self.create_and_cleanup_cache(ensureCleanCallback, True): + print(f"beforeUsage = {beforeUsage}, and afterUsage = {self.getUsage()}") + assert False + + def test_ensure_cache_cleanup_after_unclean_restart(self): + self.marionette.navigate(self.marionette.absolute_url("dom/cacheUsage.html")) + beforeUsage = self.getUsage() + + def ensureCleanCallback(): + self.openCache() + + Wait(self.marionette, timeout=60).until( + lambda x: (self.getUsage() - beforeUsage) + < EXPECTED_CACHEDIR_SIZE_AFTER_CLEANUP, + message="Cache directory is not cleaned up properly", + ) + + return ( + abs(beforeUsage - self.getUsage()) + <= EXPECTED_CACHEDIR_SIZE_AFTER_CLEANUP + and self.ensureCleanDirectory() + ) + + if not self.create_and_cleanup_cache(ensureCleanCallback, False): + print(f"beforeUsage = {beforeUsage}, and afterUsage = {self.getUsage()}") + assert False diff --git a/dom/cache/test/mochitest/driver.js b/dom/cache/test/mochitest/driver.js new file mode 100644 index 0000000000..4ea3e2ad03 --- /dev/null +++ b/dom/cache/test/mochitest/driver.js @@ -0,0 +1,157 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ +// +// This helper script exposes a runTests function that takes the name of a +// test script as its input argument and runs the test in three different +// contexts: +// 1. Regular Worker context +// 2. Service Worker context +// 3. Window context +// The function returns a promise which will get resolved once all tests +// finish. The testFile argument is the name of the test file to be run +// in the different contexts, and the optional order argument can be set +// to either "parallel" or "sequential" depending on how the caller wants +// the tests to be run. If this argument is not provided, the default is +// "both", which runs the tests in both modes. +// The caller of this function is responsible to call SimpleTest.finish +// when the returned promise is resolved. + +function runTests(testFile, order) { + async function setupPrefs() { + // Bug 1746646: Make mochitests work with TCP enabled (cookieBehavior = 5) + // Acquire storage access permission here so that the Cache API is avaialable + SpecialPowers.wrap(document).notifyUserGestureActivation(); + await SpecialPowers.addPermission( + "storageAccessAPI", + true, + window.location.href + ); + await SpecialPowers.wrap(document).requestStorageAccess(); + return SpecialPowers.pushPrefEnv({ + set: [ + ["dom.caches.enabled", true], + ["dom.caches.testing.enabled", true], + ["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.testing.enabled", true], + ["dom.serviceWorkers.exemptFromPerDomainMax", true], + [ + "privacy.partition.always_partition_third_party_non_cookie_storage", + false, + ], + ], + }); + } + + // adapted from dom/indexedDB/test/helpers.js + function clearStorage() { + var clearUnpartitionedStorage = new Promise(function (resolve, reject) { + var qms = SpecialPowers.Services.qms; + var principal = SpecialPowers.wrap(document).nodePrincipal; + var request = qms.clearStoragesForPrincipal(principal); + var cb = SpecialPowers.wrapCallback(resolve); + request.callback = cb; + }); + var clearPartitionedStorage = new Promise(function (resolve, reject) { + var qms = SpecialPowers.Services.qms; + var principal = SpecialPowers.wrap(document).partitionedPrincipal; + var request = qms.clearStoragesForPrincipal(principal); + var cb = SpecialPowers.wrapCallback(resolve); + request.callback = cb; + }); + return Promise.all([clearUnpartitionedStorage, clearPartitionedStorage]); + } + + function loadScript(script) { + return new Promise(function (resolve, reject) { + var s = document.createElement("script"); + s.src = script; + s.onerror = reject; + s.onload = resolve; + document.body.appendChild(s); + }); + } + + function importDrivers() { + /* import-globals-from worker_driver.js */ + /* import-globals-from serviceworker_driver.js */ + return Promise.all([ + loadScript("worker_driver.js"), + loadScript("serviceworker_driver.js"), + ]); + } + + function runWorkerTest() { + return workerTestExec(testFile); + } + + function runServiceWorkerTest() { + return serviceWorkerTestExec(testFile); + } + + function runFrameTest() { + return new Promise(function (resolve, reject) { + var iframe = document.createElement("iframe"); + iframe.src = "frame.html"; + iframe.onload = function () { + var doc = iframe.contentDocument; + var s = doc.createElement("script"); + s.src = testFile; + window.addEventListener("message", function onMessage(event) { + if (event.data.context != "Window") { + return; + } + if (event.data.type == "finish") { + window.removeEventListener("message", onMessage); + resolve(); + } else if (event.data.type == "status") { + ok(event.data.status, event.data.context + ": " + event.data.msg); + } + }); + doc.body.appendChild(s); + }; + document.body.appendChild(iframe); + }); + } + + SimpleTest.waitForExplicitFinish(); + + if (typeof order == "undefined") { + order = "sequential"; // sequential by default, see bug 1143222. + // TODO: Make this "both" again. + } + + ok( + order == "parallel" || order == "sequential" || order == "both", + "order argument should be valid" + ); + + if (order == "both") { + info("Running tests in both modes; first: sequential"); + return runTests(testFile, "sequential").then(function () { + info("Running tests in parallel mode"); + return runTests(testFile, "parallel"); + }); + } + if (order == "sequential") { + return setupPrefs() + .then(importDrivers) + .then(runWorkerTest) + .then(clearStorage) + .then(runServiceWorkerTest) + .then(clearStorage) + .then(runFrameTest) + .then(clearStorage) + .catch(function (e) { + ok(false, "A promise was rejected during test execution: " + e); + }); + } + return setupPrefs() + .then(importDrivers) + .then(() => + Promise.all([runWorkerTest(), runServiceWorkerTest(), runFrameTest()]) + ) + .then(clearStorage) + .catch(function (e) { + ok(false, "A promise was rejected during test execution: " + e); + }); +} diff --git a/dom/cache/test/mochitest/empty.html b/dom/cache/test/mochitest/empty.html new file mode 100644 index 0000000000..5b56631db7 --- /dev/null +++ b/dom/cache/test/mochitest/empty.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<!-- This is only used to give us access to the caches global after setting the pref. --> diff --git a/dom/cache/test/mochitest/frame.html b/dom/cache/test/mochitest/frame.html new file mode 100644 index 0000000000..0e6dd35a24 --- /dev/null +++ b/dom/cache/test/mochitest/frame.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<script> + var context = "Window"; + function ok(a, msg) { + parent.postMessage({type: "status", status: !!a, + msg: a + ": " + msg, context}, "*"); + } + + function is(a, b, msg) { + parent.postMessage({type: "status", status: a === b, + msg: a + " === " + b + ": " + msg, context}, "*"); + } + + function testDone() { + parent.postMessage({type: "finish", context}, "*"); + } +</script> diff --git a/dom/cache/test/mochitest/idle_worker.js b/dom/cache/test/mochitest/idle_worker.js new file mode 100644 index 0000000000..baba3ed4dc --- /dev/null +++ b/dom/cache/test/mochitest/idle_worker.js @@ -0,0 +1,12 @@ +// Touch CacheStorage, but then idle and do nothing. +const name = "idle_worker_cache"; +var cache; +self.caches + .open(name) + .then(c => { + cache = c; + return self.caches.delete(name); + }) + .then(_ => { + postMessage("LOADED"); + }); diff --git a/dom/cache/test/mochitest/large_url_list.js b/dom/cache/test/mochitest/large_url_list.js new file mode 100644 index 0000000000..db24c790fc --- /dev/null +++ b/dom/cache/test/mochitest/large_url_list.js @@ -0,0 +1,1002 @@ +var largeUrlList = [ + "https://example.com/cia5rherm0000hkjx7r8of8b1/cia5rhern0001hkjxes0mptxq/cia5rhern0002hkjx0ze9t1oy/cia5rhern0003hkjxa07rei71", + "https://example.com/cia5rhern0004hkjx15dh2a23/cia5rhern0005hkjxukeg547n/cia5rhern0006hkjxn78qlfk0/cia5rhern0007hkjxx7pkqem5", + "https://example.com/cia5rhern0008hkjx6xh95dcp/cia5rhern0009hkjxu3212vkx/cia5rhern000ahkjxlhu69sv7/cia5rhern000bhkjxwffaxb8c", + "https://example.com/cia5rhern000chkjxylxzrc0l/cia5rhern000dhkjxoe8p7jap/cia5rhern000ehkjx49ssgz0i/cia5rhern000fhkjxlqcm6jq9", + "https://example.com/cia5rhern000ghkjxjfag4uqk/cia5rhern000hhkjxxjmvakfj/cia5rhern000ihkjxp5j7d9ic/cia5rhern000jhkjxdog5rxfb", + "https://example.com/cia5rhern000khkjxrdrwsflr/cia5rhern000lhkjxjqj2ydrd/cia5rhern000mhkjxujiqkc1c/cia5rhern000nhkjxw0nk2nvg", + "https://example.com/cia5rhern000ohkjxpglj3lmb/cia5rhern000phkjxjekhrrqd/cia5rhern000qhkjxalsyb73l/cia5rhern000rhkjxyjbnn6er", + "https://example.com/cia5rhero000shkjxmhknjjrv/cia5rhero000thkjx8xtfdrgh/cia5rhero000uhkjxpe3dwxk6/cia5rhero000vhkjxint5ohhn", + "https://example.com/cia5rhero000whkjxvaiypkdj/cia5rhero000xhkjxaqm599g5/cia5rhero000yhkjxivrhnj8f/cia5rhero000zhkjxvmd4t8h6", + "https://example.com/cia5rhero0010hkjxsx3ke38t/cia5rhero0011hkjx8xnw9tqf/cia5rhero0012hkjxl0lf3iup/cia5rhero0013hkjxbvhj1jj9", + "https://example.com/cia5rhero0014hkjx4fmpoeu0/cia5rhero0015hkjxwpptyghv/cia5rhero0016hkjxva5k7lbw/cia5rhero0017hkjx138e6bmj", + "https://example.com/cia5rhero0018hkjxx7j78vei/cia5rhero0019hkjx6xrih33b/cia5rhero001ahkjxd4zgngrg/cia5rhero001bhkjx1tloxb1q", + "https://example.com/cia5rhero001chkjxrt8zdtde/cia5rhero001dhkjxm9mbqci7/cia5rhero001ehkjxowk5e4q6/cia5rhero001fhkjxh6qmsl1v", + "https://example.com/cia5rhero001ghkjxfadnveq2/cia5rhero001hhkjx4edkaq54/cia5rhero001ihkjx9fqoqxga/cia5rhero001jhkjx58vqa1zr", + "https://example.com/cia5rhero001khkjxdrmypltg/cia5rhero001lhkjxhqdzis67/cia5rhero001mhkjxcggzuqdx/cia5rhero001nhkjxctfvi81n", + "https://example.com/cia5rhero001ohkjxe8pk4cgt/cia5rhero001phkjx6sm4qi4x/cia5rhero001qhkjxm9lqhc6e/cia5rhero001rhkjxy9k6yacj", + "https://example.com/cia5rhero001shkjx0zfh0f80/cia5rhero001thkjx06fdx9h9/cia5rherp001uhkjxqdmh6jtq/cia5rherp001vhkjxywng3c85", + "https://example.com/cia5rherp001whkjxm2qihavv/cia5rherp001xhkjxw9v508hp/cia5rherp001yhkjx3jtn1gc8/cia5rherp001zhkjxsmvesh8h", + "https://example.com/cia5rherp0020hkjxz8yaovlg/cia5rherp0021hkjx66mvfalu/cia5rherp0022hkjxqpilypyo/cia5rherp0023hkjxsp9txbui", + "https://example.com/cia5rherp0024hkjxcc9ni9ai/cia5rherp0025hkjxslerqaui/cia5rherp0026hkjxe0jl3k05/cia5rherp0027hkjxr0hf5tjn", + "https://example.com/cia5rherp0028hkjxsg9l2j7w/cia5rherp0029hkjx8zs585su/cia5rherp002ahkjxuwrjr76m/cia5rherp002bhkjxh5nsz7nf", + "https://example.com/cia5rherp002chkjxzph0v3u0/cia5rherp002dhkjxdayhh6lg/cia5rherp002ehkjxrkvcxz41/cia5rherp002fhkjxlqa9ul9r", + "https://example.com/cia5rherp002ghkjxf6egbyrh/cia5rherp002hhkjxaw3isyif/cia5rherp002ihkjxboruj7fc/cia5rherp002jhkjx6fgq322u", + "https://example.com/cia5rherp002khkjx5adgzcsd/cia5rherp002lhkjxo84dwluf/cia5rherp002mhkjxijwq9esb/cia5rherp002nhkjx59ky0n1a", + "https://example.com/cia5rherp002ohkjx8r9ldy53/cia5rherp002phkjxn5vwv7yi/cia5rherp002qhkjxdsg26179/cia5rherp002rhkjxxvufen34", + "https://example.com/cia5rherp002shkjxj17ade31/cia5rherp002thkjx37xtqdld/cia5rherp002uhkjxbl9a3b96/cia5rherp002vhkjxrq82quxi", + "https://example.com/cia5rherp002whkjxj0kiekos/cia5rherp002xhkjxltksjia2/cia5rherp002yhkjx9f6j16y4/cia5rherp002zhkjxub535mh5", + "https://example.com/cia5rherp0030hkjxsd01uqhn/cia5rherp0031hkjxxy7v0enj/cia5rherp0032hkjxm7afimyn/cia5rherp0033hkjxu9rm6wnw", + "https://example.com/cia5rherp0034hkjxoe7lub90/cia5rherp0035hkjxdqfkfwe5/cia5rherp0036hkjx7bydg1o0/cia5rherp0037hkjxwiayjj4h", + "https://example.com/cia5rherp0038hkjxrxyltlgt/cia5rherp0039hkjxq8m8gzd9/cia5rherp003ahkjxfzlwk84z/cia5rherp003bhkjxhzsd9psq", + "https://example.com/cia5rherp003chkjx8k4d0ev6/cia5rherp003dhkjxe5rhd9bk/cia5rherp003ehkjxhdb9pe1z/cia5rherp003fhkjxnwfpch8g", + "https://example.com/cia5rherp003ghkjxv07j52h2/cia5rherp003hhkjx1i0x37cm/cia5rherp003ihkjxey8otr6v/cia5rherp003jhkjxk1np7zo5", + "https://example.com/cia5rherp003khkjxscsb30qa/cia5rherp003lhkjxcuap7ls3/cia5rherq003mhkjxsdoqgpxu/cia5rherq003nhkjxz21dqdpc", + "https://example.com/cia5rherq003ohkjx66ms0nn0/cia5rherq003phkjxb8hblxdd/cia5rherq003qhkjxmr2bkt0b/cia5rherq003rhkjxkla58f3d", + "https://example.com/cia5rherr003shkjxutmljflc/cia5rherr003thkjx9sglm092/cia5rherr003uhkjx6ttae5q1/cia5rherr003vhkjx2i22wbci", + "https://example.com/cia5rherr003whkjx0gmqk0qu/cia5rherr003xhkjxnnopsaqa/cia5rherr003yhkjxhicji4pa/cia5rherr003zhkjxg7dm7usj", + "https://example.com/cia5rherr0040hkjxqymjv2aj/cia5rherr0041hkjxgjublrsc/cia5rherr0042hkjxu34zz1y2/cia5rherr0043hkjx0v1t6s9s", + "https://example.com/cia5rherr0044hkjxourvl7pc/cia5rherr0045hkjxem1bv16o/cia5rherr0046hkjxlza5reyz/cia5rherr0047hkjxc0hqmpn8", + "https://example.com/cia5rherr0048hkjxab7n8nos/cia5rherr0049hkjxjwltgxvq/cia5rherr004ahkjxpskjnyvt/cia5rherr004bhkjx9t1zafr8", + "https://example.com/cia5rherr004chkjxos007jlr/cia5rherr004dhkjxside72xk/cia5rherr004ehkjxmsb8r01v/cia5rherr004fhkjx1c1v6ypg", + "https://example.com/cia5rherr004ghkjxl9hin1sd/cia5rherr004hhkjx1ktxr1zz/cia5rherr004ihkjxjnq6wq6b/cia5rherr004jhkjx6z9ffji0", + "https://example.com/cia5rherr004khkjxxu7r4mzw/cia5rherr004lhkjxj9vru5i2/cia5rherr004mhkjxowwrqrzg/cia5rherr004nhkjxejliyhz4", + "https://example.com/cia5rherr004ohkjx23jh1sos/cia5rherr004phkjxrq8hqfpy/cia5rherr004qhkjx4232owb6/cia5rherr004rhkjx6dbg83qw", + "https://example.com/cia5rherr004shkjx6t43ggcp/cia5rherr004thkjxmf2k2w6n/cia5rherr004uhkjxwhhmhvx5/cia5rherr004vhkjxt4qahjbz", + "https://example.com/cia5rherr004whkjx0bkl7a31/cia5rherr004xhkjxk34mm030/cia5rherr004yhkjxoundtp7u/cia5rherr004zhkjxr1htazrx", + "https://example.com/cia5rherr0050hkjxfm9e7rcy/cia5rherr0051hkjxvp7y4api/cia5rherr0052hkjxdairex18/cia5rherr0053hkjxoqt8of5n", + "https://example.com/cia5rherr0054hkjxbg8to9br/cia5rherr0055hkjxymwr54ie/cia5rherr0056hkjxez64qj8r/cia5rherr0057hkjxann7kwhj", + "https://example.com/cia5rherr0058hkjxov69tz76/cia5rherr0059hkjxxh6rrvs8/cia5rherr005ahkjxwsmsljoc/cia5rherr005bhkjxi5o005me", + "https://example.com/cia5rherr005chkjxu6w2wby8/cia5rherr005dhkjxvcmga3pp/cia5rherr005ehkjxcr90eaeq/cia5rherr005fhkjxon6fcg5h", + "https://example.com/cia5rherr005ghkjxqo4vucke/cia5rherr005hhkjxxvdv1j8f/cia5rherr005ihkjxo2vl23qb/cia5rherr005jhkjx3tbu637k", + "https://example.com/cia5rherr005khkjx8fnhdy4n/cia5rherr005lhkjxvgvc27dd/cia5rherr005mhkjxydbxzvrw/cia5rherr005nhkjx0ev5rnx0", + "https://example.com/cia5rherr005ohkjxymk6ffpy/cia5rherr005phkjx79eg202o/cia5rherr005qhkjxloql9sm9/cia5rherr005rhkjxf0fcwa88", + "https://example.com/cia5rherr005shkjxkclvai2n/cia5rherr005thkjx6lhii1h5/cia5rherr005uhkjx21imwf9f/cia5rherr005vhkjxct3akix6", + "https://example.com/cia5rherr005whkjxam3tkrf9/cia5rherr005xhkjx3ooh8d77/cia5rherr005yhkjx3ih1052q/cia5rherr005zhkjxjn08ve87", + "https://example.com/cia5rherr0060hkjxbovht1zk/cia5rherr0061hkjx3mq7yzke/cia5rherr0062hkjxc3jyhls3/cia5rherr0063hkjxw5y2kgtl", + "https://example.com/cia5rherr0064hkjxlzchg2lf/cia5rherr0065hkjxjf4qz7h7/cia5rherr0066hkjxhzxfzly2/cia5rherr0067hkjxxpsm77n2", + "https://example.com/cia5rherr0068hkjxqakagjms/cia5rherr0069hkjxahy5cahd/cia5rherr006ahkjxlx3eo0d8/cia5rherr006bhkjxqhufagp0", + "https://example.com/cia5rherr006chkjxs6l4reuh/cia5rherr006dhkjx22dhomat/cia5rherr006ehkjxv8bhjw6s/cia5rherr006fhkjxiembar5h", + "https://example.com/cia5rherr006ghkjx7uk2ahcv/cia5rherr006hhkjxrptkdzpc/cia5rherr006ihkjxoelimxey/cia5rherr006jhkjxh00sc9q0", + "https://example.com/cia5rherr006khkjx7bjaq4p8/cia5rherr006lhkjx0t17bwqu/cia5rherr006mhkjx63m6nx0x/cia5rherr006nhkjx4aylco20", + "https://example.com/cia5rherr006ohkjxqncx2bfn/cia5rherr006phkjxcs9lhj4k/cia5rherr006qhkjxltiaiox0/cia5rherr006rhkjxpxhk6ceh", + "https://example.com/cia5rherr006shkjxkjpkluh5/cia5rherr006thkjxksjhi0rp/cia5rherr006uhkjxuluxeq56/cia5rherr006vhkjx2i5mkp5o", + "https://example.com/cia5rherr006whkjx94ph66s7/cia5rherr006xhkjx963ey6z0/cia5rherr006yhkjxklr3zey7/cia5rherr006zhkjxhi9ckzcj", + "https://example.com/cia5rherr0070hkjx8qu91dki/cia5rherr0071hkjxnsj72h7a/cia5rherr0072hkjxpcch22pw/cia5rherr0073hkjxr88cl6td", + "https://example.com/cia5rherr0074hkjx33v1pc9p/cia5rherr0075hkjxbijzax59/cia5rherr0076hkjxdatwas40/cia5rherr0077hkjxp92fa2bh", + "https://example.com/cia5rherr0078hkjxids7sgwp/cia5rherr0079hkjxptr6nbvk/cia5rherr007ahkjx51bynaee/cia5rherr007bhkjx2f0oimkg", + "https://example.com/cia5rherr007chkjxj243t6fq/cia5rherr007dhkjxx0z54s2z/cia5rherr007ehkjxvtk1saqr/cia5rherr007fhkjxc99ot3di", + "https://example.com/cia5rherr007ghkjxcdyrgbe5/cia5rherr007hhkjx4g2zda93/cia5rherr007ihkjx3dhwfh5w/cia5rherr007jhkjxnucjju1k", + "https://example.com/cia5rherr007khkjxtucv4xam/cia5rherr007lhkjxzb52dzam/cia5rherr007mhkjxe5ytefsg/cia5rherr007nhkjxhn1htqim", + "https://example.com/cia5rherr007ohkjxyhvqunje/cia5rherr007phkjxg1rmr8ia/cia5rherr007qhkjxvne6tsg2/cia5rherr007rhkjx92plnv33", + "https://example.com/cia5rherr007shkjxedxbud9y/cia5rherr007thkjxsfcv846z/cia5rherr007uhkjxrtoyvp1k/cia5rherr007vhkjxwkkvb63p", + "https://example.com/cia5rherr007whkjxhom33u72/cia5rherr007xhkjx00i52itn/cia5rherr007yhkjxlbwyrfbh/cia5rherr007zhkjx7hqxgotj", + "https://example.com/cia5rherr0080hkjxm6fr03t9/cia5rherr0081hkjxsskkldqg/cia5rherr0082hkjx6agt52v9/cia5rherr0083hkjxmw1qpljq", + "https://example.com/cia5rherr0084hkjx66lbczid/cia5rherr0085hkjx1yhf4qqz/cia5rherr0086hkjx156ah7x1/cia5rhers0087hkjxf7zmqrz3", + "https://example.com/cia5rhers0088hkjxlvoy1vtq/cia5rhers0089hkjxwopzmrl8/cia5rhers008ahkjxa76skipt/cia5rhers008bhkjxchpeggii", + "https://example.com/cia5rhers008chkjx77jxzrj5/cia5rhers008dhkjxwdtj4fxt/cia5rhers008ehkjxyk1rbqs4/cia5rhers008fhkjxi6c9h7on", + "https://example.com/cia5rhers008ghkjxcjbq4qio/cia5rhers008hhkjx46uf4z78/cia5rhers008ihkjxiwjs7rs8/cia5rhers008jhkjx6gow2oc8", + "https://example.com/cia5rhers008khkjxam5doybe/cia5rhers008lhkjx33h8n79o/cia5rhers008mhkjxch0uhhqa/cia5rhers008nhkjxvkh7mio2", + "https://example.com/cia5rhers008ohkjxfixhe4vq/cia5rhers008phkjxrw2g2zi3/cia5rhers008qhkjxoynia1m2/cia5rhers008rhkjxoh1yvlac", + "https://example.com/cia5rhers008shkjxewzwesdt/cia5rhers008thkjx2yx5o3rs/cia5rhers008uhkjxm75q9exl/cia5rhers008vhkjx4jogh9q0", + "https://example.com/cia5rhers008whkjxdo6vglei/cia5rhers008xhkjx0bx8rrph/cia5rhers008yhkjx7sotv4z4/cia5rhers008zhkjxid6dq8hl", + "https://example.com/cia5rhers0090hkjxpmid4zyj/cia5rhers0091hkjx7swur7ci/cia5rhers0092hkjxd4mc5j7l/cia5rhers0093hkjxri89p7jy", + "https://example.com/cia5rhers0094hkjxirrewas9/cia5rhers0095hkjx1wkqq1g1/cia5rhers0096hkjxvpc3hp0t/cia5rhers0097hkjx1ugphwhs", + "https://example.com/cia5rhers0098hkjxxptvggpn/cia5rhers0099hkjx0hgy6tpl/cia5rhers009ahkjxrgk4cwx1/cia5rhers009bhkjxhff7ocag", + "https://example.com/cia5rhers009chkjxt2zanh56/cia5rhers009dhkjxeh17c4wg/cia5rhers009ehkjxkhdljhm4/cia5rhers009fhkjxa78k166d", + "https://example.com/cia5rhers009ghkjxblshruiu/cia5rhers009hhkjx6bpkys70/cia5rhers009ihkjx097xi5jr/cia5rhers009jhkjxl1v2ym6h", + "https://example.com/cia5rhers009khkjxvciqe48d/cia5rhers009lhkjxxgfzrzqn/cia5rhers009mhkjx99k29erp/cia5rhers009nhkjxnmdineg9", + "https://example.com/cia5rhers009ohkjx0rr58cbk/cia5rhers009phkjxo0wwxbtm/cia5rhers009qhkjx8j0n0ta4/cia5rhers009rhkjx8jok0mo9", + "https://example.com/cia5rhers009shkjxo1trhdsu/cia5rhers009thkjxtukv1mwy/cia5rhers009uhkjx0e82f0we/cia5rhers009vhkjx8j0ysbxu", + "https://example.com/cia5rhers009whkjxdl5recmi/cia5rhers009xhkjx801pcdzp/cia5rhers009yhkjx2wnyv52l/cia5rhers009zhkjxdmjnipn0", + "https://example.com/cia5rhers00a0hkjxj46kgklk/cia5rhers00a1hkjxmaxskno1/cia5rhers00a2hkjxrv6jtia4/cia5rhers00a3hkjx524js0bd", + "https://example.com/cia5rhers00a4hkjx6y55uml1/cia5rhers00a5hkjxahhtmal1/cia5rhers00a6hkjx0pt7rbot/cia5rhers00a7hkjxtboirxmu", + "https://example.com/cia5rhers00a8hkjxqmrjjens/cia5rhers00a9hkjxxbmuxaqn/cia5rhers00aahkjx2fdz4j2q/cia5rhers00abhkjxflwb8nml", + "https://example.com/cia5rhers00achkjxc7n5xjqb/cia5rhers00adhkjx98mur9hy/cia5rhers00aehkjxrmv4miio/cia5rhers00afhkjxm1wmuk24", + "https://example.com/cia5rhers00aghkjxhh9nyajy/cia5rhers00ahhkjxypuj93ni/cia5rhers00aihkjxqhvpkhvu/cia5rhers00ajhkjxcebhpi2g", + "https://example.com/cia5rhers00akhkjxpq2z92bg/cia5rhers00alhkjx6ztkugxa/cia5rhers00amhkjxys8qr2ae/cia5rhers00anhkjxugpiwy4o", + "https://example.com/cia5rhers00aohkjx5lptk7ll/cia5rhert00aphkjx3m0tdlo6/cia5rhert00aqhkjxtun34qda/cia5rhert00arhkjxgn2cukgo", + "https://example.com/cia5rhert00ashkjxgndi0t1w/cia5rhert00athkjxngdtw2ac/cia5rhert00auhkjxtlqdm9tk/cia5rhert00avhkjxpmv39lb6", + "https://example.com/cia5rhert00awhkjxsm1i5606/cia5rhert00axhkjxiini4u7n/cia5rhert00ayhkjxawcdih8y/cia5rhert00azhkjx2uwcskyo", + "https://example.com/cia5rhert00b0hkjxjosiu610/cia5rhert00b1hkjx0inj6sis/cia5rhert00b2hkjx687j5ca5/cia5rhert00b3hkjxaaepyb37", + "https://example.com/cia5rhert00b4hkjxccpt8awt/cia5rhert00b5hkjxb4kuj419/cia5rhert00b6hkjxq8bi4zga/cia5rhert00b7hkjxrgomql9g", + "https://example.com/cia5rhert00b8hkjxjxy3fhb0/cia5rhert00b9hkjxftkoktwh/cia5rhert00bahkjxjx5o3cnn/cia5rhert00bbhkjxa6frtobg", + "https://example.com/cia5rhert00bchkjxe5tkaifo/cia5rhert00bdhkjx5jppppvo/cia5rhert00behkjxdmsqdq9p/cia5rhert00bfhkjxnw4tk86n", + "https://example.com/cia5rhert00bghkjx49wz4l5r/cia5rhert00bhhkjx2mfza2xe/cia5rhert00bihkjxbsd9ovfr/cia5rhert00bjhkjx3vye5ep3", + "https://example.com/cia5rhert00bkhkjxwtdsj589/cia5rhert00blhkjxmjchgsmf/cia5rhert00bmhkjxm5fxzqpq/cia5rhert00bnhkjxyf65cmel", + "https://example.com/cia5rhert00bohkjx7c22ja5m/cia5rhert00bphkjxhe6e895b/cia5rhert00bqhkjxj6kge909/cia5rhert00brhkjx65rkq4ma", + "https://example.com/cia5rhert00bshkjx1cn2s8w0/cia5rhert00bthkjxlbbqak7z/cia5rhert00buhkjxfy9yreib/cia5rhert00bvhkjxaivuz0fw", + "https://example.com/cia5rhert00bwhkjxinogvtl2/cia5rhert00bxhkjxw62nijla/cia5rhert00byhkjxl5nlc2eo/cia5rhert00bzhkjxhlmdfgi7", + "https://example.com/cia5rhert00c0hkjx4n6b40nr/cia5rhert00c1hkjxsnmkujr5/cia5rhert00c2hkjxv6w0h717/cia5rhert00c3hkjxrcvb5qs6", + "https://example.com/cia5rhert00c4hkjxgd95z86n/cia5rhert00c5hkjxv1tvmf9c/cia5rhert00c6hkjx4cv3ix8y/cia5rhert00c7hkjxli2to7w7", + "https://example.com/cia5rhert00c8hkjxgaiuvkul/cia5rhert00c9hkjxkcpdyj68/cia5rhert00cahkjxjxrfjfl0/cia5rhert00cbhkjxk64v5mhq", + "https://example.com/cia5rhert00cchkjxzkjnwkn1/cia5rhert00cdhkjx1cdlk3ms/cia5rhert00cehkjxaw4lnp43/cia5rhert00cfhkjxz4hk5gsw", + "https://example.com/cia5rhert00cghkjxpil3za50/cia5rhert00chhkjxp7t5jz46/cia5rhert00cihkjx5cwk8th0/cia5rhert00cjhkjxaxp4y2o0", + "https://example.com/cia5rhert00ckhkjxcylxiiem/cia5rhert00clhkjxzkx50uda/cia5rhert00cmhkjxs5uv5t4e/cia5rhert00cnhkjxxz9rplpv", + "https://example.com/cia5rhert00cohkjxa1r51z2k/cia5rhert00cphkjx1zw5bwvd/cia5rhert00cqhkjxkmcxldzz/cia5rhert00crhkjx8xm9oeny", + "https://example.com/cia5rhert00cshkjxuoqaoftt/cia5rhert00cthkjx1x01dmec/cia5rhert00cuhkjx9sixehxp/cia5rhert00cvhkjxomyssiza", + "https://example.com/cia5rhert00cwhkjx7et34wux/cia5rhert00cxhkjxncva74m3/cia5rhert00cyhkjxfkizvrik/cia5rhert00czhkjxgbzmphlh", + "https://example.com/cia5rhert00d0hkjx5amei299/cia5rhert00d1hkjxumygde64/cia5rhert00d2hkjxpnuisg9m/cia5rhert00d3hkjxakoawt1r", + "https://example.com/cia5rhert00d4hkjx1h1sd1xk/cia5rhert00d5hkjxiszqt7gt/cia5rhert00d6hkjxrgly5qmw/cia5rhert00d7hkjx0khb7is3", + "https://example.com/cia5rhert00d8hkjx8q3rh7mm/cia5rhert00d9hkjxsc3r3d6p/cia5rhert00dahkjxi4ka0kza/cia5rhert00dbhkjxzvnrcsv0", + "https://example.com/cia5rhert00dchkjxqlqg4rat/cia5rhert00ddhkjxtjjv0jz2/cia5rhert00dehkjxf7uayrst/cia5rhert00dfhkjxzjqnclyl", + "https://example.com/cia5rhert00dghkjxdq5ojgf7/cia5rhert00dhhkjxs3fjff88/cia5rheru00dihkjxo2axpgvn/cia5rheru00djhkjx8lrs9xha", + "https://example.com/cia5rheru00dkhkjx45m33mct/cia5rheru00dlhkjxzrjk6adw/cia5rheru00dmhkjxhhgorfhb/cia5rheru00dnhkjxwadtejpl", + "https://example.com/cia5rheru00dohkjxj8rdgozp/cia5rheru00dphkjxsqktwr0d/cia5rheru00dqhkjxlodva5ul/cia5rheru00drhkjxy79ve13k", + "https://example.com/cia5rheru00dshkjxwawhyuhd/cia5rheru00dthkjxovwtz7zd/cia5rheru00duhkjxnhebnm70/cia5rheru00dvhkjxt28pubcm", + "https://example.com/cia5rheru00dwhkjxasdto0h0/cia5rheru00dxhkjxvoxohbir/cia5rheru00dyhkjxr50yvvwt/cia5rheru00dzhkjxiffhdptt", + "https://example.com/cia5rheru00e0hkjx9htn1puz/cia5rheru00e1hkjxqvm483lo/cia5rheru00e2hkjxbr3v8ysf/cia5rheru00e3hkjx1xxys4u4", + "https://example.com/cia5rheru00e4hkjx0pdbwiqd/cia5rheru00e5hkjxcjyvelv6/cia5rheru00e6hkjxfar4jska/cia5rheru00e7hkjxfkw7rkrq", + "https://example.com/cia5rheru00e8hkjx1rtfronr/cia5rheru00e9hkjxpgfyuqxd/cia5rheru00eahkjx9hw9j0h3/cia5rheru00ebhkjx83acges9", + "https://example.com/cia5rheru00echkjxbrhqzezc/cia5rheru00edhkjxu3iycpdz/cia5rheru00eehkjxs91i2o4s/cia5rheru00efhkjxgsawfxoa", + "https://example.com/cia5rheru00eghkjxdhzfetw8/cia5rheru00ehhkjx1wnw1va3/cia5rheru00eihkjxo0vft0jh/cia5rheru00ejhkjxa8vgdyzn", + "https://example.com/cia5rheru00ekhkjxfo36jiae/cia5rheru00elhkjxim0qkqcu/cia5rheru00emhkjxyv3cm8s8/cia5rheru00enhkjx4zsm4g1k", + "https://example.com/cia5rherv00eohkjxl16e03j1/cia5rherv00ephkjxtfqgb09e/cia5rherv00eqhkjxxnuib8vx/cia5rherv00erhkjx3c2eh7rw", + "https://example.com/cia5rherv00eshkjxk2ugn862/cia5rherv00ethkjx4a4i5bu4/cia5rherv00euhkjx4x1ll1hu/cia5rherv00evhkjx4a90801w", + "https://example.com/cia5rherv00ewhkjx16ofuvju/cia5rherv00exhkjxbjicg8ee/cia5rherv00eyhkjxi37ly2xd/cia5rherv00ezhkjxme2gnc8n", + "https://example.com/cia5rherv00f0hkjxupxvqy6f/cia5rherv00f1hkjxif6y2ebt/cia5rherv00f2hkjx2hxktwi1/cia5rherv00f3hkjxboq2udk4", + "https://example.com/cia5rherv00f4hkjxe5t1s9vm/cia5rherv00f5hkjx2p1bkk8q/cia5rherv00f6hkjx3ugeare1/cia5rherv00f7hkjxw5xpdje2", + "https://example.com/cia5rherv00f8hkjxr347e4en/cia5rherv00f9hkjxjmjvxubi/cia5rherv00fahkjxhgdld04j/cia5rherv00fbhkjxury8qhpe", + "https://example.com/cia5rherv00fchkjx36kklfip/cia5rherv00fdhkjx51q4kxdz/cia5rherv00fehkjxm1xs1n6p/cia5rherv00ffhkjxzrms1ho0", + "https://example.com/cia5rherv00fghkjxq54a40bh/cia5rherv00fhhkjxfjd33rlp/cia5rherv00fihkjx8ydqzwn4/cia5rherv00fjhkjx800mxgbz", + "https://example.com/cia5rherv00fkhkjxr5ktedsn/cia5rherv00flhkjxkh6ip88y/cia5rherv00fmhkjxx824akqe/cia5rherv00fnhkjxn8b9z0y4", + "https://example.com/cia5rherv00fohkjximtuc4oo/cia5rherv00fphkjxo8xnj3a1/cia5rherv00fqhkjx3frdvz0l/cia5rherv00frhkjxnt4ip06p", + "https://example.com/cia5rherv00fshkjxebbqlsj4/cia5rherv00fthkjxhejhs2en/cia5rherv00fuhkjxww4v962t/cia5rherv00fvhkjx5n0tpk2r", + "https://example.com/cia5rherv00fwhkjxomhb4741/cia5rherv00fxhkjxkgs095w0/cia5rherv00fyhkjxtve4rfnk/cia5rherv00fzhkjxkl9m13ke", + "https://example.com/cia5rherv00g0hkjxwfqm1g52/cia5rherv00g1hkjx0sajw56m/cia5rherv00g2hkjxcs30a0m9/cia5rherv00g3hkjxuq2edyvx", + "https://example.com/cia5rherv00g4hkjxpmffchhu/cia5rherv00g5hkjxkhh7fc92/cia5rherv00g6hkjxsak4bwp4/cia5rherv00g7hkjxemsqgdmx", + "https://example.com/cia5rherv00g8hkjxxd4iaucx/cia5rherv00g9hkjx82yt525c/cia5rherv00gahkjx9higv8wz/cia5rherv00gbhkjx0bxq8ejn", + "https://example.com/cia5rherv00gchkjxolbgqpvx/cia5rherv00gdhkjxh1q1mlra/cia5rherv00gehkjx6rmpv7a6/cia5rherv00gfhkjx7v471pja", + "https://example.com/cia5rherv00gghkjx4swi554w/cia5rherv00ghhkjxrbcdqi5x/cia5rherv00gihkjxrzkje2z3/cia5rherv00gjhkjx23xefzn2", + "https://example.com/cia5rherv00gkhkjx7om372u4/cia5rherv00glhkjx7t4qbrj9/cia5rherv00gmhkjxm4021iiz/cia5rherv00gnhkjxgxmosagw", + "https://example.com/cia5rherv00gohkjx92hhv2g9/cia5rherv00gphkjx7bf3eo7t/cia5rherv00gqhkjx6wj13cjb/cia5rherv00grhkjx9xdd6xqg", + "https://example.com/cia5rherv00gshkjxapw92pmq/cia5rherv00gthkjx7uqh0m79/cia5rherv00guhkjx9uxnghi5/cia5rherv00gvhkjx9dk24e4x", + "https://example.com/cia5rherv00gwhkjxm8spj2z7/cia5rherv00gxhkjx1bzjkoj5/cia5rherv00gyhkjxj4n460vb/cia5rherv00gzhkjx6824pani", + "https://example.com/cia5rherv00h0hkjx8wfm03s5/cia5rherv00h1hkjxboapbcoy/cia5rherv00h2hkjxqgzz6g4s/cia5rherv00h3hkjxcbpuhpk2", + "https://example.com/cia5rherv00h4hkjx0eho0z4b/cia5rherv00h5hkjx1z59ioaa/cia5rherv00h6hkjxzqmxtasv/cia5rherv00h7hkjxflt86e5f", + "https://example.com/cia5rherv00h8hkjxxlr7008t/cia5rherv00h9hkjxz8kctoc9/cia5rherv00hahkjxq60p0kx0/cia5rherv00hbhkjxd0zud58a", + "https://example.com/cia5rherv00hchkjx4o8rphb9/cia5rherv00hdhkjxs3n7yko7/cia5rherv00hehkjxnxwntfg5/cia5rherv00hfhkjxb39xgejv", + "https://example.com/cia5rherv00hghkjxb1yco8y1/cia5rherv00hhhkjx2v6pz42w/cia5rherv00hihkjx400aow1n/cia5rherv00hjhkjxtwik0d1s", + "https://example.com/cia5rherv00hkhkjxwnaoc3oc/cia5rherv00hlhkjxsmntvjhm/cia5rherv00hmhkjx4ag0j8cq/cia5rherv00hnhkjxbiigrngm", + "https://example.com/cia5rherv00hohkjx2s4opprd/cia5rherv00hphkjx8pkc39p2/cia5rherv00hqhkjxif9qfvct/cia5rherv00hrhkjxe4vtvd6z", + "https://example.com/cia5rherv00hshkjxampz8fwm/cia5rherv00hthkjxf48ybrar/cia5rherv00huhkjx3asrrys5/cia5rherv00hvhkjxc570882r", + "https://example.com/cia5rherv00hwhkjxfooadgv0/cia5rherv00hxhkjxnmtttd03/cia5rherv00hyhkjx0deqal5b/cia5rherv00hzhkjxleqe94v1", + "https://example.com/cia5rherv00i0hkjxs3ps41wq/cia5rherv00i1hkjxw46qft2r/cia5rherv00i2hkjxfmfjpssy/cia5rherv00i3hkjxayjpzx43", + "https://example.com/cia5rherv00i4hkjxpxotlyhp/cia5rherv00i5hkjxhz77s1t6/cia5rherv00i6hkjxwgw9wpq7/cia5rherv00i7hkjxy724la5w", + "https://example.com/cia5rherv00i8hkjx6wko56vc/cia5rherv00i9hkjxiqs85dqr/cia5rherv00iahkjx32pvc8lh/cia5rherv00ibhkjxouu7jcs7", + "https://example.com/cia5rherv00ichkjxub141io8/cia5rherv00idhkjx4xp7yqzz/cia5rherv00iehkjx4l9xkah3/cia5rherv00ifhkjx3qco4ypm", + "https://example.com/cia5rherv00ighkjx9sp14gne/cia5rherv00ihhkjx713r0569/cia5rherv00iihkjxorq3d5yp/cia5rherv00ijhkjxtdo94p17", + "https://example.com/cia5rherv00ikhkjx4j5jbzre/cia5rherv00ilhkjxcgsn6xlu/cia5rherv00imhkjxx8la8kf5/cia5rherv00inhkjx3hz5opiw", + "https://example.com/cia5rherv00iohkjx3dgmyl4e/cia5rherv00iphkjx8glt32u3/cia5rherv00iqhkjx6cb6wlxs/cia5rherv00irhkjxza76xuqt", + "https://example.com/cia5rherv00ishkjxo51i49wr/cia5rherv00ithkjxxa546aho/cia5rherv00iuhkjx50q0dsyb/cia5rherv00ivhkjxcq50j6b5", + "https://example.com/cia5rherv00iwhkjxaklfq2jh/cia5rherv00ixhkjxq47wm5d8/cia5rherv00iyhkjx93fsh773/cia5rherv00izhkjx05qw5ls6", + "https://example.com/cia5rherv00j0hkjxcpk80dqg/cia5rherv00j1hkjxe5fze7ph/cia5rherv00j2hkjx0b4x6jj2/cia5rherv00j3hkjxslnfjk8f", + "https://example.com/cia5rherv00j4hkjxn02a7r4w/cia5rherv00j5hkjxqbq2abzk/cia5rherv00j6hkjx046gf9gh/cia5rherv00j7hkjxw8y6aybl", + "https://example.com/cia5rherv00j8hkjxua9fh4f2/cia5rherv00j9hkjx0n7j7tf0/cia5rherv00jahkjx1h8p0yzt/cia5rherv00jbhkjx6isout34", + "https://example.com/cia5rherv00jchkjxscxpp7mk/cia5rherv00jdhkjxx7314ajy/cia5rherv00jehkjx2wej8wjh/cia5rherv00jfhkjxs4i5eiho", + "https://example.com/cia5rherv00jghkjxcrkt8o3t/cia5rherv00jhhkjx2u0sm2r0/cia5rherv00jihkjxtpjif9k6/cia5rherv00jjhkjxaagcsohi", + "https://example.com/cia5rherv00jkhkjx8gq5x6ov/cia5rherv00jlhkjxev9zq161/cia5rherv00jmhkjx2sjcp2px/cia5rherv00jnhkjxn8jpfipq", + "https://example.com/cia5rherv00johkjxbx8744i5/cia5rherv00jphkjxi2eza54a/cia5rherv00jqhkjx1q5y2n4p/cia5rherv00jrhkjxvl0soujd", + "https://example.com/cia5rherv00jshkjxs7ziy4vs/cia5rherv00jthkjx3ewtvj26/cia5rherv00juhkjxkwhid4b7/cia5rherv00jvhkjxx7nurgrx", + "https://example.com/cia5rherv00jwhkjxxlcfjhj3/cia5rherv00jxhkjxoresrx2f/cia5rherv00jyhkjxtsmt6apo/cia5rherv00jzhkjxghybzsqs", + "https://example.com/cia5rherv00k0hkjx7o0bc0o5/cia5rherv00k1hkjxmzwu9w7x/cia5rherv00k2hkjxw6z2g76g/cia5rherv00k3hkjx8htly8zt", + "https://example.com/cia5rherv00k4hkjx59yd941i/cia5rherv00k5hkjx0j5ccesw/cia5rherv00k6hkjxduxlqh65/cia5rherv00k7hkjx5et0n6t0", + "https://example.com/cia5rherv00k8hkjxj72kcc3p/cia5rherv00k9hkjx0zy8vr32/cia5rherv00kahkjx6kmi5wtf/cia5rherv00kbhkjxh2ut5f4w", + "https://example.com/cia5rherv00kchkjxq9ki0lgr/cia5rherv00kdhkjx8mafuept/cia5rherv00kehkjx3kffqdj5/cia5rherv00kfhkjxk30tt2a0", + "https://example.com/cia5rherv00kghkjxaw98qyx2/cia5rherv00khhkjxieg0bycx/cia5rherw00kihkjxdmdkp4x9/cia5rherw00kjhkjxijtzapeq", + "https://example.com/cia5rherw00kkhkjxh1oh0kl4/cia5rherw00klhkjx6ffidsax/cia5rherw00kmhkjxqtgyxcy0/cia5rherw00knhkjx2xyipie3", + "https://example.com/cia5rherw00kohkjxdq8vr7d0/cia5rherw00kphkjxes36xbks/cia5rherw00kqhkjx4ektym79/cia5rherw00krhkjxib4btsp0", + "https://example.com/cia5rherw00kshkjxpc4m0m9e/cia5rherw00kthkjxgdi6nnzj/cia5rherw00kuhkjxcwj7gk86/cia5rherw00kvhkjx7cj949ld", + "https://example.com/cia5rherw00kwhkjxmwj8brl1/cia5rherw00kxhkjxae8yeb7p/cia5rherw00kyhkjxqze289bb/cia5rherw00kzhkjx2jy2h9ji", + "https://example.com/cia5rherw00l0hkjxxadfzs3n/cia5rherw00l1hkjxhngih2c7/cia5rherw00l2hkjxn5zwgxrx/cia5rherw00l3hkjxz3mp1gb9", + "https://example.com/cia5rherw00l4hkjxhcw0erdu/cia5rherw00l5hkjxj6j3l2zh/cia5rherw00l6hkjx533r5z4r/cia5rherw00l7hkjxvjjbvgo6", + "https://example.com/cia5rherw00l8hkjxx61zlkek/cia5rherw00l9hkjxb7xj73l2/cia5rherw00lahkjxrpt6fk4m/cia5rherw00lbhkjxlron4prb", + "https://example.com/cia5rherw00lchkjxtq99vutt/cia5rherw00ldhkjxon95rdss/cia5rherw00lehkjxblctcrfw/cia5rherw00lfhkjxc3mffk2x", + "https://example.com/cia5rherw00lghkjx4v9dujig/cia5rherw00lhhkjx734cxan0/cia5rherw00lihkjxlxx3bx3y/cia5rherw00ljhkjxa5te3vm6", + "https://example.com/cia5rherw00lkhkjxy4akb3lo/cia5rherw00llhkjxxg2t6ecs/cia5rherw00lmhkjx5v0ur1zi/cia5rherw00lnhkjxuw4grqgg", + "https://example.com/cia5rherw00lohkjx1tx7bq0f/cia5rherw00lphkjx118uu54q/cia5rherw00lqhkjxo63inpim/cia5rherw00lrhkjx60iprrlh", + "https://example.com/cia5rherw00lshkjxq67z9znh/cia5rherw00lthkjxknfimvv6/cia5rherw00luhkjxln059pxx/cia5rherw00lvhkjx0b5u1wvh", + "https://example.com/cia5rherw00lwhkjxdnbo7o8x/cia5rherw00lxhkjxialw39ph/cia5rherw00lyhkjxq44794oz/cia5rherw00lzhkjx063oj379", + "https://example.com/cia5rherw00m0hkjxa51hcx2d/cia5rherw00m1hkjx5udmt5pw/cia5rherw00m2hkjxh2zenkxy/cia5rherw00m3hkjxaa4b1ukf", + "https://example.com/cia5rherw00m4hkjxu0txyp2a/cia5rherw00m5hkjxku5t6nia/cia5rherw00m6hkjxpuvkxgxe/cia5rherw00m7hkjx7mgdnt4i", + "https://example.com/cia5rherw00m8hkjx9tjak8nq/cia5rherw00m9hkjx6tjbc4c1/cia5rherw00mahkjxhrlq09fa/cia5rherw00mbhkjxncngvvyl", + "https://example.com/cia5rherw00mchkjxqjijeepd/cia5rherw00mdhkjx5n3u57g1/cia5rherw00mehkjxlhfhx86m/cia5rherw00mfhkjxhtz36qq5", + "https://example.com/cia5rherw00mghkjxiknnl4br/cia5rherw00mhhkjxl1kv5f98/cia5rherw00mihkjxzo6yez34/cia5rherw00mjhkjx2ffeb3lh", + "https://example.com/cia5rherw00mkhkjxziimpsbk/cia5rherw00mlhkjxp3bcocf5/cia5rherw00mmhkjxbwztbp6k/cia5rherw00mnhkjxyc9eixhz", + "https://example.com/cia5rherw00mohkjxm8kai7n5/cia5rherw00mphkjx65gvhaf2/cia5rherw00mqhkjxfh4vu8eq/cia5rherw00mrhkjxfv2gj9bt", + "https://example.com/cia5rherw00mshkjxmnuhrdj4/cia5rherw00mthkjxjj5iizh6/cia5rherw00muhkjxiw62tfl7/cia5rherw00mvhkjxhzemezd3", + "https://example.com/cia5rherw00mwhkjxlml9rs61/cia5rherw00mxhkjxl2mbwrnp/cia5rherw00myhkjxj96ye1zv/cia5rherw00mzhkjxodju2h5o", + "https://example.com/cia5rherw00n0hkjxuivmtahu/cia5rherw00n1hkjx8gbwu61v/cia5rherw00n2hkjx1j9gzlce/cia5rherw00n3hkjxaz67uu2l", + "https://example.com/cia5rherw00n4hkjxoz2v47cp/cia5rherw00n5hkjxc2m0xq1d/cia5rherw00n6hkjxs5pej7ym/cia5rherw00n7hkjxrj28b2tt", + "https://example.com/cia5rherw00n8hkjxikm4mbwi/cia5rherw00n9hkjxcfsu9f9d/cia5rherw00nahkjxawec3u0q/cia5rherw00nbhkjx38j8uycv", + "https://example.com/cia5rherw00nchkjxpxks6dv1/cia5rherw00ndhkjxme2uh9cm/cia5rherw00nehkjxiu6s9qet/cia5rherw00nfhkjxjeycmmn7", + "https://example.com/cia5rherw00nghkjx66hdwbtg/cia5rherw00nhhkjxki3i81i4/cia5rherw00nihkjxvve944gl/cia5rherw00njhkjx3jwe46il", + "https://example.com/cia5rherw00nkhkjx8znncv54/cia5rherw00nlhkjxsyqrjo5g/cia5rherw00nmhkjxkir8br45/cia5rherw00nnhkjxexumdwa2", + "https://example.com/cia5rherw00nohkjxzs49c8vj/cia5rherw00nphkjx5eq7mllr/cia5rherw00nqhkjxxqc7wn0n/cia5rherw00nrhkjx3xnlk87f", + "https://example.com/cia5rherw00nshkjxis89wcyt/cia5rherw00nthkjxmo962d98/cia5rherw00nuhkjx5102qsyd/cia5rherw00nvhkjx4dhdkq7d", + "https://example.com/cia5rherw00nwhkjx6o2era0y/cia5rherw00nxhkjxibwv5nl3/cia5rherw00nyhkjxnobkkmmt/cia5rherw00nzhkjx2rfj3yw8", + "https://example.com/cia5rherw00o0hkjxys1f6cpt/cia5rherw00o1hkjxv7y1wk16/cia5rherw00o2hkjx72rwd3p7/cia5rherw00o3hkjxdpw2fjf4", + "https://example.com/cia5rherw00o4hkjxj0qwjpur/cia5rherw00o5hkjxr0vma4yn/cia5rherw00o6hkjxjdio6m66/cia5rherw00o7hkjx6tah5wos", + "https://example.com/cia5rherw00o8hkjxtseqnpix/cia5rherw00o9hkjxcll29qxs/cia5rherw00oahkjxy6j4514j/cia5rherw00obhkjxyt12uhto", + "https://example.com/cia5rherw00ochkjxv94azfo3/cia5rherw00odhkjxvvb9a4cf/cia5rherw00oehkjxro8j0dau/cia5rherw00ofhkjxazh1bnkv", + "https://example.com/cia5rherw00oghkjxaiqblgaz/cia5rherw00ohhkjxq0jg2ekb/cia5rherw00oihkjxhxp787hh/cia5rherw00ojhkjxwn2bygkc", + "https://example.com/cia5rherw00okhkjxrvuzh590/cia5rherw00olhkjxukov5sdm/cia5rherw00omhkjxprnz66kz/cia5rherw00onhkjxd19rb8es", + "https://example.com/cia5rherw00oohkjxdp73iojj/cia5rherw00ophkjx0ejclbnc/cia5rherw00oqhkjxzbrto5j9/cia5rherw00orhkjx64ybndrk", + "https://example.com/cia5rherw00oshkjxto8g8o9d/cia5rherw00othkjx0siti4zs/cia5rherw00ouhkjxn4k2rxwd/cia5rherw00ovhkjxlfh7kchr", + "https://example.com/cia5rherw00owhkjx5u0y9ly7/cia5rherw00oxhkjxuc2947n2/cia5rherw00oyhkjxclqlf4cz/cia5rherw00ozhkjxw4ljr3n2", + "https://example.com/cia5rherw00p0hkjxprwdryfg/cia5rherw00p1hkjxnm9mqxjr/cia5rherw00p2hkjxo97f8u6g/cia5rherw00p3hkjxu3v7vxny", + "https://example.com/cia5rherw00p4hkjx52wsz26a/cia5rherw00p5hkjxbcznr0do/cia5rherw00p6hkjxytrdjjnt/cia5rherw00p7hkjx6l5uvkab", + "https://example.com/cia5rherw00p8hkjxm2zue659/cia5rherx00p9hkjxbkzeky4l/cia5rherx00pahkjxrqp8ljqj/cia5rherx00pbhkjxuxod17kg", + "https://example.com/cia5rherx00pchkjxa26ekxyy/cia5rherx00pdhkjx0kxle2w1/cia5rherx00pehkjxxugq6n6r/cia5rherx00pfhkjx2ucx69kc", + "https://example.com/cia5rherx00pghkjxnqt22yl7/cia5rherx00phhkjxuxv53gmq/cia5rherx00pihkjxcb70hxmq/cia5rherx00pjhkjxlvjdb6xl", + "https://example.com/cia5rherx00pkhkjxjc8qkw9h/cia5rherx00plhkjxcbs6t9k7/cia5rherx00pmhkjxh6cijkuv/cia5rherx00pnhkjxpf14evbg", + "https://example.com/cia5rherx00pohkjxfgxqyo6d/cia5rherx00pphkjxtpilb91o/cia5rherx00pqhkjxny7abx9y/cia5rherx00prhkjx0l5g83bc", + "https://example.com/cia5rherx00pshkjxxuxfrdbr/cia5rherx00pthkjxxnj6u6sk/cia5rherx00puhkjxpubm3g6s/cia5rherx00pvhkjxyj7u9c6t", + "https://example.com/cia5rherx00pwhkjx6ppgibkl/cia5rherx00pxhkjxvv4p4kry/cia5rherx00pyhkjxpkbho8g0/cia5rherx00pzhkjxivjo0784", + "https://example.com/cia5rherx00q0hkjxffxtz7i5/cia5rherx00q1hkjxygfowug9/cia5rherx00q2hkjxvsau87zx/cia5rherx00q3hkjx6z1kw9b2", + "https://example.com/cia5rherx00q4hkjx4auglr08/cia5rherx00q5hkjxno848f23/cia5rherx00q6hkjxy6y8cv6y/cia5rherx00q7hkjxzzoogxhg", + "https://example.com/cia5rherx00q8hkjx70m64of5/cia5rherx00q9hkjxg5c1aukr/cia5rherx00qahkjxqn1h5a85/cia5rherx00qbhkjxg2cf36ig", + "https://example.com/cia5rherx00qchkjxlwf1o9ji/cia5rherx00qdhkjx1gdhjcsr/cia5rherx00qehkjx172b5dpn/cia5rherx00qfhkjxe3uruk25", + "https://example.com/cia5rherx00qghkjx2ptty8ex/cia5rherx00qhhkjx5latps1q/cia5rherx00qihkjx9bdo19z2/cia5rherx00qjhkjxfj5a849t", + "https://example.com/cia5rherx00qkhkjxataa91qp/cia5rherx00qlhkjxkos37rp2/cia5rherx00qmhkjxnr52z1ck/cia5rherx00qnhkjxg2wv4j3b", + "https://example.com/cia5rherx00qohkjx5zybfy6z/cia5rherx00qphkjx3jotkzjk/cia5rherx00qqhkjxzqnuoxc1/cia5rherx00qrhkjxjqx7n6dd", + "https://example.com/cia5rherx00qshkjxfusl4u8i/cia5rherx00qthkjx7nax9k3j/cia5rherx00quhkjxce6sda7o/cia5rherx00qvhkjxvjw9krhf", + "https://example.com/cia5rherx00qwhkjx3myek18h/cia5rherx00qxhkjxye1vc3g5/cia5rherx00qyhkjx0qnkwhv8/cia5rherx00qzhkjx7xbpfb2g", + "https://example.com/cia5rherx00r0hkjxr05rkysp/cia5rherx00r1hkjx5skxve27/cia5rherx00r2hkjxm42ww2wl/cia5rherx00r3hkjxvgaok3d7", + "https://example.com/cia5rherx00r4hkjxhqn73qfk/cia5rherx00r5hkjx0vhqzi3y/cia5rherx00r6hkjxzo83uw13/cia5rherx00r7hkjxshtbkjap", + "https://example.com/cia5rherx00r8hkjxkolk05tr/cia5rherx00r9hkjx87txftcu/cia5rherx00rahkjx7zt1mxfl/cia5rherx00rbhkjxj3225obu", + "https://example.com/cia5rherx00rchkjxec3j4cbw/cia5rherx00rdhkjx2o60bre2/cia5rherx00rehkjx1gza5jo1/cia5rherx00rfhkjx6i15t347", + "https://example.com/cia5rherx00rghkjxqsx1tilb/cia5rherx00rhhkjxld7q3ees/cia5rherx00rihkjxywwmwh7a/cia5rherx00rjhkjxa7lzkj0b", + "https://example.com/cia5rherx00rkhkjxo2uekt9y/cia5rherx00rlhkjxz1y92fdx/cia5rherx00rmhkjxrls010bq/cia5rherx00rnhkjx5phg7y9q", + "https://example.com/cia5rherx00rohkjxt69rlmpb/cia5rherx00rphkjxxdo9vbof/cia5rherx00rqhkjxgnvs2gxf/cia5rherx00rrhkjx5vv7kk7v", + "https://example.com/cia5rherx00rshkjxfn0v3dx4/cia5rherx00rthkjx5wkktp8p/cia5rherx00ruhkjx6palbn57/cia5rherx00rvhkjxugk01wvb", + "https://example.com/cia5rherx00rwhkjxzgza4olt/cia5rherx00rxhkjxwdssdcbj/cia5rherx00ryhkjxxnyxv3t6/cia5rherx00rzhkjxhr6w38i4", + "https://example.com/cia5rherx00s0hkjxerf3k9ib/cia5rherx00s1hkjxnqqw079u/cia5rherx00s2hkjxifw1h8n3/cia5rherx00s3hkjxd05rx85o", + "https://example.com/cia5rherx00s4hkjx2x89mh5w/cia5rherx00s5hkjx87d0h7li/cia5rherx00s6hkjxqjueoeqw/cia5rherx00s7hkjxyq9w3n82", + "https://example.com/cia5rherx00s8hkjxzigd1zp5/cia5rherx00s9hkjxdtx2amst/cia5rherx00sahkjx2onc2f21/cia5rherx00sbhkjx4hgu22zb", + "https://example.com/cia5rherx00schkjxrz7trjeu/cia5rherx00sdhkjxwmrp365i/cia5rherx00sehkjx7eq317yf/cia5rherx00sfhkjxo93dnhcw", + "https://example.com/cia5rherx00sghkjx2zsmv5zb/cia5rherx00shhkjxuu1u80qs/cia5rherx00sihkjx8avektr4/cia5rherx00sjhkjxpg3tjre5", + "https://example.com/cia5rherx00skhkjxm9hrr8dp/cia5rherx00slhkjxmklu8fxx/cia5rherx00smhkjxpz58b4co/cia5rherx00snhkjx6kflkbwz", + "https://example.com/cia5rherx00sohkjx6zveco0b/cia5rherx00sphkjx9tzv10q9/cia5rherx00sqhkjx5kw9y9vt/cia5rherx00srhkjx5q2dpc7o", + "https://example.com/cia5rherx00sshkjxfmf3zzhg/cia5rherx00sthkjx085rnzf5/cia5rherx00suhkjxo7eaxytl/cia5rherx00svhkjx3bfbsur9", + "https://example.com/cia5rherx00swhkjxzihyd64f/cia5rherx00sxhkjxor1bcxl3/cia5rhery00syhkjxlensj1wa/cia5rhery00szhkjxwk1jdpzj", + "https://example.com/cia5rhery00t0hkjxz5hf0kfl/cia5rhery00t1hkjx36ar1r16/cia5rhery00t2hkjxcv7t3hqq/cia5rhery00t3hkjxkzgqe0a6", + "https://example.com/cia5rhery00t4hkjxpmbibq16/cia5rhery00t5hkjxrqr5n4lt/cia5rhery00t6hkjx4npmmnvj/cia5rhery00t7hkjxewqanavg", + "https://example.com/cia5rhery00t8hkjxci9wud4s/cia5rhery00t9hkjxui809qxy/cia5rhery00tahkjx870oqect/cia5rhery00tbhkjx8g24crc0", + "https://example.com/cia5rhery00tchkjxzjllkr5i/cia5rhery00tdhkjxqnnjio0r/cia5rhery00tehkjxnice4c5a/cia5rhery00tfhkjx0b0tfkbd", + "https://example.com/cia5rhery00tghkjx1hfn5jnm/cia5rhery00thhkjx0m68lrh0/cia5rhery00tihkjxe24uktvm/cia5rhery00tjhkjxwudbxvgf", + "https://example.com/cia5rhery00tkhkjxyupjrqmt/cia5rhery00tlhkjxns9kt84a/cia5rhery00tmhkjxnjnkvsza/cia5rhery00tnhkjx2u1kf42m", + "https://example.com/cia5rhery00tohkjxv8euxxvv/cia5rhery00tphkjxcewtixg8/cia5rhery00tqhkjxm0fvhod1/cia5rhery00trhkjxzfels6hy", + "https://example.com/cia5rhery00tshkjxcnmhpytv/cia5rhery00tthkjxgmwy284j/cia5rhery00tuhkjxvl9f0bet/cia5rhery00tvhkjxvd9h00tu", + "https://example.com/cia5rhery00twhkjxworddumj/cia5rhery00txhkjx5hn3bob3/cia5rhery00tyhkjxwxhy5o31/cia5rhery00tzhkjxy7swe892", + "https://example.com/cia5rhery00u0hkjx9v2rskyu/cia5rhery00u1hkjxt65535lp/cia5rhery00u2hkjxiephk9x0/cia5rhery00u3hkjxylul9icr", + "https://example.com/cia5rhery00u4hkjxo1tucbyj/cia5rhery00u5hkjxchfewpdu/cia5rhery00u6hkjxzh1f7dsv/cia5rhery00u7hkjxow4myzvc", + "https://example.com/cia5rhery00u8hkjxjkljwzmx/cia5rhery00u9hkjxb2hq0zff/cia5rhery00uahkjx3zil5iye/cia5rhery00ubhkjxpx6l4i62", + "https://example.com/cia5rhery00uchkjxzybwg1aj/cia5rhery00udhkjxbc85v998/cia5rhery00uehkjx9x8k1ebt/cia5rhery00ufhkjxziinfjvs", + "https://example.com/cia5rhery00ughkjxqjq7rbqe/cia5rhery00uhhkjxn422gi7s/cia5rhery00uihkjx6wdh0kru/cia5rhery00ujhkjxgx2z6i30", + "https://example.com/cia5rhery00ukhkjxxwekiqd8/cia5rhery00ulhkjxti0u03ji/cia5rhery00umhkjxneh94911/cia5rhery00unhkjx5uothdt2", + "https://example.com/cia5rhery00uohkjxh8wvz750/cia5rhery00uphkjxya408v8j/cia5rhery00uqhkjxowcw4c0j/cia5rhery00urhkjxp0hxhyjr", + "https://example.com/cia5rhery00ushkjxj5ezkt47/cia5rhery00uthkjxfcbhp09u/cia5rhery00uuhkjxw14otqjw/cia5rhery00uvhkjxhkdyfxrs", + "https://example.com/cia5rhery00uwhkjxehm078n0/cia5rhery00uxhkjxbadu0bio/cia5rhery00uyhkjxak1ocp8h/cia5rhery00uzhkjxing0qkah", + "https://example.com/cia5rhery00v0hkjxl8s5to3x/cia5rhery00v1hkjx9t9obxjk/cia5rhery00v2hkjx37ndtfyo/cia5rhery00v3hkjxgsrqx8wo", + "https://example.com/cia5rhery00v4hkjxcv3mqk3k/cia5rhery00v5hkjxm8gbw43x/cia5rhery00v6hkjxu55dmspc/cia5rhery00v7hkjx34me677j", + "https://example.com/cia5rhery00v8hkjxwecik8go/cia5rhery00v9hkjxap89471j/cia5rhery00vahkjxllo77l7s/cia5rhery00vbhkjxrqbtypmt", + "https://example.com/cia5rhery00vchkjx4uu12kzr/cia5rhery00vdhkjx5sxrg1cw/cia5rhery00vehkjxpimi78w5/cia5rhery00vfhkjxvu1h0bnc", + "https://example.com/cia5rhery00vghkjxryq1kku2/cia5rhery00vhhkjx8yq7g6dg/cia5rhery00vihkjxrhoe95rs/cia5rhery00vjhkjxg036tj71", + "https://example.com/cia5rhery00vkhkjxfk603ubw/cia5rhery00vlhkjxv6cvncpa/cia5rhery00vmhkjxtlptcbfj/cia5rhery00vnhkjx019qtozs", + "https://example.com/cia5rhery00vohkjx55roqfyq/cia5rhery00vphkjxq51jjd0w/cia5rhery00vqhkjxzl5r049u/cia5rhery00vrhkjx8w085tma", + "https://example.com/cia5rhery00vshkjx43gw6sxr/cia5rhery00vthkjx20l8em51/cia5rhery00vuhkjxdwoh2vk9/cia5rhery00vvhkjxw1g6vrut", + "https://example.com/cia5rhery00vwhkjxvn2ebxzm/cia5rhery00vxhkjxz2hlqhzd/cia5rhery00vyhkjxqvadz9wb/cia5rhery00vzhkjx9rh2a3uh", + "https://example.com/cia5rhery00w0hkjxa0rqkpov/cia5rhery00w1hkjxp833u09z/cia5rhery00w2hkjxn2awahj4/cia5rhery00w3hkjxwqcb9cgd", + "https://example.com/cia5rhery00w4hkjx2qn7xtr0/cia5rhery00w5hkjx4mw9p5o5/cia5rhery00w6hkjx1h6f1nne/cia5rhery00w7hkjxqgof32en", + "https://example.com/cia5rhery00w8hkjx40u225qd/cia5rhery00w9hkjx6jc1e5lj/cia5rhery00wahkjxqqn44l7k/cia5rhery00wbhkjxdwnm2lan", + "https://example.com/cia5rhery00wchkjxmf3n36f9/cia5rhery00wdhkjxjitsfpkb/cia5rhery00wehkjxrwzzunbx/cia5rhery00wfhkjxlu5ar2zw", + "https://example.com/cia5rhery00wghkjxkoktvbqj/cia5rhery00whhkjxjs6l9c1l/cia5rhery00wihkjx4c6l8wcz/cia5rhery00wjhkjxwrrx1kta", + "https://example.com/cia5rhery00wkhkjx4o64pmsg/cia5rhery00wlhkjx5jwjko3n/cia5rhery00wmhkjxuj79fu0e/cia5rhery00wnhkjxdxjnmwu4", + "https://example.com/cia5rhery00wohkjxnj9yz95o/cia5rhery00wphkjxqs39n7we/cia5rhery00wqhkjxw7enbbt8/cia5rhery00wrhkjxhirvzsj8", + "https://example.com/cia5rhery00wshkjxklfnj99r/cia5rhery00wthkjxyvxvxzxm/cia5rhery00wuhkjxselr08ti/cia5rhery00wvhkjx0n4m1gwb", + "https://example.com/cia5rhery00wwhkjxidbq1641/cia5rhery00wxhkjxer0uj0bx/cia5rhery00wyhkjx3rj98a5m/cia5rhery00wzhkjxar7ucjyp", + "https://example.com/cia5rhery00x0hkjxikq8tega/cia5rhery00x1hkjxgq9t00p7/cia5rhery00x2hkjxzczhf4ta/cia5rhery00x3hkjxs8rl0xle", + "https://example.com/cia5rhery00x4hkjxtpgdpam7/cia5rhery00x5hkjxnudpwm02/cia5rhery00x6hkjx96jfugp6/cia5rhery00x7hkjxugyl5bsm", + "https://example.com/cia5rhery00x8hkjx26k3912r/cia5rhery00x9hkjx8j1o37fy/cia5rhery00xahkjxcnx1kjtl/cia5rhery00xbhkjxws0y4q9u", + "https://example.com/cia5rhery00xchkjxnceot2tu/cia5rhery00xdhkjxmfcanvsn/cia5rhery00xehkjxti3dt4zk/cia5rhery00xfhkjx4r9pxmk8", + "https://example.com/cia5rhery00xghkjxcl0s61iu/cia5rhery00xhhkjxy85ou2fq/cia5rhery00xihkjx8p53n3u3/cia5rhery00xjhkjx6dzo2asw", + "https://example.com/cia5rhery00xkhkjxcfvflel3/cia5rhery00xlhkjxjs82vnte/cia5rhery00xmhkjxrisis221/cia5rhery00xnhkjx92ojt9kd", + "https://example.com/cia5rhery00xohkjxxu18v57w/cia5rhery00xphkjx5gfp65ut/cia5rhery00xqhkjx0zug4xqu/cia5rhery00xrhkjxxqj8k3ce", + "https://example.com/cia5rhery00xshkjxbpshqaoi/cia5rhery00xthkjxthb498xj/cia5rhery00xuhkjxu6o0heam/cia5rhery00xvhkjxcxp7f3yh", + "https://example.com/cia5rhery00xwhkjxre2n0fww/cia5rhery00xxhkjxtu8s69t6/cia5rhery00xyhkjx7q1fm4xo/cia5rhery00xzhkjx6o1nu6ga", + "https://example.com/cia5rhery00y0hkjxlsfyk6o1/cia5rhery00y1hkjxpaqyucwy/cia5rhery00y2hkjxapp8yfj0/cia5rhery00y3hkjx0fnzbtnb", + "https://example.com/cia5rhery00y4hkjxlm2p2v6d/cia5rhery00y5hkjx0vzxj59x/cia5rhery00y6hkjxkamlg3ck/cia5rhery00y7hkjxkayvfx3a", + "https://example.com/cia5rhery00y8hkjxdwdcp5cs/cia5rherz00y9hkjxjb0teg8f/cia5rherz00yahkjxs1uzi2l6/cia5rherz00ybhkjxp449moik", + "https://example.com/cia5rherz00ychkjxwvi52xlt/cia5rherz00ydhkjxr7g9xhla/cia5rherz00yehkjxmrqqq92m/cia5rherz00yfhkjxljrtx13a", + "https://example.com/cia5rherz00yghkjx6sxe01ps/cia5rherz00yhhkjxh27l6kvi/cia5rherz00yihkjxbrazjcpk/cia5rherz00yjhkjxmi6ft3qb", + "https://example.com/cia5rherz00ykhkjxifkk8p2x/cia5rherz00ylhkjxpqz9t3cb/cia5rherz00ymhkjxc00r45v0/cia5rherz00ynhkjxgnbgvycv", + "https://example.com/cia5rherz00yohkjxmr9yq0jk/cia5rherz00yphkjxaamwbi9t/cia5rherz00yqhkjxv53l03jj/cia5rherz00yrhkjxsvkylos9", + "https://example.com/cia5rherz00yshkjx3vhhy1ut/cia5rherz00ythkjxnx4cssnw/cia5rherz00yuhkjxyojj0lzk/cia5rherz00yvhkjxoyozoftr", + "https://example.com/cia5rherz00ywhkjxl9hqei9p/cia5rherz00yxhkjxyefcfdtf/cia5rherz00yyhkjxpmcgonjp/cia5rherz00yzhkjxtp7r9f3r", + "https://example.com/cia5rherz00z0hkjxrhistyei/cia5rherz00z1hkjxinya1udt/cia5rherz00z2hkjxt2uibesw/cia5rherz00z3hkjxrv504cf7", + "https://example.com/cia5rherz00z4hkjxo0a9j311/cia5rherz00z5hkjxazvef1je/cia5rherz00z6hkjxb75qrdko/cia5rherz00z7hkjxnv1dgzal", + "https://example.com/cia5rherz00z8hkjx8ny20q55/cia5rherz00z9hkjxwlj70f0m/cia5rherz00zahkjx42hyz0m2/cia5rherz00zbhkjxwotnxn7y", + "https://example.com/cia5rherz00zchkjxa1tke93x/cia5rherz00zdhkjxzd8wghy0/cia5rherz00zehkjxba383v6a/cia5rherz00zfhkjxjkcs4bwl", + "https://example.com/cia5rherz00zghkjxk8sklzkb/cia5rherz00zhhkjxfwy3q53n/cia5rherz00zihkjxvheuc2pr/cia5rherz00zjhkjxbzljm6zl", + "https://example.com/cia5rherz00zkhkjxv84kqu85/cia5rherz00zlhkjxso9bvlw7/cia5rherz00zmhkjxib6w80kz/cia5rherz00znhkjx41d5nf7s", + "https://example.com/cia5rherz00zohkjxdd3iy5vu/cia5rherz00zphkjxwad53vl4/cia5rherz00zqhkjx4ad3e109/cia5rherz00zrhkjx4r6bwsia", + "https://example.com/cia5rherz00zshkjx1rzmmdvs/cia5rherz00zthkjx6165yn1j/cia5rherz00zuhkjxnefkdvqx/cia5rherz00zvhkjxgm3q4960", + "https://example.com/cia5rherz00zwhkjx78quz63t/cia5rherz00zxhkjxdj7uhzb1/cia5rherz00zyhkjxgfrciagd/cia5rherz00zzhkjx2fhew3jm", + "https://example.com/cia5rherz0100hkjxpoguaspc/cia5rherz0101hkjxj8wf0hvv/cia5rherz0102hkjxft1bu9bg/cia5rherz0103hkjxh7rc7icq", + "https://example.com/cia5rherz0104hkjxl336njqd/cia5rherz0105hkjxq5od0pbq/cia5rherz0106hkjxtgk2vqld/cia5rherz0107hkjxnyquy58x", + "https://example.com/cia5rherz0108hkjx0e8vlkuv/cia5rherz0109hkjx1w4ao3zl/cia5rherz010ahkjx0uvsczyx/cia5rherz010bhkjxctueaktb", + "https://example.com/cia5rherz010chkjxgocyaoln/cia5rherz010dhkjxvym9xjpu/cia5rherz010ehkjxkmv9qd1h/cia5rherz010fhkjxux4kwy9m", + "https://example.com/cia5rherz010ghkjxgjg51jll/cia5rherz010hhkjxheyloqok/cia5rherz010ihkjxocdihpf0/cia5rherz010jhkjxkjob7g4l", + "https://example.com/cia5rherz010khkjxyqnrc520/cia5rherz010lhkjxyfmva03e/cia5rherz010mhkjx7pf5a1q1/cia5rherz010nhkjxvcbajwq5", + "https://example.com/cia5rherz010ohkjxlj1peujy/cia5rherz010phkjxbe0edddw/cia5rherz010qhkjxese0v0d0/cia5rherz010rhkjx3z5sqmvd", + "https://example.com/cia5rherz010shkjx031cq64v/cia5rherz010thkjxo31twbuq/cia5rherz010uhkjxx0w89wkt/cia5rherz010vhkjxunc21rd5", + "https://example.com/cia5rherz010whkjxokxwgntu/cia5rherz010xhkjx8zhcummr/cia5rherz010yhkjxrr19wgdd/cia5rherz010zhkjx3krrrmfy", + "https://example.com/cia5rherz0110hkjxsnag5gy8/cia5rherz0111hkjxti3yt0uo/cia5rherz0112hkjxuvxezwly/cia5rherz0113hkjx42tjs0w1", + "https://example.com/cia5rherz0114hkjxhap5ggkp/cia5rherz0115hkjxhpvs28ez/cia5rherz0116hkjxchtzr6ub/cia5rherz0117hkjxldrskwe8", + "https://example.com/cia5rherz0118hkjx3moumoc6/cia5rherz0119hkjx8jmsv1po/cia5rherz011ahkjx5tbk1781/cia5rherz011bhkjxda8axg94", + "https://example.com/cia5rherz011chkjxpch14tnq/cia5rherz011dhkjx779uxhge/cia5rherz011ehkjxd1gossfl/cia5rherz011fhkjxe6fsxfle", + "https://example.com/cia5rherz011ghkjxkv0e5x6o/cia5rherz011hhkjxrmm01lop/cia5rherz011ihkjxadtpfth3/cia5rherz011jhkjxmsbqnjtx", + "https://example.com/cia5rherz011khkjxs9v8q989/cia5rherz011lhkjxbb47ojmz/cia5rherz011mhkjxfqrbtm2s/cia5rherz011nhkjxf0ukz3z7", + "https://example.com/cia5rherz011ohkjx1ljxj85v/cia5rherz011phkjxzyc52kr1/cia5rherz011qhkjxx03aq7rt/cia5rherz011rhkjxib8yi6wz", + "https://example.com/cia5rherz011shkjxelvnazea/cia5rherz011thkjx6ge3ekjc/cia5rherz011uhkjxj6spkxml/cia5rherz011vhkjx2g4n6l67", + "https://example.com/cia5rherz011whkjxoa9tcq7o/cia5rherz011xhkjx0aa7y41v/cia5rherz011yhkjx56c9pqwk/cia5rherz011zhkjx5iy36w89", + "https://example.com/cia5rherz0120hkjx04d4k3fi/cia5rherz0121hkjxvl6nw4m5/cia5rherz0122hkjxxc1jbk55/cia5rherz0123hkjx0pjnf99r", + "https://example.com/cia5rherz0124hkjx91z4pze5/cia5rherz0125hkjxg7qc4u38/cia5rherz0126hkjx1yegfvw4/cia5rherz0127hkjxl15ygp9h", + "https://example.com/cia5rherz0128hkjx4tkyj2om/cia5rherz0129hkjx7h7oqbrp/cia5rherz012ahkjxkajenzcs/cia5rherz012bhkjxi4bowdxs", + "https://example.com/cia5rherz012chkjx2szlut25/cia5rherz012dhkjxupa098p0/cia5rherz012ehkjxlg93n5ca/cia5rherz012fhkjx3e3lqc5s", + "https://example.com/cia5rherz012ghkjxiypurty6/cia5rherz012hhkjxnpuba7yn/cia5rherz012ihkjxqajb87r0/cia5rherz012jhkjx3w8tfq58", + "https://example.com/cia5rherz012khkjx6kpvfmqc/cia5rherz012lhkjxle4ozx4b/cia5rherz012mhkjxcx3zh6l8/cia5rherz012nhkjxwhol1p7z", + "https://example.com/cia5rherz012ohkjx8hcbuea8/cia5rherz012phkjxjn78pjpu/cia5rherz012qhkjxcso0w7ob/cia5rherz012rhkjxuwsrzjnb", + "https://example.com/cia5rherz012shkjxmfrs9kl4/cia5rherz012thkjxqd79q3jk/cia5rherz012uhkjx4y92c2l9/cia5rherz012vhkjxah5zn3ql", + "https://example.com/cia5rherz012whkjxnoco7250/cia5rherz012xhkjx50xbnyn6/cia5rherz012yhkjxhiz0qo7f/cia5rherz012zhkjxpvm1udb0", + "https://example.com/cia5rherz0130hkjxo3o4fndr/cia5rherz0131hkjxkz4fvrzq/cia5rherz0132hkjxz7tax104/cia5rherz0133hkjx1g0g06dn", + "https://example.com/cia5rherz0134hkjx5y2031n3/cia5rherz0135hkjx7uqpqo6r/cia5rherz0136hkjxy33y4m0i/cia5rherz0137hkjxi2zwuxom", + "https://example.com/cia5rherz0138hkjx2r63kcp2/cia5rherz0139hkjxqhukego2/cia5rherz013ahkjxrct5kwo5/cia5rherz013bhkjxlwdsrkdb", + "https://example.com/cia5rherz013chkjxqb7drtld/cia5rherz013dhkjxhk7nzkp6/cia5rherz013ehkjxt59enx4r/cia5rherz013fhkjxmequgudl", + "https://example.com/cia5rherz013ghkjxsvtndprv/cia5rherz013hhkjx5qzj9yky/cia5rherz013ihkjx7wi51091/cia5rherz013jhkjx07qd1vho", + "https://example.com/cia5rherz013khkjxxhuteqrg/cia5rherz013lhkjxnytihmq0/cia5rherz013mhkjxxcuyopb7/cia5rherz013nhkjxz8wjm6kv", + "https://example.com/cia5rherz013ohkjxti624ceh/cia5rherz013phkjx9es0m8z1/cia5rherz013qhkjx2thq0yiq/cia5rherz013rhkjxcz1h935h", + "https://example.com/cia5rherz013shkjxy1fcs2p9/cia5rherz013thkjxqj1f3hzd/cia5rherz013uhkjx9n3img9m/cia5rherz013vhkjxbbsd9s7u", + "https://example.com/cia5rherz013whkjxzrks74yw/cia5rherz013xhkjx19u9gnum/cia5rherz013yhkjxf189dqov/cia5rherz013zhkjxn840ifqp", + "https://example.com/cia5rherz0140hkjxzflkd8o3/cia5rherz0141hkjxth1k5pcv/cia5rherz0142hkjxk4tx2d6t/cia5rherz0143hkjxua6in4hi", + "https://example.com/cia5rherz0144hkjxh5223dp4/cia5rherz0145hkjxggdx0inf/cia5rherz0146hkjxukma20rn/cia5rherz0147hkjxbz6yr3vj", + "https://example.com/cia5rherz0148hkjxj6yz49cp/cia5rherz0149hkjxnshaboc7/cia5rherz014ahkjx9k7w03oz/cia5rherz014bhkjxim3qdl32", + "https://example.com/cia5rherz014chkjxkwm4bedt/cia5rherz014dhkjxd87owzz9/cia5rherz014ehkjx7gvsq5h8/cia5rherz014fhkjxvg5i2lzo", + "https://example.com/cia5rhes0014ghkjx57nwx3bd/cia5rhes0014hhkjx7yg5lnmm/cia5rhes0014ihkjxpcj5y5pc/cia5rhes0014jhkjxx7pqjwyi", + "https://example.com/cia5rhes0014khkjx8je94eu9/cia5rhes0014lhkjxj7s0ayqj/cia5rhes0014mhkjxaj8frq8f/cia5rhes0014nhkjxsvfwikzc", + "https://example.com/cia5rhes0014ohkjx7w5zhsfa/cia5rhes0014phkjxb8znpn93/cia5rhes0014qhkjx26dojt2q/cia5rhes0014rhkjx4z51j3v1", + "https://example.com/cia5rhes0014shkjxsrfqfh66/cia5rhes0014thkjxchkutc4l/cia5rhes0014uhkjx61hu197u/cia5rhes0014vhkjx88tbe055", + "https://example.com/cia5rhes0014whkjxnsy6o8oh/cia5rhes0014xhkjxhf7qa11c/cia5rhes0014yhkjx8dg54bhs/cia5rhes0014zhkjxwddjjbfx", + "https://example.com/cia5rhes00150hkjxvbx9bs0t/cia5rhes00151hkjx1ndja821/cia5rhes00152hkjxgre5jaft/cia5rhes00153hkjx403j16ab", + "https://example.com/cia5rhes00154hkjx850qetqc/cia5rhes00155hkjx25fuxyq1/cia5rhes00156hkjxt0otyqf9/cia5rhes00157hkjxkrckit2g", + "https://example.com/cia5rhes00158hkjxeka610dd/cia5rhes00159hkjxirohiw4g/cia5rhes0015ahkjx4a2e7hwj/cia5rhes0015bhkjx54ew959x", + "https://example.com/cia5rhes0015chkjxdbymvixv/cia5rhes0015dhkjxehyc0l2p/cia5rhes0015ehkjxkkzsw7sr/cia5rhes0015fhkjx694x9jdr", + "https://example.com/cia5rhes0015ghkjx8524c513/cia5rhes0015hhkjx9gbh0axg/cia5rhes0015ihkjxhux4m9va/cia5rhes0015jhkjxizfpn19a", + "https://example.com/cia5rhes0015khkjxmy1viucg/cia5rhes0015lhkjx4k9cpi1x/cia5rhes0015mhkjxlwccit5i/cia5rhes0015nhkjxyyerl1x5", + "https://example.com/cia5rhes0015ohkjxse6u4cq1/cia5rhes0015phkjxmbosv5k1/cia5rhes0015qhkjxagd18q9e/cia5rhes0015rhkjx18k37mza", + "https://example.com/cia5rhes0015shkjxkf88qpr2/cia5rhes0015thkjxxjngloiv/cia5rhes0015uhkjxw3p51ph3/cia5rhes0015vhkjxuv117q6n", + "https://example.com/cia5rhes0015whkjxepiuli5w/cia5rhes0015xhkjx6912ozju/cia5rhes0015yhkjxs50s4iw9/cia5rhes0015zhkjx4fqv3fj5", + "https://example.com/cia5rhes00160hkjxaezek04y/cia5rhes00161hkjxo7jexmqa/cia5rhes00162hkjx8qt4t84r/cia5rhes00163hkjx0x35v1ea", + "https://example.com/cia5rhes00164hkjxum5cztru/cia5rhes00165hkjxykw801f6/cia5rhes00166hkjx87cgbtl9/cia5rhes00167hkjxr5laneo4", + "https://example.com/cia5rhes00168hkjx4675xx8q/cia5rhes00169hkjxa69bs98w/cia5rhes0016ahkjxgxbg1ktg/cia5rhes0016bhkjxcssrfeb6", + "https://example.com/cia5rhes0016chkjxskrmxbeu/cia5rhes0016dhkjxchuf9w7d/cia5rhes0016ehkjx96tmup0w/cia5rhes0016fhkjx3b2ir9k8", + "https://example.com/cia5rhes0016ghkjxshn9r5cd/cia5rhes0016hhkjxt0okuboo/cia5rhes0016ihkjx3xc5n1z7/cia5rhes0016jhkjxmm1rhinv", + "https://example.com/cia5rhes0016khkjxs4md552m/cia5rhes0016lhkjxdqs7jlks/cia5rhes0016mhkjxsbqpbr27/cia5rhes0016nhkjxsw0eoqxh", + "https://example.com/cia5rhes0016ohkjxj1uyawl4/cia5rhes0016phkjx4s3keqvp/cia5rhes0016qhkjxlr2ujty3/cia5rhes0016rhkjxshluxgzs", + "https://example.com/cia5rhes0016shkjxmjm47a2n/cia5rhes0016thkjxvawl3vod/cia5rhes0016uhkjxlkwcmxrn/cia5rhes0016vhkjx0yrlq0k0", + "https://example.com/cia5rhes0016whkjx47wacy0d/cia5rhes0016xhkjxx20x2adr/cia5rhes0016yhkjxpyafhbax/cia5rhes0016zhkjxkm7homqb", + "https://example.com/cia5rhes00170hkjx58dppj32/cia5rhes00171hkjxgl9ekfiu/cia5rhes00172hkjx35hn4ajh/cia5rhes00173hkjx4k5x795i", + "https://example.com/cia5rhes00174hkjx431k7e7c/cia5rhes00175hkjxdfvrfwqy/cia5rhes00176hkjx2wqmhu6x/cia5rhes00177hkjxd8ykmqj9", + "https://example.com/cia5rhes00178hkjxpbwz0dv1/cia5rhes00179hkjxfxnx8xde/cia5rhes0017ahkjxnumozh8d/cia5rhes0017bhkjxjvgv7bu3", + "https://example.com/cia5rhes0017chkjx6cbtaca4/cia5rhes0017dhkjxl6oa2n77/cia5rhes0017ehkjxv4e7c73p/cia5rhes0017fhkjxarapkb0w", + "https://example.com/cia5rhes0017ghkjxvzviznq4/cia5rhes0017hhkjxkxkq2w0r/cia5rhes0017ihkjxfdhga9qz/cia5rhes0017jhkjxzr0zbpli", + "https://example.com/cia5rhes0017khkjxd6x7dl0m/cia5rhes0017lhkjxpa472u8x/cia5rhes0017mhkjxi7scj2zd/cia5rhes0017nhkjxcrar3doc", + "https://example.com/cia5rhes1017ohkjxim1b2tgs/cia5rhes1017phkjxido7zpq3/cia5rhes1017qhkjxzhgszmuh/cia5rhes1017rhkjxh9n4vlu9", + "https://example.com/cia5rhes1017shkjxazazffwt/cia5rhes1017thkjxt1mu7dkg/cia5rhes1017uhkjx79p8vex3/cia5rhes1017vhkjxzk3rwfaj", + "https://example.com/cia5rhes1017whkjxg9ldz44h/cia5rhes1017xhkjxubjc7d35/cia5rhes1017yhkjxyn9t58r7/cia5rhes1017zhkjx822o28jf", + "https://example.com/cia5rhes10180hkjxy0zeitqi/cia5rhes10181hkjxuiumypud/cia5rhes10182hkjxqhf3xprn/cia5rhes10183hkjx9orcdf2t", + "https://example.com/cia5rhes10184hkjx60vpjosn/cia5rhes10185hkjxiuxdbrjp/cia5rhes10186hkjxjazso4v3/cia5rhes10187hkjx1yda3p8i", + "https://example.com/cia5rhes10188hkjx67qn30yn/cia5rhes10189hkjxd8e62yud/cia5rhes1018ahkjxr1ogihzw/cia5rhes1018bhkjxqa83yxs2", + "https://example.com/cia5rhes1018chkjxcijm6ol2/cia5rhes1018dhkjxn27lkryl/cia5rhes1018ehkjxin74swtd/cia5rhes1018fhkjxc3n9hjub", + "https://example.com/cia5rhes1018ghkjxs06i4n1v/cia5rhes1018hhkjxtbrrprdd/cia5rhes1018ihkjxh375u2d5/cia5rhes1018jhkjxwe4m1w3k", + "https://example.com/cia5rhes1018khkjxc2fo3tyn/cia5rhes1018lhkjx11wqgr3o/cia5rhes1018mhkjx55cz73km/cia5rhes1018nhkjx027g05rh", + "https://example.com/cia5rhes1018ohkjxuxt9w0qg/cia5rhes1018phkjxuppi0zpt/cia5rhes1018qhkjx3hedzfgq/cia5rhes1018rhkjxdbef85sg", + "https://example.com/cia5rhes1018shkjxh23bn4hl/cia5rhes1018thkjx2jo33xt5/cia5rhes1018uhkjxgrf6z3q5/cia5rhes1018vhkjxs51u2bsq", + "https://example.com/cia5rhes1018whkjxh98q42o4/cia5rhes1018xhkjxtktqtwob/cia5rhes1018yhkjxxf9qq7me/cia5rhes1018zhkjx540am2xr", + "https://example.com/cia5rhes10190hkjxcom1s1af/cia5rhes10191hkjxr15i8zfz/cia5rhes10192hkjxbsyx6pqa/cia5rhes10193hkjx5lfk3tnz", + "https://example.com/cia5rhes10194hkjx63khbmh5/cia5rhes10195hkjx23tzm25c/cia5rhes10196hkjx3tu55kps/cia5rhes10197hkjxt9kgwuye", + "https://example.com/cia5rhes10198hkjxvsic8zyi/cia5rhes10199hkjxiqcxj6ha/cia5rhes1019ahkjxul53ymxv/cia5rhes1019bhkjx8j5i4gjo", + "https://example.com/cia5rhes1019chkjxhkzab45h/cia5rhes1019dhkjxp9m537kv/cia5rhes1019ehkjxflgayfwl/cia5rhes1019fhkjxpxcfuwm7", + "https://example.com/cia5rhes1019ghkjx0ec3mbfs/cia5rhes1019hhkjxpzum6b24/cia5rhes1019ihkjx8l7ygjw5/cia5rhes1019jhkjxc4kywxia", + "https://example.com/cia5rhes1019khkjxcgdm2x8i/cia5rhes1019lhkjxsd4z7axk/cia5rhes1019mhkjxrivl4h0v/cia5rhes1019nhkjxwq5r7rjw", + "https://example.com/cia5rhes1019ohkjxlbgrt2qs/cia5rhes1019phkjxrqx7xr97/cia5rhes1019qhkjxqxuaxnbc/cia5rhes1019rhkjx479nva7e", + "https://example.com/cia5rhes1019shkjxo903skww/cia5rhes1019thkjx835fib01/cia5rhes1019uhkjxqnb5hb1c/cia5rhes1019vhkjx985hdr8a", + "https://example.com/cia5rhes1019whkjxul29xzs7/cia5rhes1019xhkjx8v769rft/cia5rhes1019yhkjx8moz4avh/cia5rhes1019zhkjxltk1bmj1", + "https://example.com/cia5rhes101a0hkjxgj1bgqcu/cia5rhes101a1hkjxam87hyv6/cia5rhes101a2hkjx6n7xfzcf/cia5rhes101a3hkjxwhuzx4bu", + "https://example.com/cia5rhes101a4hkjxkz4gt4bb/cia5rhes101a5hkjx8jto6sbw/cia5rhes101a6hkjxdkz6q053/cia5rhes101a7hkjxafsa477k", + "https://example.com/cia5rhes101a8hkjxmawq81f9/cia5rhes101a9hkjx7a1m25vl/cia5rhes101aahkjxn9vib2k1/cia5rhes101abhkjxnd9lr35m", + "https://example.com/cia5rhes101achkjxaz60ife8/cia5rhes101adhkjxb9jyduwc/cia5rhes101aehkjximmqxxdc/cia5rhes101afhkjxrivbhs77", + "https://example.com/cia5rhes101aghkjxrq2da1pd/cia5rhes101ahhkjxjcuopb44/cia5rhes101aihkjxmeqnm90k/cia5rhes101ajhkjxli2mp598", + "https://example.com/cia5rhes101akhkjx3uzisjok/cia5rhes101alhkjx2z2rnozw/cia5rhes101amhkjxektigddg/cia5rhes101anhkjxvsxrqsn5", + "https://example.com/cia5rhes101aohkjxfb78h44w/cia5rhes101aphkjx2g9hr8n2/cia5rhes101aqhkjx61plt1q1/cia5rhes101arhkjxajstaro6", + "https://example.com/cia5rhes101ashkjxm5zox05g/cia5rhes101athkjxqnofipd6/cia5rhes101auhkjxr39j3scv/cia5rhes101avhkjxcnv43592", + "https://example.com/cia5rhes101awhkjxi5amucip/cia5rhes101axhkjxy05rb4by/cia5rhes101ayhkjxoqbug0w2/cia5rhes101azhkjxobqv30io", + "https://example.com/cia5rhes101b0hkjxgtxrq6a1/cia5rhes101b1hkjx3kckskk9/cia5rhes101b2hkjxgp3x3n2k/cia5rhes101b3hkjxjoa91opd", + "https://example.com/cia5rhes101b4hkjxl0uryndo/cia5rhes101b5hkjxn8o6oumu/cia5rhes101b6hkjxrjbze70s/cia5rhes101b7hkjxv5mrjv5y", + "https://example.com/cia5rhes101b8hkjx7yeg3s3m/cia5rhes101b9hkjxnchy3mil/cia5rhes101bahkjxawomopeo/cia5rhes101bbhkjx9oml99jy", + "https://example.com/cia5rhes101bchkjxzaccplvr/cia5rhes101bdhkjxmv4u1l8n/cia5rhes101behkjxja90rgy0/cia5rhes101bfhkjxolfzxocw", + "https://example.com/cia5rhes101bghkjxy1vfbaaw/cia5rhes101bhhkjxg6xznpan/cia5rhes101bihkjxlg9fzku8/cia5rhes101bjhkjxnh2hjnui", + "https://example.com/cia5rhes101bkhkjxsclo2zp3/cia5rhes101blhkjxuvrcudv5/cia5rhes101bmhkjx605j2zjj/cia5rhes101bnhkjx2xml0fvu", + "https://example.com/cia5rhes101bohkjx5gf3ijos/cia5rhes101bphkjxpe0su46e/cia5rhes101bqhkjxs22f7ad2/cia5rhes101brhkjx9agg7eo1", + "https://example.com/cia5rhes101bshkjx3sn7g8yy/cia5rhes101bthkjxe04n3g8b/cia5rhes101buhkjxgy5w6ts0/cia5rhes101bvhkjx7q91193i", + "https://example.com/cia5rhes101bwhkjxfgzjxdtg/cia5rhes101bxhkjx9fof34tp/cia5rhes101byhkjxoyqeyb8o/cia5rhes101bzhkjxs4h8rhgv", + "https://example.com/cia5rhes101c0hkjxxj1zh5us/cia5rhes101c1hkjxoc2z40bk/cia5rhes101c2hkjxp2mh4dck/cia5rhes101c3hkjx11dpdt65", + "https://example.com/cia5rhes101c4hkjxrfzebrql/cia5rhes101c5hkjxhgaz06ty/cia5rhes101c6hkjxrs79e85g/cia5rhes101c7hkjxggrhbqyx", + "https://example.com/cia5rhes101c8hkjxvgxdgnbw/cia5rhes101c9hkjxyjttv60a/cia5rhes101cahkjxsq6fq2jl/cia5rhes101cbhkjx7q41av4q", + "https://example.com/cia5rhes101cchkjxovipv8ev/cia5rhes101cdhkjxqmj2adv7/cia5rhes101cehkjxkac54kr0/cia5rhes101cfhkjxuqcmlixm", + "https://example.com/cia5rhes101cghkjx8p3hy50r/cia5rhes101chhkjxitheakp9/cia5rhes101cihkjxrm1z2bcp/cia5rhes101cjhkjx6e60mdcr", + "https://example.com/cia5rhes101ckhkjxavxp0z0w/cia5rhes101clhkjxo78s8ce3/cia5rhes101cmhkjx3q5plsy4/cia5rhes101cnhkjxbr2dyljs", + "https://example.com/cia5rhes101cohkjxx6uzzh6z/cia5rhes101cphkjx39t0gmdt/cia5rhes101cqhkjx5cpi5gv2/cia5rhes101crhkjx8tiw2khg", + "https://example.com/cia5rhes101cshkjxx26p2oew/cia5rhes101cthkjxh5x6cctw/cia5rhes101cuhkjxmqbok5qb/cia5rhes101cvhkjx98q4u6vg", + "https://example.com/cia5rhes101cwhkjxca46qqdc/cia5rhes101cxhkjxkya9jblw/cia5rhes101cyhkjxsx55uj72/cia5rhes101czhkjx4px01ypv", + "https://example.com/cia5rhes201d0hkjxrfq1bxuy/cia5rhes201d1hkjxum4pm3s6/cia5rhes201d2hkjx9djj6tvc/cia5rhes201d3hkjxkobt5p5a", + "https://example.com/cia5rhes201d4hkjx6vbuy1h3/cia5rhes201d5hkjxtyrtq6sn/cia5rhes201d6hkjxyn0dbgeq/cia5rhes201d7hkjx9g1d2pu0", + "https://example.com/cia5rhes201d8hkjxsci6f24w/cia5rhes201d9hkjxd8q6ugbk/cia5rhes201dahkjx8c8yunrs/cia5rhes201dbhkjxb657b9hh", + "https://example.com/cia5rhes201dchkjxhpytp0es/cia5rhes201ddhkjxzz6or9dl/cia5rhes201dehkjxvt1iaj4e/cia5rhes201dfhkjxcovukh36", + "https://example.com/cia5rhes201dghkjxs4wcuyr5/cia5rhes201dhhkjxa3ltvy94/cia5rhes201dihkjx1q3i72ys/cia5rhes201djhkjxjgthq1xi", + "https://example.com/cia5rhes201dkhkjxsvsw8r7g/cia5rhes201dlhkjxho9dzz7z/cia5rhes201dmhkjxtd6y9lt9/cia5rhes201dnhkjx2mryfja5", + "https://example.com/cia5rhes201dohkjx1qpsam6z/cia5rhes201dphkjxyqckmdus/cia5rhes201dqhkjx05x0cua4/cia5rhes201drhkjxlv48ezca", + "https://example.com/cia5rhes201dshkjxv3tvrlnv/cia5rhes201dthkjxsb1vp68a/cia5rhes201duhkjxr3dpwbsl/cia5rhes201dvhkjxooy13asr", + "https://example.com/cia5rhes201dwhkjxy2yxmf1a/cia5rhes201dxhkjxg7ddbk62/cia5rhes201dyhkjxyfw66d9i/cia5rhes201dzhkjxhiriqvpp", + "https://example.com/cia5rhes201e0hkjxgnojdvfu/cia5rhes201e1hkjx35d46pkf/cia5rhes201e2hkjx8al6xyxc/cia5rhes201e3hkjxpm8o33n5", + "https://example.com/cia5rhes201e4hkjxgmt6q22i/cia5rhes201e5hkjxcxujptph/cia5rhes201e6hkjxvjqvqv5y/cia5rhes201e7hkjx7frk9v00", + "https://example.com/cia5rhes201e8hkjxwksc2h6k/cia5rhes201e9hkjxmrv2nebe/cia5rhes201eahkjxju4ycxem/cia5rhes201ebhkjxu63x5ai0", + "https://example.com/cia5rhes201echkjxq4et8qb3/cia5rhes201edhkjxmawlqvb6/cia5rhes201eehkjx5mvbc5jf/cia5rhes201efhkjxf81g9ft0", + "https://example.com/cia5rhes201eghkjxwc2n8rrz/cia5rhes201ehhkjx96jrb9qp/cia5rhes201eihkjxolmvqk0b/cia5rhes201ejhkjx8t4yxqdy", + "https://example.com/cia5rhes201ekhkjxjj375p8m/cia5rhes201elhkjxd7n988u0/cia5rhes201emhkjx4sgv75jt/cia5rhes201enhkjx89v2rpwd", + "https://example.com/cia5rhes201eohkjx441c02sl/cia5rhes201ephkjxicff9k4p/cia5rhes201eqhkjx5c7sjm4x/cia5rhes201erhkjxvl0a13y1", + "https://example.com/cia5rhes201eshkjxf8inxrty/cia5rhes201ethkjxqjixrhe3/cia5rhes201euhkjx6cq543as/cia5rhes201evhkjxiq4rvbm6", + "https://example.com/cia5rhes201ewhkjxpzr481o0/cia5rhes201exhkjxfqo3ya1u/cia5rhes201eyhkjxzaieceuz/cia5rhes201ezhkjxrp9aiyto", + "https://example.com/cia5rhes201f0hkjxdxg04ktt/cia5rhes201f1hkjxc5xqh8w6/cia5rhes201f2hkjxxqt7mk69/cia5rhes201f3hkjxhz4mt35k", + "https://example.com/cia5rhes201f4hkjxwif8ix73/cia5rhes201f5hkjxcnk41o2f/cia5rhes201f6hkjxqvmwkmte/cia5rhes201f7hkjxjhf0rwkd", + "https://example.com/cia5rhes201f8hkjxgu0mbayd/cia5rhes201f9hkjxoirm2pi6/cia5rhes201fahkjx3eggxv1v/cia5rhes201fbhkjx3dr0v5lr", + "https://example.com/cia5rhes201fchkjx9bl00653/cia5rhes201fdhkjxd986f7dy/cia5rhes201fehkjxcjhtezko/cia5rhes201ffhkjx2g6pp08r", + "https://example.com/cia5rhes201fghkjxieabfnjf/cia5rhes201fhhkjxhncmkptc/cia5rhes201fihkjxd2idn405/cia5rhes201fjhkjxxh12k6dz", + "https://example.com/cia5rhes201fkhkjxjhb8dl6c/cia5rhes201flhkjxjesmmxj5/cia5rhes201fmhkjxgdq4watu/cia5rhes201fnhkjxcx2v7046", + "https://example.com/cia5rhes201fohkjxooyrgbd6/cia5rhes201fphkjxnswgkqhg/cia5rhes201fqhkjxr1olqtyi/cia5rhes201frhkjxpylkppc7", + "https://example.com/cia5rhes201fshkjxss3f3m7a/cia5rhes201fthkjxtb752b31/cia5rhes201fuhkjxl8v5tked/cia5rhes201fvhkjxs83n3lna", + "https://example.com/cia5rhes201fwhkjx7mn2ufyp/cia5rhes201fxhkjxykgvds9s/cia5rhes201fyhkjxzj880aau/cia5rhes201fzhkjx9hmzn5w1", + "https://example.com/cia5rhes201g0hkjx40m23pfq/cia5rhes201g1hkjxu5axzq44/cia5rhes201g2hkjxtenwlezp/cia5rhes201g3hkjxeaanxtc3", + "https://example.com/cia5rhes201g4hkjxfko2j17a/cia5rhes201g5hkjxdngk92iq/cia5rhes201g6hkjxyiixm3h3/cia5rhes201g7hkjx1e0o9rbr", + "https://example.com/cia5rhes201g8hkjxzvuuf9mr/cia5rhes201g9hkjx9i4067eb/cia5rhes201gahkjxe0877b7o/cia5rhes201gbhkjxhjeqydx3", + "https://example.com/cia5rhes201gchkjx28buxxph/cia5rhes201gdhkjx11mlvzu6/cia5rhes201gehkjx56z31f3p/cia5rhes201gfhkjxon8mxyaq", + "https://example.com/cia5rhes201gghkjxzhavbsbu/cia5rhes201ghhkjxpalfbbgq/cia5rhes201gihkjxmg14pb0i/cia5rhes201gjhkjxz1k6lfox", + "https://example.com/cia5rhes201gkhkjxr91y8n1x/cia5rhes201glhkjxcd8gf56b/cia5rhes201gmhkjxgmgi5aag/cia5rhes201gnhkjxzuskm0u3", + "https://example.com/cia5rhes201gohkjxh6stmdzj/cia5rhes201gphkjxjhxmrc1z/cia5rhes201gqhkjxbsb6x26m/cia5rhes201grhkjx2qjq5azu", + "https://example.com/cia5rhes201gshkjxwgykuiuh/cia5rhes201gthkjxshezzoh7/cia5rhes201guhkjxhxk5wn0c/cia5rhes201gvhkjxfgd3dy5o", + "https://example.com/cia5rhes201gwhkjxrjdm59mt/cia5rhes201gxhkjx5p1au9tm/cia5rhes201gyhkjx2fhr9h8l/cia5rhes201gzhkjx1d6ey84l", + "https://example.com/cia5rhes201h0hkjxw539qclb/cia5rhes201h1hkjxtasbgd4k/cia5rhes201h2hkjxnggs4jvi/cia5rhes201h3hkjx10i4oa01", + "https://example.com/cia5rhes201h4hkjxc4yc7ah2/cia5rhes201h5hkjxpp8h6vjy/cia5rhes201h6hkjx3is219tv/cia5rhes201h7hkjxi5vczfdr", + "https://example.com/cia5rhes201h8hkjx0pnfnjv8/cia5rhes201h9hkjxvab7mw12/cia5rhes201hahkjxpdkx31mo/cia5rhes201hbhkjx5qrrpii9", + "https://example.com/cia5rhes201hchkjxxpstoh0r/cia5rhes201hdhkjxd3fqr26w/cia5rhes201hehkjxa89a8p00/cia5rhes201hfhkjxjb7dx816", + "https://example.com/cia5rhes201hghkjxoundwtvv/cia5rhes201hhhkjxq0l8n544/cia5rhes201hihkjxfnxig9tq/cia5rhes201hjhkjxmthbhba3", + "https://example.com/cia5rhes201hkhkjxh8del6ix/cia5rhes201hlhkjxbbkqryiz/cia5rhes201hmhkjxsrbt8rwc/cia5rhes201hnhkjxvjinr83g", + "https://example.com/cia5rhes201hohkjxnm39jamh/cia5rhes201hphkjxbpdbg85s/cia5rhes201hqhkjxt4xsrvvw/cia5rhes201hrhkjx28uncmqm", + "https://example.com/cia5rhes201hshkjx9y3havo3/cia5rhes201hthkjxsl3xf65k/cia5rhes201huhkjxevlc6mpu/cia5rhes201hvhkjxios9hjnc", + "https://example.com/cia5rhes201hwhkjx1gqoupdx/cia5rhes201hxhkjxoezqmdn4/cia5rhes201hyhkjxsd34q556/cia5rhes201hzhkjxvkqinyu3", + "https://example.com/cia5rhes201i0hkjxtuqp1qgj/cia5rhes201i1hkjxjq0bui86/cia5rhes201i2hkjxlu4behua/cia5rhes201i3hkjxbarxd26f", + "https://example.com/cia5rhes201i4hkjx1dgyo81l/cia5rhes201i5hkjx3xb9oqcc/cia5rhes201i6hkjxgiyz2tkh/cia5rhes201i7hkjx6w3cspdt", + "https://example.com/cia5rhes201i8hkjxgazao8qk/cia5rhes201i9hkjx9g0kulps/cia5rhes201iahkjxo3xkc8pd/cia5rhes201ibhkjx1dqefi47", + "https://example.com/cia5rhes201ichkjxg5gkkixu/cia5rhes201idhkjxy4ocvh6v/cia5rhes201iehkjx4cyin399/cia5rhes201ifhkjxx2gjdbml", + "https://example.com/cia5rhes201ighkjxhg0kk0p1/cia5rhes201ihhkjxt9erqxzy/cia5rhes201iihkjxorqialmn/cia5rhes201ijhkjxuj6s809s", + "https://example.com/cia5rhes201ikhkjx0vne1gub/cia5rhes201ilhkjxvumlvx2e/cia5rhes201imhkjxkwp8knsu/cia5rhes201inhkjxi7n4t5yd", + "https://example.com/cia5rhes201iohkjxzho5l61h/cia5rhes201iphkjxxe8fo8zr/cia5rhes201iqhkjxqnsimx8u/cia5rhes201irhkjxecgdhcvp", + "https://example.com/cia5rhes201ishkjx2fi6dek5/cia5rhes201ithkjxf3i7k6mm/cia5rhes201iuhkjx0uioh430/cia5rhes201ivhkjxqw1gumyl", + "https://example.com/cia5rhes201iwhkjxlqpv0zzb/cia5rhes201ixhkjxfxx80lsv/cia5rhes201iyhkjx6f1rt1ik/cia5rhes201izhkjx0pmgbqf9", + "https://example.com/cia5rhes201j0hkjxi3jo8eqm/cia5rhes201j1hkjx8iahnhoa/cia5rhes201j2hkjxcp1bjuci/cia5rhes201j3hkjxushcyv9h", + "https://example.com/cia5rhes201j4hkjxmfy3u8bq/cia5rhes201j5hkjxl6j0ozf5/cia5rhes201j6hkjx0jrm1hr3/cia5rhes201j7hkjxkgzgfuc2", + "https://example.com/cia5rhes201j8hkjx6sbhqirt/cia5rhes201j9hkjx3jm8ttkr/cia5rhes201jahkjx4ww2jkjb/cia5rhes201jbhkjx2vwmj8mw", + "https://example.com/cia5rhes201jchkjx6s6c38ry/cia5rhes201jdhkjxo5iduoju/cia5rhes201jehkjxl337z10k/cia5rhes201jfhkjxennzj2ed", + "https://example.com/cia5rhes201jghkjx65xshc5s/cia5rhes201jhhkjxtrvnzhf5/cia5rhes201jihkjxers53yxq/cia5rhes201jjhkjxw8nisucr", + "https://example.com/cia5rhes301jkhkjx7rpx2kp1/cia5rhes301jlhkjxa3840rux/cia5rhes301jmhkjx1943602l/cia5rhes301jnhkjxft42idno", + "https://example.com/cia5rhes301johkjxoa2e62n8/cia5rhes301jphkjx8jfoflvc/cia5rhes301jqhkjxjt9rh0u6/cia5rhes301jrhkjxofoa9vq2", + "https://example.com/cia5rhes301jshkjxnm4wl4ab/cia5rhes301jthkjx89ehf3ty/cia5rhes301juhkjxwubr4oap/cia5rhes301jvhkjxk2e8yz43", + "https://example.com/cia5rhes301jwhkjx4bo3r583/cia5rhes301jxhkjxfmbottug/cia5rhes301jyhkjx86nc7v6s/cia5rhes301jzhkjx9h9x7167", + "https://example.com/cia5rhes301k0hkjx0oc98odu/cia5rhes301k1hkjxdjynl4c1/cia5rhes301k2hkjxc471ye9i/cia5rhes301k3hkjxcawvse26", + "https://example.com/cia5rhes301k4hkjxsqss9ydm/cia5rhes301k5hkjx9e2cz05j/cia5rhes301k6hkjxjb18jpjj/cia5rhes301k7hkjxai3k1edl", + "https://example.com/cia5rhes301k8hkjxeqdbbtwd/cia5rhes301k9hkjxeliovzfz/cia5rhes301kahkjxt8kvwaw8/cia5rhes301kbhkjx334ytlc2", + "https://example.com/cia5rhes301kchkjxl1lize37/cia5rhes301kdhkjxczqjsftr/cia5rhes301kehkjxercwjrhh/cia5rhes301kfhkjxdeb3fvgv", + "https://example.com/cia5rhes301kghkjx0yk4gm2e/cia5rhes301khhkjxt4gdd4ly/cia5rhes301kihkjxegsqzb2u/cia5rhes301kjhkjxp1cug6e2", + "https://example.com/cia5rhes301kkhkjxyhe6rxl6/cia5rhes301klhkjxqfebfzea/cia5rhes301kmhkjxrz0qs4pq/cia5rhes301knhkjxyskiwz5y", + "https://example.com/cia5rhes301kohkjx3tpgvarg/cia5rhes301kphkjx19vihidz/cia5rhes301kqhkjxos60mu4k/cia5rhes301krhkjxbvenxr93", + "https://example.com/cia5rhes301kshkjx9ysyvjir/cia5rhes301kthkjxrk2z4v9t/cia5rhes301kuhkjxitxi78qg/cia5rhes301kvhkjx6m0cf7dl", + "https://example.com/cia5rhes301kwhkjxt4f6hr4z/cia5rhes301kxhkjxilxbilms/cia5rhes301kyhkjxotkf8aaj/cia5rhes301kzhkjx7czn8fdy", + "https://example.com/cia5rhes301l0hkjxf9jhzc7i/cia5rhes301l1hkjx1by7b0y3/cia5rhes301l2hkjxoo8obxiq/cia5rhes301l3hkjxvoc40tkj", + "https://example.com/cia5rhes301l4hkjxgjpxmlpv/cia5rhes301l5hkjx94yuj664/cia5rhes301l6hkjxr8e8y97y/cia5rhes301l7hkjxwznfxlhr", + "https://example.com/cia5rhes301l8hkjxif8hgss9/cia5rhes301l9hkjxls026lu2/cia5rhes301lahkjx8g221cqp/cia5rhes301lbhkjx5nnfkl1o", + "https://example.com/cia5rhes301lchkjx55outsg7/cia5rhes301ldhkjxa32ta3im/cia5rhes301lehkjxqzx1v4ag/cia5rhes301lfhkjxzs4h9iq8", + "https://example.com/cia5rhes301lghkjx9zymf1is/cia5rhes301lhhkjxxi8tt4p0/cia5rhes301lihkjxwsqjsjxe/cia5rhes301ljhkjxa2tfzt6w", + "https://example.com/cia5rhes301lkhkjxs9t7x1x9/cia5rhes301llhkjxo81fsok6/cia5rhes301lmhkjxgi0i3j3b/cia5rhes301lnhkjx5i1k3l6t", + "https://example.com/cia5rhes301lohkjxj1t98ds7/cia5rhes301lphkjxdee7ecco/cia5rhes301lqhkjxgfslix18/cia5rhes301lrhkjx4w0teefo", + "https://example.com/cia5rhes301lshkjxghppkl49/cia5rhes301lthkjx7b8lqwg7/cia5rhes301luhkjx9a10fkrm/cia5rhes301lvhkjxbbotm5de", + "https://example.com/cia5rhes301lwhkjxmjdqwoun/cia5rhes301lxhkjxqscqyygp/cia5rhes301lyhkjx9v1twpxt/cia5rhes301lzhkjxedovrwz9", + "https://example.com/cia5rhes301m0hkjxa79l057t/cia5rhes301m1hkjxi4lf4pam/cia5rhes301m2hkjxbc54aj2i/cia5rhes301m3hkjxh0uiocv9", + "https://example.com/cia5rhes301m4hkjxehur1yoh/cia5rhes301m5hkjxa360j1dg/cia5rhes301m6hkjxxu566hbq/cia5rhes301m7hkjxx3ynzmno", + "https://example.com/cia5rhes301m8hkjxboi8g565/cia5rhes301m9hkjxcw8d20dp/cia5rhes301mahkjxgep6vvnb/cia5rhes301mbhkjxaig0kixq", + "https://example.com/cia5rhes301mchkjxrjfsemox/cia5rhes301mdhkjxwifr2cdy/cia5rhes301mehkjx0mfczty9/cia5rhes301mfhkjxw0d7du38", + "https://example.com/cia5rhes301mghkjxma95b0fw/cia5rhes301mhhkjxe08g59uf/cia5rhes301mihkjx6uflwnsd/cia5rhes301mjhkjxrwodr62q", + "https://example.com/cia5rhes301mkhkjx6rb2vspf/cia5rhes301mlhkjx95l13vr9/cia5rhes301mmhkjx8nf0whp6/cia5rhes301mnhkjxpa5k4qfz", + "https://example.com/cia5rhes301mohkjx3lbw4jvc/cia5rhes301mphkjx80vx4999/cia5rhes301mqhkjxyyl6bvqt/cia5rhes301mrhkjxq4xrjjqk", + "https://example.com/cia5rhes301mshkjx8bf0phng/cia5rhes301mthkjxorxsclwf/cia5rhes301muhkjxi92z3o3d/cia5rhes301mvhkjxc3pvc5j6", + "https://example.com/cia5rhes301mwhkjxm1nnxnhb/cia5rhes301mxhkjxtkkdy3i1/cia5rhes301myhkjxcrbfcl0b/cia5rhes301mzhkjxq6p026u3", + "https://example.com/cia5rhes301n0hkjx1c2gv11c/cia5rhes301n1hkjxxfi36cpe/cia5rhes301n2hkjx38o7jvti/cia5rhes301n3hkjx9x9p0qh6", + "https://example.com/cia5rhes301n4hkjx04xmiymb/cia5rhes301n5hkjx1bimz4eh/cia5rhes301n6hkjxnjqswkw6/cia5rhes301n7hkjxx11qd98z", + "https://example.com/cia5rhes301n8hkjxdgkuvg3u/cia5rhes301n9hkjx9408l8sy/cia5rhes301nahkjxbdy48hsf/cia5rhes301nbhkjx9gbl5y30", + "https://example.com/cia5rhes301nchkjx4rj2l6gk/cia5rhes301ndhkjxw603iycn/cia5rhes301nehkjxq9p4xm5r/cia5rhes301nfhkjx17hqhk81", + "https://example.com/cia5rhes301nghkjxm9macc2i/cia5rhes301nhhkjxubpvluxn/cia5rhes301nihkjxtznh1gve/cia5rhes301njhkjxwft7i8zr", + "https://example.com/cia5rhes301nkhkjxf33xqqss/cia5rhes301nlhkjxt2nsxi7y/cia5rhes301nmhkjxx1k3jzgs/cia5rhes301nnhkjx377meb7x", + "https://example.com/cia5rhes301nohkjx1iur2w22/cia5rhes301nphkjxrj1q40j2/cia5rhes301nqhkjxpv78uwi7/cia5rhes301nrhkjx74y43ako", + "https://example.com/cia5rhes301nshkjxgqi4066n/cia5rhes301nthkjxzeax16t1/cia5rhes301nuhkjxkus1cy9e/cia5rhes301nvhkjxrk8s23la", + "https://example.com/cia5rhes301nwhkjxtz5i4jno/cia5rhes301nxhkjxnve6r7to/cia5rhes301nyhkjxoy3cq981/cia5rhes301nzhkjxsteraq5a", + "https://example.com/cia5rhes301o0hkjx0bjvkfri/cia5rhes301o1hkjxl3tpcl1b/cia5rhes301o2hkjxih6vk4ck/cia5rhes301o3hkjxh0vrl561", + "https://example.com/cia5rhes301o4hkjxmqogfrad/cia5rhes301o5hkjx9m8hdpcc/cia5rhes301o6hkjxaluh3wcr/cia5rhes301o7hkjx4m8wommz", + "https://example.com/cia5rhes301o8hkjxml1xa1az/cia5rhes301o9hkjxx678ystu/cia5rhes301oahkjxndq6nh65/cia5rhes301obhkjxadfjm3wa", + "https://example.com/cia5rhes301ochkjxwz6f2spm/cia5rhes301odhkjxgvmfaaq8/cia5rhes301oehkjxt17j08ud/cia5rhes301ofhkjxneg64ahh", + "https://example.com/cia5rhes301oghkjx2odplosg/cia5rhes301ohhkjx6lsxvvhc/cia5rhes301oihkjx1zjlr3lf/cia5rhes301ojhkjx4too7ovk", + "https://example.com/cia5rhes301okhkjx5ie6svqi/cia5rhes301olhkjx1dvra0d8/cia5rhes301omhkjx01eottp8/cia5rhes301onhkjx0k4cthfm", + "https://example.com/cia5rhes301oohkjx2uggrotk/cia5rhes301ophkjxo0nc672k/cia5rhes301oqhkjxyxv3yip2/cia5rhes301orhkjx1lzdi04w", + "https://example.com/cia5rhes301oshkjx239gzsvl/cia5rhes301othkjxegmfaqs4/cia5rhes301ouhkjx3k7u7klw/cia5rhes301ovhkjxx0w3i22n", + "https://example.com/cia5rhes301owhkjx43szuyvt/cia5rhes301oxhkjxwn8rt15b/cia5rhes301oyhkjxn9plrtrh/cia5rhes301ozhkjx939j8ua7", + "https://example.com/cia5rhes301p0hkjx933v5a7c/cia5rhes301p1hkjxnptb4syc/cia5rhes301p2hkjxlbdlt4c7/cia5rhes301p3hkjxdnx9ndcb", + "https://example.com/cia5rhes301p4hkjxgwkvdwyk/cia5rhes301p5hkjxu5l7j6a8/cia5rhes301p6hkjx69gflmy6/cia5rhes301p7hkjxl0ebaafj", + "https://example.com/cia5rhes301p8hkjxf8355jja/cia5rhes301p9hkjxynm9lc74/cia5rhes301pahkjx9gj8htwg/cia5rhes301pbhkjx9dnwyvr7", + "https://example.com/cia5rhes401pchkjxvc8103ko/cia5rhes401pdhkjxdvj8k8ys/cia5rhes401pehkjx1yvwz1t3/cia5rhes401pfhkjx9tdmf2pk", + "https://example.com/cia5rhes401pghkjxm2moyuwg/cia5rhes401phhkjx6sd8pxql/cia5rhes401pihkjx0f56qfr0/cia5rhes401pjhkjxe09zm4ee", + "https://example.com/cia5rhes401pkhkjxmw6xikqs/cia5rhes401plhkjxtrw32c5n/cia5rhes401pmhkjx2gs5r2uw/cia5rhes401pnhkjx3lnqjuvy", + "https://example.com/cia5rhes401pohkjx18h593mm/cia5rhes401pphkjx74f4atkm/cia5rhes401pqhkjxl804wbka/cia5rhes401prhkjxvjq32png", + "https://example.com/cia5rhes401pshkjxq7ig2fmw/cia5rhes401pthkjx94834nui/cia5rhes401puhkjxg5h7u1tk/cia5rhes401pvhkjx83fsa82j", + "https://example.com/cia5rhes401pwhkjxslfaan9d/cia5rhes401pxhkjx5qqbf367/cia5rhes401pyhkjx9uafkt0z/cia5rhes401pzhkjxyk4qxvdq", + "https://example.com/cia5rhes401q0hkjxxovjahis/cia5rhes401q1hkjx7811zjvy/cia5rhes401q2hkjx87k6qna2/cia5rhes401q3hkjxoj0w4dpu", + "https://example.com/cia5rhes401q4hkjxln1jw5x1/cia5rhes401q5hkjxrh7gm7b7/cia5rhes401q6hkjx7r2y10bk/cia5rhes401q7hkjxhqkthpq6", + "https://example.com/cia5rhes401q8hkjx6u394gyd/cia5rhes401q9hkjxrtrhrat9/cia5rhes401qahkjxdt7xqdcp/cia5rhes401qbhkjxm5ymdwfi", + "https://example.com/cia5rhes401qchkjx025wiukn/cia5rhes401qdhkjxpovs1w4l/cia5rhes401qehkjxjdc5rv3v/cia5rhes401qfhkjxe3c0v82a", + "https://example.com/cia5rhes401qghkjxzhl0kyt9/cia5rhes401qhhkjxx91x3w69/cia5rhes401qihkjxsldvc9au/cia5rhes401qjhkjxnag09g7f", + "https://example.com/cia5rhes401qkhkjxp81l6si9/cia5rhes401qlhkjxdzg4648q/cia5rhes401qmhkjx5rysqo3m/cia5rhes401qnhkjxquhuyu1t", + "https://example.com/cia5rhes401qohkjxsko3ojrg/cia5rhes401qphkjxvda749pk/cia5rhes401qqhkjxudg42xak/cia5rhes401qrhkjx7edixyt2", + "https://example.com/cia5rhes401qshkjx9jc7o9ik/cia5rhes401qthkjxvhwfd027/cia5rhes401quhkjx22ja7ygg/cia5rhes401qvhkjxx2yc25pu", + "https://example.com/cia5rhes401qwhkjxs1yqlawy/cia5rhes401qxhkjxap6eqaza/cia5rhes401qyhkjxhbq9zkww/cia5rhes401qzhkjx5j6rm35k", + "https://example.com/cia5rhes401r0hkjxk8f23od8/cia5rhes401r1hkjxf2hw9jtn/cia5rhes401r2hkjx0dcvkzlo/cia5rhes401r3hkjxqgiol3kt", + "https://example.com/cia5rhes401r4hkjxk8rzt66b/cia5rhes401r5hkjx4zc092sq/cia5rhes401r6hkjxdkgh2lu3/cia5rhes401r7hkjxrxdp47yk", + "https://example.com/cia5rhes401r8hkjxl08yep62/cia5rhes401r9hkjx1xzzdt21/cia5rhes401rahkjxl1d6b0c6/cia5rhes401rbhkjx6zyydco5", + "https://example.com/cia5rhes401rchkjx76v07kx8/cia5rhes401rdhkjxr6p5yan5/cia5rhes401rehkjxx6g8s1x3/cia5rhes401rfhkjxjwzjn0xv", + "https://example.com/cia5rhes401rghkjxhiae442a/cia5rhes401rhhkjx28xtp7j6/cia5rhes401rihkjxqsail6xh/cia5rhes401rjhkjxr7kiu6ki", + "https://example.com/cia5rhes401rkhkjxqne03tot/cia5rhes401rlhkjxjhxcypey/cia5rhes401rmhkjxsma2ekxx/cia5rhes401rnhkjx02z0sp28", + "https://example.com/cia5rhes401rohkjxnrlforbh/cia5rhes401rphkjxuuk7smlp/cia5rhes401rqhkjxp387ih60/cia5rhes401rrhkjxxn9o7q8q", + "https://example.com/cia5rhes401rshkjxh4lkmqca/cia5rhes401rthkjx0jm2hnqp/cia5rhes401ruhkjxeo73uqck/cia5rhes401rvhkjxjbn6t4yy", + "https://example.com/cia5rhes401rwhkjxzepyozzy/cia5rhes401rxhkjx1pykuc1m/cia5rhes401ryhkjxpgqmomw4/cia5rhes401rzhkjx3zbmunev", + "https://example.com/cia5rhes401s0hkjxjbbqwl70/cia5rhes401s1hkjx9xuj3zqs/cia5rhes401s2hkjxafsii503/cia5rhes401s3hkjxuu216w98", + "https://example.com/cia5rhes401s4hkjxbbt02xp1/cia5rhes401s5hkjx1wbhsus2/cia5rhes401s6hkjx1ml5tjx2/cia5rhes401s7hkjxmxzwknq3", + "https://example.com/cia5rhes401s8hkjxjna2smh1/cia5rhes401s9hkjxxqoxe1xs/cia5rhes401sahkjxta8cres1/cia5rhes401sbhkjxlobgkg5k", + "https://example.com/cia5rhes401schkjx4a93mw54/cia5rhes401sdhkjx11zbb5rf/cia5rhes401sehkjxztk9dbrf/cia5rhes401sfhkjxgh3yzmo1", + "https://example.com/cia5rhes401sghkjxc28fo8pm/cia5rhes401shhkjx94mcy08n/cia5rhes401sihkjxk9c7sc1a/cia5rhes401sjhkjx2kpauvo7", + "https://example.com/cia5rhes401skhkjxil6rkwln/cia5rhes401slhkjx7rw9fbmh/cia5rhes401smhkjxp1azo0ra/cia5rhes401snhkjxjn6ske3g", + "https://example.com/cia5rhes401sohkjxld97hjxq/cia5rhes401sphkjxw88rub86/cia5rhes401sqhkjxdepedlux/cia5rhes401srhkjxtz9wqykr", + "https://example.com/cia5rhes401sshkjx23nj0gw3/cia5rhes401sthkjxamuty3aa/cia5rhes401suhkjxkzkkksxw/cia5rhes401svhkjxfy7t55xc", + "https://example.com/cia5rhes401swhkjx2fv1t47w/cia5rhes401sxhkjx493ijzth/cia5rhes401syhkjxlt98ctc5/cia5rhes401szhkjxcgaqy6ks", + "https://example.com/cia5rhes401t0hkjxibyaz6lz/cia5rhes401t1hkjxajqz3b7v/cia5rhes401t2hkjxutdwnzqk/cia5rhes401t3hkjxqr2zpknp", + "https://example.com/cia5rhes401t4hkjx2me8lthv/cia5rhes401t5hkjxej9j1ggl/cia5rhes401t6hkjxoxxbptsl/cia5rhes401t7hkjx31lkyc9v", + "https://example.com/cia5rhes401t8hkjxljaq5d24/cia5rhes401t9hkjxcj9ozsjc/cia5rhes401tahkjx45acwqjh/cia5rhes401tbhkjxsfepsuqn", + "https://example.com/cia5rhes401tchkjx2d54v2mc/cia5rhes401tdhkjxg995kn83/cia5rhes401tehkjx3sa4rnpk/cia5rhes401tfhkjx9p5zj2fw", + "https://example.com/cia5rhes401tghkjxetiwdot9/cia5rhes401thhkjxdzft6ee5/cia5rhes401tihkjxc44k574p/cia5rhes401tjhkjxhlaamwjt", + "https://example.com/cia5rhes401tkhkjxfhcebmkr/cia5rhes401tlhkjx6d2ahwy8/cia5rhes401tmhkjxnvqrt43n/cia5rhes401tnhkjx6y3x0tl6", + "https://example.com/cia5rhes401tohkjxm76vc3bd/cia5rhes401tphkjxe4toa8ix/cia5rhes401tqhkjx44k31o69/cia5rhes401trhkjx06h29ag1", + "https://example.com/cia5rhes401tshkjx0en3ww5b/cia5rhes401tthkjxj0sbg5rs/cia5rhes401tuhkjx4mbcemrx/cia5rhes401tvhkjxzah5kckz", + "https://example.com/cia5rhes401twhkjxbc8vo2b5/cia5rhes401txhkjx1quodrlw/cia5rhes401tyhkjx5q10omzn/cia5rhes401tzhkjxhoknv1pd", + "https://example.com/cia5rhes401u0hkjxyboul8es/cia5rhes401u1hkjxb2vn5wu5/cia5rhes401u2hkjxat1dog9k/cia5rhes401u3hkjxg9cpxurx", + "https://example.com/cia5rhes401u4hkjxcy69r1cg/cia5rhes401u5hkjxakh0jykj/cia5rhes401u6hkjxpsaz87je/cia5rhes401u7hkjx3ujs32jl", + "https://example.com/cia5rhes401u8hkjxogdqi93b/cia5rhes401u9hkjxh0e8e5it/cia5rhes401uahkjxcypc72ho/cia5rhes401ubhkjx07fholph", + "https://example.com/cia5rhes401uchkjx96q3s4y9/cia5rhes401udhkjxbw6z849k/cia5rhes401uehkjxhbtqh3g4/cia5rhes401ufhkjxbp1hjydk", + "https://example.com/cia5rhes401ughkjxq6z30rsc/cia5rhes401uhhkjxgnc7011n/cia5rhes401uihkjx00l0t29g/cia5rhes401ujhkjxpdkefo86", + "https://example.com/cia5rhes401ukhkjx5w5u4uez/cia5rhes401ulhkjxkp60rcm2/cia5rhes401umhkjx2o152chr/cia5rhes401unhkjxj1c837fv", + "https://example.com/cia5rhes401uohkjxkm3hwgxw/cia5rhes401uphkjxr9fpwgxo/cia5rhes401uqhkjxbju1cc6a/cia5rhes401urhkjxnyjsugye", + "https://example.com/cia5rhes401ushkjxnl9fzmwd/cia5rhes401uthkjx829ud4hl/cia5rhes401uuhkjxgzo6bd97/cia5rhes401uvhkjxninvqfmi", + "https://example.com/cia5rhes401uwhkjx23xkeeyb/cia5rhes401uxhkjxr7f81k32/cia5rhes401uyhkjxu8gwxp2s/cia5rhes401uzhkjx0zbojk5h", + "https://example.com/cia5rhes401v0hkjxnp3m2er4/cia5rhes401v1hkjxh6zxquzd/cia5rhes401v2hkjxcp9r8512/cia5rhes401v3hkjxfj2ziffr", + "https://example.com/cia5rhes401v4hkjx450sdsy6/cia5rhes401v5hkjxid7nsxhs/cia5rhes401v6hkjx5umhcl29/cia5rhes401v7hkjx8c4ntx9f", + "https://example.com/cia5rhes401v8hkjxm7493idl/cia5rhes401v9hkjxvp3boxa7/cia5rhes401vahkjxpdhxc0bd/cia5rhes401vbhkjxjq8g7bbv", + "https://example.com/cia5rhes401vchkjxutbrig4f/cia5rhes401vdhkjxs6v3l5bs/cia5rhes401vehkjxz0s7ot2j/cia5rhes401vfhkjx6e86cpuy", + "https://example.com/cia5rhes401vghkjxn3ce11di/cia5rhes401vhhkjx0lgmp1co/cia5rhes401vihkjxgjby3l0n/cia5rhes401vjhkjxh7bj2rti", + "https://example.com/cia5rhes401vkhkjxq9xd82bm/cia5rhes401vlhkjxs2x6daye/cia5rhes401vmhkjxnv72qdm3/cia5rhes401vnhkjxjuu4sj2i", + "https://example.com/cia5rhes401vohkjxf8wvg4tv/cia5rhes401vphkjxus1ibfvl/cia5rhes401vqhkjxaapjjznh/cia5rhes401vrhkjxhpfk9ana", + "https://example.com/cia5rhes401vshkjxb314pxv2/cia5rhes401vthkjxcfenzpqi/cia5rhes401vuhkjxvbef4uzt/cia5rhes401vvhkjxcg2mtju1", + "https://example.com/cia5rhes401vwhkjxobjolxrt/cia5rhes401vxhkjx8n6q1mbj/cia5rhes401vyhkjx1ffiobsm/cia5rhes401vzhkjx845f4yrb", + "https://example.com/cia5rhes501w0hkjxnkd5nvx4/cia5rhes501w1hkjxd10zyp5f/cia5rhes501w2hkjxed1isr4c/cia5rhes501w3hkjx52w25p6h", + "https://example.com/cia5rhes501w4hkjxz590qwcl/cia5rhes501w5hkjxirypp5am/cia5rhes501w6hkjx0la9fxfb/cia5rhes501w7hkjxqn8mmj3v", + "https://example.com/cia5rhes501w8hkjxtvynj745/cia5rhes501w9hkjxicsfn3ft/cia5rhes501wahkjxawuf4y2u/cia5rhes501wbhkjxuioyhj09", + "https://example.com/cia5rhes501wchkjxpzp5z6gl/cia5rhes501wdhkjxqyu6yfrv/cia5rhes501wehkjxksohpokd/cia5rhes501wfhkjxocde2wt3", + "https://example.com/cia5rhes501wghkjxocbfchks/cia5rhes501whhkjx0tpiw1nt/cia5rhes501wihkjxslhtkvr0/cia5rhes501wjhkjx3f2wxmki", + "https://example.com/cia5rhes501wkhkjxpmltynvl/cia5rhes501wlhkjxig3aj85x/cia5rhes501wmhkjxplefjg23/cia5rhes501wnhkjxanfao1fs", + "https://example.com/cia5rhes501wohkjx9gnuza2e/cia5rhes501wphkjxi89ym1sn/cia5rhes501wqhkjxmpb91ix0/cia5rhes501wrhkjx6vdiefye", + "https://example.com/cia5rhes501wshkjxxy1dl1w5/cia5rhes501wthkjxs40731ag/cia5rhes501wuhkjx5tu8ptk3/cia5rhes501wvhkjxb83m364e", + "https://example.com/cia5rhes501wwhkjxyxi7zia4/cia5rhes501wxhkjxfttjkfl1/cia5rhes501wyhkjx73a609nu/cia5rhes501wzhkjxhrqkcsc9", + "https://example.com/cia5rhes501x0hkjxpzoc18gx/cia5rhes501x1hkjx2evbj8dh/cia5rhes501x2hkjxcmt0dte5/cia5rhes501x3hkjxs2o08cdn", + "https://example.com/cia5rhes501x4hkjxn8ppwkl6/cia5rhes501x5hkjxve994e14/cia5rhes501x6hkjxp3nroxzg/cia5rhes501x7hkjxdzh6iphg", + "https://example.com/cia5rhes501x8hkjx3dxj6rdf/cia5rhes501x9hkjx0uek477t/cia5rhes501xahkjxyomgqdjw/cia5rhes501xbhkjx0adcgz3e", + "https://example.com/cia5rhes501xchkjxjr38fuho/cia5rhes501xdhkjxi9h8gxgv/cia5rhes501xehkjx9lnq5x48/cia5rhes501xfhkjx6x7q34qn", + "https://example.com/cia5rhes501xghkjx7kdv4j16/cia5rhes501xhhkjxzh3h1621/cia5rhes501xihkjx2tll48zr/cia5rhes501xjhkjx2mgqrjx7", + "https://example.com/cia5rhes501xkhkjxb38ktam2/cia5rhes501xlhkjxqe4kly98/cia5rhes501xmhkjxs8kc7y4g/cia5rhes501xnhkjxon8hd6t9", + "https://example.com/cia5rhes501xohkjxvh07sqak/cia5rhes501xphkjxa8cjku7k/cia5rhes501xqhkjx7czbmtzz/cia5rhes501xrhkjx2v5gm68q", + "https://example.com/cia5rhes501xshkjxhafeuujz/cia5rhes501xthkjx83z1ik2e/cia5rhes501xuhkjxfwfbdp20/cia5rhes501xvhkjxat92izys", + "https://example.com/cia5rhes501xwhkjxmxkavbl1/cia5rhes501xxhkjxjmwfaudp/cia5rhes501xyhkjxjb6y5ckv/cia5rhes501xzhkjxx1qzw43n", + "https://example.com/cia5rhes501y0hkjxjqq91tnx/cia5rhes501y1hkjx5fqvt95y/cia5rhes501y2hkjx213i79od/cia5rhes501y3hkjx2bhrwh3c", + "https://example.com/cia5rhes501y4hkjxpg0w8tm1/cia5rhes501y5hkjx4rfqmukn/cia5rhes501y6hkjxdmm6zlwo/cia5rhes501y7hkjxuuszpo6e", + "https://example.com/cia5rhes501y8hkjx1dijvw0o/cia5rhes501y9hkjxh1co3ai2/cia5rhes501yahkjxfcerdd6h/cia5rhes501ybhkjx52yrnztr", + "https://example.com/cia5rhes501ychkjxmuxdm6gn/cia5rhes501ydhkjxk9cu7gzp/cia5rhes501yehkjxt9czxhe8/cia5rhes501yfhkjxf1hpxe7k", + "https://example.com/cia5rhes501yghkjxzyfsx9ee/cia5rhes501yhhkjxoobntt4j/cia5rhes501yihkjxbv4l41i4/cia5rhes501yjhkjx9cg8i6yq", + "https://example.com/cia5rhes501ykhkjxb8micj52/cia5rhes501ylhkjxi810y8kg/cia5rhes501ymhkjx35pqd2dp/cia5rhes501ynhkjx411ay6w2", + "https://example.com/cia5rhes501yohkjxp230m8o4/cia5rhes501yphkjx85aei3f0/cia5rhes501yqhkjx39awmvdg/cia5rhes501yrhkjxabhea8z7", + "https://example.com/cia5rhes501yshkjxmy4w0zr0/cia5rhes501ythkjxxxtlmezs/cia5rhes501yuhkjx8mwm07hi/cia5rhes501yvhkjx1l5p3sr0", + "https://example.com/cia5rhes501ywhkjxdrcc28nn/cia5rhes501yxhkjxqcqd6ogs/cia5rhes501yyhkjxim858nwj/cia5rhes501yzhkjxpi5u68xr", + "https://example.com/cia5rhes501z0hkjxgxk8ryu8/cia5rhes501z1hkjx7jqdu67h/cia5rhes501z2hkjx21zj3rmt/cia5rhes501z3hkjxcq1lwavz", + "https://example.com/cia5rhes501z4hkjxgx1266ef/cia5rhes501z5hkjxi6uyr5et/cia5rhes501z6hkjxhr0eot9n/cia5rhes501z7hkjxyz2oyzjs", + "https://example.com/cia5rhes501z8hkjx5s5s200w/cia5rhes501z9hkjx67v1yb2z/cia5rhes501zahkjxw5u36sb5/cia5rhes501zbhkjxl17xibdr", + "https://example.com/cia5rhes501zchkjxpx05d6o1/cia5rhes501zdhkjxiiadtum2/cia5rhes501zehkjxoj9i56gl/cia5rhes501zfhkjxqcxmjy73", + "https://example.com/cia5rhes501zghkjxegc7tvdy/cia5rhes501zhhkjxqeeoq63e/cia5rhes501zihkjxysrggeqs/cia5rhes501zjhkjxf24x4w8j", + "https://example.com/cia5rhes501zkhkjx36w5g359/cia5rhes501zlhkjxuornb7pf/cia5rhes501zmhkjx4pvpci2q/cia5rhes501znhkjxbv1oa4fp", + "https://example.com/cia5rhes501zohkjxb6t1a9pz/cia5rhes501zphkjxg5ezhfdv/cia5rhes501zqhkjxl3efud9l/cia5rhes501zrhkjxcqb7r2sc", + "https://example.com/cia5rhes501zshkjxd7wcvoav/cia5rhes501zthkjxelhdxd7w/cia5rhes501zuhkjxh07pf32p/cia5rhes501zvhkjxgcxn3nvl", + "https://example.com/cia5rhes501zwhkjx95ri5zb5/cia5rhes501zxhkjxci9sujxb/cia5rhes501zyhkjx1hzc65ou/cia5rhes501zzhkjxf1kbgic9", + "https://example.com/cia5rhes50200hkjxphxlxmld/cia5rhes50201hkjx0sveusk8/cia5rhes50202hkjxg5822asq/cia5rhes50203hkjxxle2qnr4", + "https://example.com/cia5rhes50204hkjxswna3iww/cia5rhes50205hkjxo41y7z2t/cia5rhes50206hkjx1auwgf30/cia5rhes50207hkjx3vyiy15y", + "https://example.com/cia5rhes50208hkjx6n640dxz/cia5rhes50209hkjxxb3tliuh/cia5rhes5020ahkjxht8vaioj/cia5rhes5020bhkjxqjo5gr27", + "https://example.com/cia5rhes5020chkjxh9wu9gbv/cia5rhes5020dhkjxbrv63660/cia5rhes5020ehkjxbmozonad/cia5rhes5020fhkjxsek9b1wa", + "https://example.com/cia5rhes5020ghkjxrlfea9iv/cia5rhes5020hhkjxt7qh369y/cia5rhes5020ihkjxkn7yslxt/cia5rhes5020jhkjx2ge4xq51", + "https://example.com/cia5rhes5020khkjx2sp9c2gt/cia5rhes5020lhkjx1ks9juca/cia5rhes5020mhkjxrova7tax/cia5rhes5020nhkjxnaxah6tg", + "https://example.com/cia5rhes5020ohkjx9btins8g/cia5rhes5020phkjxy4or4s6u/cia5rhes5020qhkjxxrqcpd3n/cia5rhes5020rhkjxm6xw3z2x", + "https://example.com/cia5rhes5020shkjxz31fkpjb/cia5rhes5020thkjxsxivj1tx/cia5rhes5020uhkjx218dg3oe/cia5rhes5020vhkjxpxflwg9k", + "https://example.com/cia5rhes5020whkjx3xpogsrh/cia5rhes5020xhkjxv5k6yvhb/cia5rhes5020yhkjxmg5wu4xg/cia5rhes5020zhkjx49u1376r", + "https://example.com/cia5rhes50210hkjxu07iog9j/cia5rhes50211hkjxe2zq097b/cia5rhes50212hkjx7d2n5bis/cia5rhes50213hkjx98z0f1wd", + "https://example.com/cia5rhes50214hkjxxz2fxal3/cia5rhes50215hkjx4cdss157/cia5rhes50216hkjxgemb403b/cia5rhes50217hkjxcx1to7hv", + "https://example.com/cia5rhes50218hkjxlm8ctocp/cia5rhes50219hkjx1fcacxy3/cia5rhes5021ahkjxx59gdemf/cia5rhes5021bhkjxa8w89mbs", + "https://example.com/cia5rhes5021chkjxgbgtxsby/cia5rhes5021dhkjxpsb7jlci/cia5rhes5021ehkjxo8ytwukr/cia5rhes5021fhkjxtpoy84xh", + "https://example.com/cia5rhes5021ghkjxyk2hucae/cia5rhes5021hhkjxyiywhstb/cia5rhes5021ihkjx1sdmxxsc/cia5rhes5021jhkjxp5btccgt", + "https://example.com/cia5rhes5021khkjxav298li6/cia5rhes5021lhkjx4ba0mhnf/cia5rhes5021mhkjxngkomyhl/cia5rhes5021nhkjxxtqqmtir", + "https://example.com/cia5rhes5021ohkjxbavqb4tz/cia5rhes5021phkjx1f18irux/cia5rhes5021qhkjxgef61ilr/cia5rhes5021rhkjxeh1y04kj", + "https://example.com/cia5rhes5021shkjxr4s9i0ob/cia5rhes5021thkjxfocdh5vi/cia5rhes5021uhkjxjcwajris/cia5rhes5021vhkjxitwdjshb", + "https://example.com/cia5rhes5021whkjxwhlm3an5/cia5rhes5021xhkjx5dcoj15s/cia5rhes5021yhkjxy9biyupr/cia5rhes5021zhkjx6wit7c1p", + "https://example.com/cia5rhes50220hkjxco3srhrz/cia5rhes50221hkjxn8kb150i/cia5rhes50222hkjxcfl48mla/cia5rhes50223hkjx5wzddel7", + "https://example.com/cia5rhes50224hkjxv4kbq0bu/cia5rhes50225hkjxdlcujhtv/cia5rhes50226hkjx0nm0ncdj/cia5rhes50227hkjx4hnvg7w9", + "https://example.com/cia5rhes50228hkjxn2hoexz0/cia5rhes50229hkjx5a0zae0n/cia5rhes5022ahkjx7kw3lf0v/cia5rhes5022bhkjx9uaqp2w5", + "https://example.com/cia5rhes5022chkjxmllq37r4/cia5rhes5022dhkjxuogvq5kp/cia5rhes5022ehkjxegsxagw5/cia5rhes5022fhkjx25d5a5z8", + "https://example.com/cia5rhes5022ghkjxwwoecae0/cia5rhes5022hhkjxli8zm9vs/cia5rhes5022ihkjxzxcky0jv/cia5rhes5022jhkjxvsb9g2qa", + "https://example.com/cia5rhes5022khkjxhwpswkll/cia5rhes5022lhkjxow1y1vc4/cia5rhes5022mhkjxh0o8b4r5/cia5rhes5022nhkjxjsyoo9le", + "https://example.com/cia5rhes5022ohkjx50pmnu22/cia5rhes5022phkjxfdh1jhl2/cia5rhes5022qhkjxh67gv4up/cia5rhes5022rhkjxmpux301t", + "https://example.com/cia5rhes5022shkjxmgm2q2tv/cia5rhes5022thkjx7ivn1k01/cia5rhes5022uhkjxs4j1z1st/cia5rhes5022vhkjxh3y1ak61", + "https://example.com/cia5rhes5022whkjxy2vkf9qu/cia5rhes5022xhkjxotujbeup/cia5rhes5022yhkjx5qiu2ujp/cia5rhes5022zhkjxluajf32y", + "https://example.com/cia5rhes50230hkjxk7stw4db/cia5rhes60231hkjxf7aj9i0m/cia5rhes60232hkjxziydwog0/cia5rhes60233hkjxx3x1fbuc", + "https://example.com/cia5rhes60234hkjxg2uqu0ml/cia5rhes60235hkjxq7n4gpgv/cia5rhes60236hkjxolpslbdw/cia5rhes60237hkjxyn1lp5ir", + "https://example.com/cia5rhes60238hkjxwis4nirx/cia5rhes60239hkjxaiqtx5n6/cia5rhes6023ahkjxsgrablt0/cia5rhes6023bhkjxc06147lu", + "https://example.com/cia5rhes6023chkjxxge8xmjn/cia5rhes6023dhkjx5j31jwgd/cia5rhes6023ehkjxwuz388j6/cia5rhes6023fhkjx3pdltokg", + "https://example.com/cia5rhes6023ghkjx6dffsn9x/cia5rhes6023hhkjxzjoqqtor/cia5rhes6023ihkjx3bz79voa/cia5rhes6023jhkjxa7bb04th", + "https://example.com/cia5rhes6023khkjxhg5ub876/cia5rhes6023lhkjxrklzuro9/cia5rhes6023mhkjx8xmhpdqm/cia5rhes6023nhkjxch1jn490", + "https://example.com/cia5rhes6023ohkjxhad7g229/cia5rhes6023phkjx4zaksvdn/cia5rhes6023qhkjxx6ko1cpf/cia5rhes6023rhkjx0vireriy", + "https://example.com/cia5rhes6023shkjxhvae8jtn/cia5rhes6023thkjxw4de6xi4/cia5rhes6023uhkjxzfqht8ml/cia5rhes6023vhkjxs8ul3zvc", + "https://example.com/cia5rhes6023whkjxdsyyu08r/cia5rhes6023xhkjxhddko66j/cia5rhes6023yhkjxnfhgsx6b/cia5rhes6023zhkjxt63bqpbs", + "https://example.com/cia5rhes60240hkjxa7oafjex/cia5rhes60241hkjx74x1e2f3/cia5rhes60242hkjxiaptta0r/cia5rhes60243hkjxingpv6qf", + "https://example.com/cia5rhes60244hkjx832w9v0m/cia5rhes60245hkjxbtb4g19e/cia5rhes60246hkjxahthge6j/cia5rhes60247hkjxhqj3m07o", + "https://example.com/cia5rhes60248hkjxcf7nc4li/cia5rhes60249hkjxyaeee0po/cia5rhes6024ahkjxz0zbl31v/cia5rhes6024bhkjxyli25oi7", + "https://example.com/cia5rhes6024chkjxqymyzh67/cia5rhes6024dhkjx41mtrlwg/cia5rhes6024ehkjxupbohin3/cia5rhes6024fhkjx1wtwax3q", + "https://example.com/cia5rhes6024ghkjxbhnnx8qm/cia5rhes6024hhkjx330f907k/cia5rhes6024ihkjxt8kevs6h/cia5rhes6024jhkjx6fz60hhj", + "https://example.com/cia5rhes6024khkjx6jh6byd0/cia5rhes6024lhkjxnqak5lqd/cia5rhes6024mhkjx6qi3ka0d/cia5rhes6024nhkjxmydiqa1w", + "https://example.com/cia5rhes6024ohkjx1wzyvp8g/cia5rhes6024phkjxcpe4crtr/cia5rhes6024qhkjx5k672peu/cia5rhes6024rhkjxrgc14c0o", + "https://example.com/cia5rhes6024shkjxt3phdd6y/cia5rhes6024thkjxrcolx8rw/cia5rhes6024uhkjx1m8lrl96/cia5rhes6024vhkjx1ub0usjq", + "https://example.com/cia5rhes6024whkjx30q3vye6/cia5rhes6024xhkjxqhicyl5l/cia5rhes6024yhkjxewkiuvcd/cia5rhes6024zhkjxpi0s95q6", + "https://example.com/cia5rhes60250hkjx7x45wchz/cia5rhes60251hkjx29nj5yrn/cia5rhes60252hkjxmjtv4j8t/cia5rhes60253hkjx62flt3ct", + "https://example.com/cia5rhes60254hkjxj24tyltz/cia5rhes60255hkjxu43vfkjt/cia5rhes60256hkjxorb3l17v/cia5rhes60257hkjxuusa9260", + "https://example.com/cia5rhes60258hkjx2mtr4h7o/cia5rhes60259hkjxfni1laoe/cia5rhes6025ahkjxi8p6cxws/cia5rhes6025bhkjxms0v3mvk", + "https://example.com/cia5rhes6025chkjxak2ehrye/cia5rhes6025dhkjxkkwv08j7/cia5rhes6025ehkjxmviua90r/cia5rhes6025fhkjxxz5403tq", + "https://example.com/cia5rhes6025ghkjxw2zi9e42/cia5rhes6025hhkjxcpaquver/cia5rhes6025ihkjxdza15efa/cia5rhes6025jhkjxj10ftcde", + "https://example.com/cia5rhes6025khkjxzdgyklzu/cia5rhes6025lhkjxepec48wo/cia5rhes6025mhkjxrr0rxhsw/cia5rhes6025nhkjxbx5apxib", + "https://example.com/cia5rhes6025ohkjxmw1aiv3f/cia5rhes6025phkjxf2m420e9/cia5rhes6025qhkjxjiwth0yz/cia5rhes6025rhkjxrmxufevy", + "https://example.com/cia5rhes6025shkjxusdiwv01/cia5rhes6025thkjxds425t8m/cia5rhes6025uhkjxuqrtt7if/cia5rhes6025vhkjxowk5zvf3", + "https://example.com/cia5rhes6025whkjxh652j091/cia5rhes6025xhkjxg7n9opan/cia5rhes6025yhkjxhx4aysaj/cia5rhes6025zhkjxu82h4n54", + "https://example.com/cia5rhes60260hkjxi674w0z0/cia5rhes60261hkjxojs9dwc5/cia5rhes60262hkjx9zme8232/cia5rhes60263hkjxg3tduw2q", + "https://example.com/cia5rhes60264hkjxen5f1emm/cia5rhes60265hkjx9wlrydmg/cia5rhes60266hkjxyk0z00l1/cia5rhes60267hkjxim57nlkk", + "https://example.com/cia5rhes60268hkjx0dxjfg9r/cia5rhes60269hkjxvsd7fx55/cia5rhes6026ahkjxr4wv79py/cia5rhes6026bhkjxbtuynf74", + "https://example.com/cia5rhes6026chkjx0hbrlens/cia5rhes6026dhkjx4oarjdzi/cia5rhes6026ehkjxcfh9kh1i/cia5rhes6026fhkjxdvhhj9ps", + "https://example.com/cia5rhes6026ghkjxzbxwxiwi/cia5rhes6026hhkjx10dmy3ck/cia5rhes6026ihkjxrh57qzib/cia5rhes6026jhkjxa6wqf4ro", + "https://example.com/cia5rhes6026khkjxw4rqjhaq/cia5rhes6026lhkjxuc55dmgp/cia5rhes6026mhkjxlv6a6sz0/cia5rhes6026nhkjxwxm1u6cu", + "https://example.com/cia5rhes6026ohkjxcezmtk1t/cia5rhes6026phkjxt8hncf2i/cia5rhes6026qhkjxuxprl91o/cia5rhes6026rhkjx9ujzo2je", + "https://example.com/cia5rhes6026shkjxxutau6ka/cia5rhes6026thkjxa2hy9mje/cia5rhes6026uhkjxr2vho147/cia5rhes6026vhkjx7h70z8i9", + "https://example.com/cia5rhes6026whkjx1nagxk22/cia5rhes6026xhkjxke02jgeq/cia5rhes6026yhkjxhemx0l0x/cia5rhes6026zhkjx8uhw94o4", + "https://example.com/cia5rhes60270hkjxtpo8z0gx/cia5rhes60271hkjxaldlng02/cia5rhes60272hkjxi6u6vyos/cia5rhes60273hkjx8t4gz8q3", + "https://example.com/cia5rhes60274hkjxzetzmgfp/cia5rhes60275hkjxqtd9rh66/cia5rhes60276hkjxo38ak1v6/cia5rhes60277hkjx3t2grzdi", + "https://example.com/cia5rhes60278hkjxssjf92tp/cia5rhes60279hkjxtdiimuwo/cia5rhes6027ahkjxv7i327um/cia5rhes6027bhkjx34iyiwau", + "https://example.com/cia5rhes6027chkjxsalv7vq1/cia5rhes6027dhkjxj1qa0eqe/cia5rhes6027ehkjxdstykpct/cia5rhes6027fhkjxep1lg57f", + "https://example.com/cia5rhes6027ghkjxir6tvp5r/cia5rhes6027hhkjx37mwtxmp/cia5rhes6027ihkjxajh8kdk0/cia5rhes6027jhkjxprxxf6bf", + "https://example.com/cia5rhes6027khkjxtx8rt4eg/cia5rhes6027lhkjx6stckrq2/cia5rhes6027mhkjxbp2scl06/cia5rhes6027nhkjx5tcodm70", + "https://example.com/cia5rhes6027ohkjx02hq4e4i/cia5rhes6027phkjxpj98682x/cia5rhes6027qhkjxi6t9w6j8/cia5rhes6027rhkjxdoo5aitq", + "https://example.com/cia5rhes6027shkjxq61ipcpf/cia5rhes6027thkjx4c95chxk/cia5rhes6027uhkjx5yp65br8/cia5rhes6027vhkjxgaj3cw9t", + "https://example.com/cia5rhes6027whkjxx18if78t/cia5rhes6027xhkjxeruuk14w/cia5rhes6027yhkjxzur0jh40/cia5rhes6027zhkjx2zxmcdyy", + "https://example.com/cia5rhes60280hkjxrh298dzu/cia5rhes60281hkjx5m40ppz3/cia5rhes60282hkjxfak6x0vp/cia5rhes60283hkjxcokmxlit", + "https://example.com/cia5rhes60284hkjx58dts12q/cia5rhes60285hkjx7hgaud95/cia5rhes60286hkjxdycu90lv/cia5rhes60287hkjxjj4cgdk8", + "https://example.com/cia5rhes60288hkjxai7gc5c8/cia5rhes60289hkjxbnomezv6/cia5rhes6028ahkjxw7wxahj2/cia5rhes6028bhkjx1smzie0j", + "https://example.com/cia5rhes6028chkjxa57aiiju/cia5rhes6028dhkjxs1etgvw7/cia5rhes6028ehkjxtsbz6p0z/cia5rhes6028fhkjxmo1vsspv", + "https://example.com/cia5rhes6028ghkjxieobtxp5/cia5rhes6028hhkjx9ragsscj/cia5rhes6028ihkjx385kpk1h/cia5rhes6028jhkjxotj68l1k", + "https://example.com/cia5rhes6028khkjxea5reemm/cia5rhes6028lhkjx0kwzwbyo/cia5rhes6028mhkjx4nqjjcde/cia5rhes6028nhkjxzrrex5ue", + "https://example.com/cia5rhes6028ohkjx7t2lhe7z/cia5rhes6028phkjx46qyubif/cia5rhes6028qhkjxjolbuqus/cia5rhes6028rhkjx8r7ii6z7", + "https://example.com/cia5rhes6028shkjxilpnvd7j/cia5rhes6028thkjxof8m415p/cia5rhes6028uhkjxjp4mywli/cia5rhes6028vhkjxcw58yxw0", + "https://example.com/cia5rhes6028whkjxhya97tqs/cia5rhes6028xhkjxpezwz1pe/cia5rhes6028yhkjxx59c4igt/cia5rhes6028zhkjxdjv35rpr", + "https://example.com/cia5rhes60290hkjxnthanean/cia5rhes60291hkjxni7pjxv4/cia5rhes60292hkjx0flrl74n/cia5rhes60293hkjxm9x63zo7", + "https://example.com/cia5rhes60294hkjxpnfmclsw/cia5rhes60295hkjx56ccc80r/cia5rhes60296hkjx4s91lrwv/cia5rhes60297hkjxf132ofl7", + "https://example.com/cia5rhes60298hkjxl3mctpt0/cia5rhes60299hkjxvlg5nt62/cia5rhes6029ahkjx336mdt5q/cia5rhes6029bhkjxx1be21if", + "https://example.com/cia5rhes6029chkjxo22y49m7/cia5rhes6029dhkjx1llimb0p/cia5rhes6029ehkjxt13ucuxv/cia5rhes6029fhkjxh2xoljln", + "https://example.com/cia5rhes6029ghkjx68wd962d/cia5rhes6029hhkjx387d5swn/cia5rhes6029ihkjxh34aue0p/cia5rhes6029jhkjxfh61fg9l", + "https://example.com/cia5rhes6029khkjxuz53ttqc/cia5rhes6029lhkjxvrp7a6bu/cia5rhes6029mhkjx5ug57g8j/cia5rhes6029nhkjxiv7fjxr3", + "https://example.com/cia5rhes6029ohkjx2im4dkbc/cia5rhes6029phkjxk2vkitw7/cia5rhes6029qhkjx1g18697q/cia5rhes6029rhkjxu7cv0cp5", + "https://example.com/cia5rhes6029shkjxzfgxcfx5/cia5rhes6029thkjx6bi4op1u/cia5rhes6029uhkjx57v7j2tp/cia5rhes6029vhkjxqsn3ros1", + "https://example.com/cia5rhes7029whkjx33b3346i/cia5rhes7029xhkjxnhbvzlyl/cia5rhes7029yhkjxhofpksax/cia5rhes7029zhkjxpckp9le4", + "https://example.com/cia5rhes702a0hkjx6pzs7e5d/cia5rhes702a1hkjxp2x65zqo/cia5rhes702a2hkjxu66pcizj/cia5rhes702a3hkjx7o8r0f06", + "https://example.com/cia5rhes702a4hkjxs3nk500n/cia5rhes702a5hkjxg0rbzm6k/cia5rhes702a6hkjx234c6g7e/cia5rhes702a7hkjx9ocd54xq", + "https://example.com/cia5rhes702a8hkjxhv3xsjpp/cia5rhes702a9hkjxofxw9mdy/cia5rhes702aahkjxwtmyec4h/cia5rhes702abhkjxly8sn8hi", + "https://example.com/cia5rhes702achkjx7zlau40c/cia5rhes702adhkjx6i9t1hdm/cia5rhes702aehkjx3w115jp6/cia5rhes702afhkjx3spdsa1v", + "https://example.com/cia5rhes702aghkjxd4i1f3k7/cia5rhes702ahhkjx1o7338m9/cia5rhes702aihkjx3issv8lp/cia5rhes702ajhkjxkkpxy74s", + "https://example.com/cia5rhes702akhkjxdng2ft24/cia5rhes702alhkjxvf0nimyo/cia5rhes702amhkjxubx3l0hc/cia5rhes702anhkjxjdg78083", + "https://example.com/cia5rhes702aohkjxb6np3w0m/cia5rhes702aphkjxbmp49sgd/cia5rhes702aqhkjx3wm23ff0/cia5rhes702arhkjx9ht9wc86", + "https://example.com/cia5rhes702ashkjxw56jbjfz/cia5rhes702athkjx6js735z5/cia5rhes702auhkjxucfu5lpt/cia5rhes702avhkjxbyglt9ex", + "https://example.com/cia5rhes702awhkjx18s0uu13/cia5rhes702axhkjxi3zrv40h/cia5rhes702ayhkjx3a8cp916/cia5rhes702azhkjxczqrzngo", + "https://example.com/cia5rhes702b0hkjxglj4n5o7/cia5rhes702b1hkjx63bg4kb1/cia5rhes702b2hkjx60relgsi/cia5rhes702b3hkjxiol0e8ym", + "https://example.com/cia5rhes702b4hkjxjfpk1sg5/cia5rhes702b5hkjxk428e7bk/cia5rhes702b6hkjxr97qxcy0/cia5rhes702b7hkjxdyz4rzzn", + "https://example.com/cia5rhes702b8hkjx7vylah33/cia5rhes702b9hkjxinhs95fl/cia5rhes702bahkjxpengba9m/cia5rhes702bbhkjxh5smj013", + "https://example.com/cia5rhes702bchkjxqce1aoab/cia5rhes702bdhkjxaiyf10a3/cia5rhes702behkjx5yqopkqf/cia5rhes702bfhkjx3hiu4jp5", + "https://example.com/cia5rhes702bghkjx27997nof/cia5rhes702bhhkjxh131a1mu/cia5rhes702bihkjxdv7jmcf7/cia5rhes702bjhkjxu56c6np2", + "https://example.com/cia5rhes702bkhkjxqpt1iswl/cia5rhes702blhkjxxvuevm79/cia5rhes702bmhkjxlb6egm5v/cia5rhes702bnhkjx0frya4zv", + "https://example.com/cia5rhes702bohkjx62rqvbxx/cia5rhes702bphkjxn5543qcw/cia5rhes702bqhkjxo6xrcl3m/cia5rhes702brhkjxxiyxytk6", + "https://example.com/cia5rhes702bshkjxtupz79qv/cia5rhes702bthkjx46tmi8da/cia5rhes702buhkjxa076ev9b/cia5rhes702bvhkjxwzgfevcu", + "https://example.com/cia5rhes702bwhkjxwmx0x18a/cia5rhes702bxhkjxpq4el7be/cia5rhes702byhkjxwlypdgqk/cia5rhes702bzhkjxf16uiqj9", + "https://example.com/cia5rhes702c0hkjx0oylz3z7/cia5rhes702c1hkjxnka3undy/cia5rhes702c2hkjx9pvadq7q/cia5rhes702c3hkjxubumi03d", + "https://example.com/cia5rhes702c4hkjxv1je61d0/cia5rhes702c5hkjx3gud1w7h/cia5rhes702c6hkjxhbputn4m/cia5rhes702c7hkjx2fwamiyv", + "https://example.com/cia5rhes702c8hkjxbgkmje13/cia5rhes702c9hkjxlumxva5q/cia5rhes702cahkjxmiet3v1x/cia5rhes702cbhkjx8ibo8t0v", + "https://example.com/cia5rhes702cchkjxyl6aj596/cia5rhes702cdhkjxuk4jdais/cia5rhes702cehkjxznkrhgcf/cia5rhes702cfhkjxedld1xxc", + "https://example.com/cia5rhes702cghkjxc2ry2vt4/cia5rhes702chhkjxahplgyzs/cia5rhes702cihkjxdfgeirre/cia5rhes702cjhkjx5k6zbwnv", + "https://example.com/cia5rhes702ckhkjxt8jo94yh/cia5rhes702clhkjxsjs9l544/cia5rhes702cmhkjxob8bd0zc/cia5rhes702cnhkjx6cfcl3n9", + "https://example.com/cia5rhes702cohkjxb9cd9ogj/cia5rhes702cphkjxpoorw1yg/cia5rhes702cqhkjxykcpxjap/cia5rhes702crhkjx3469lxlp", + "https://example.com/cia5rhes702cshkjxmwi9wm5t/cia5rhes702cthkjx8tmzifvh/cia5rhes702cuhkjx4l68blak/cia5rhes702cvhkjxdxodcgpw", + "https://example.com/cia5rhes702cwhkjx0tbp18xa/cia5rhes702cxhkjxa9e95679/cia5rhes702cyhkjxpunm4oge/cia5rhes702czhkjxsxewphj9", + "https://example.com/cia5rhes702d0hkjx1a2yy8af/cia5rhes702d1hkjx4f2cssht/cia5rhes702d2hkjxa1d631y5/cia5rhes702d3hkjx5isc7bl5", + "https://example.com/cia5rhes702d4hkjxxf0dzxl4/cia5rhes702d5hkjxxnd097v7/cia5rhes702d6hkjx98mpvdya/cia5rhes702d7hkjx284luop7", + "https://example.com/cia5rhes702d8hkjxy6hghmfk/cia5rhes702d9hkjxr4ozxswm/cia5rhes702dahkjx4aemrdzl/cia5rhes702dbhkjx3b9om3gn", + "https://example.com/cia5rhes702dchkjx2q559yuu/cia5rhes702ddhkjxr1frvgb5/cia5rhes702dehkjx59to46ip/cia5rhes702dfhkjxtjmix0kn", + "https://example.com/cia5rhes702dghkjxk4m6a2s0/cia5rhes702dhhkjxfwaeszqy/cia5rhes702dihkjx4zf8y4ca/cia5rhes702djhkjxvhfrquil", + "https://example.com/cia5rhes702dkhkjx2orxsnm3/cia5rhes702dlhkjx47rdcwpv/cia5rhes702dmhkjx8j62q07m/cia5rhes702dnhkjxt3qftg4a", + "https://example.com/cia5rhes702dohkjxer57v1ky/cia5rhes702dphkjxjbishjq1/cia5rhes702dqhkjxt8r2fmuw/cia5rhes702drhkjx8etd1xkq", + "https://example.com/cia5rhes702dshkjxwbjmsogs/cia5rhes702dthkjxzjt0f26i/cia5rhes702duhkjxrspfet0e/cia5rhes702dvhkjx24ih1puf", + "https://example.com/cia5rhes702dwhkjx4qx5ofni/cia5rhes702dxhkjxyxhxsw0c/cia5rhes702dyhkjx8mi9wbce/cia5rhes702dzhkjxr9gk1g19", + "https://example.com/cia5rhes702e0hkjxin8zq13k/cia5rhes702e1hkjxn5bq0ikw/cia5rhes702e2hkjxxb2qoxsk/cia5rhes702e3hkjxbco0q0qj", + "https://example.com/cia5rhes702e4hkjxhxbl6l43/cia5rhes702e5hkjx0zz697fh/cia5rhes702e6hkjxfdsk112c/cia5rhes702e7hkjxabbxyd7j", + "https://example.com/cia5rhes702e8hkjx3vnctynz/cia5rhes702e9hkjxg4zopm86/cia5rhes702eahkjxo3bg8ml3/cia5rhes702ebhkjxp3aeugu4", + "https://example.com/cia5rhes702echkjxal3j832h/cia5rhes702edhkjx1lyibi15/cia5rhes702eehkjxstdtwkp6/cia5rhes702efhkjxdnbnyno0", + "https://example.com/cia5rhes702eghkjx55wp2mw0/cia5rhes702ehhkjxwmxwjl29/cia5rhes702eihkjxg7t126ld/cia5rhes702ejhkjx15qdziu1", + "https://example.com/cia5rhes702ekhkjxc0im9wy4/cia5rhes702elhkjxh2jd7hzr/cia5rhes702emhkjxcu8r9pzm/cia5rhes702enhkjx9jbgidf1", + "https://example.com/cia5rhes702eohkjxhlu6h4ep/cia5rhes702ephkjx4mwoc3ql/cia5rhes702eqhkjxe2bwkjv6/cia5rhes702erhkjxh8shrs32", + "https://example.com/cia5rhes702eshkjxs8w53l9b/cia5rhes702ethkjx1xsjdbbm/cia5rhes702euhkjxjrkym5vf/cia5rhes702evhkjxsuode17c", + "https://example.com/cia5rhes702ewhkjxj1bzme2d/cia5rhes702exhkjx88mzjzre/cia5rhes702eyhkjxst5flmg9/cia5rhes702ezhkjxdar3h55h", + "https://example.com/cia5rhes702f0hkjxrdjoki1j/cia5rhes702f1hkjx7iz1lpso/cia5rhes702f2hkjxvsyy2boh/cia5rhes702f3hkjxe4lwxkjq", + "https://example.com/cia5rhes702f4hkjxhsgvfwf9/cia5rhes702f5hkjxtemdddm6/cia5rhes702f6hkjx8t7z5qmo/cia5rhes702f7hkjxgb9mzb5t", + "https://example.com/cia5rhes702f8hkjxen7vbt3a/cia5rhes702f9hkjxozpijk1f/cia5rhes702fahkjxh2l1f7h6/cia5rhes702fbhkjxxojzw7gn", + "https://example.com/cia5rhes702fchkjx0tvnzt2w/cia5rhes702fdhkjxbi6zt33e/cia5rhes702fehkjxd54fxgzx/cia5rhes702ffhkjxsayc02os", + "https://example.com/cia5rhes702fghkjxpygjjz89/cia5rhes702fhhkjxbct2ojjb/cia5rhes702fihkjxe46ngi4m/cia5rhes702fjhkjxq7azlfig", + "https://example.com/cia5rhes702fkhkjx1ff4tumn/cia5rhes702flhkjxosiemsy8/cia5rhes702fmhkjx0o6ktv9m/cia5rhes702fnhkjxj9yp67gs", + "https://example.com/cia5rhes702fohkjxqro5xqt8/cia5rhes702fphkjx6s3gi6y0/cia5rhes702fqhkjxkkab85zz/cia5rhes702frhkjxo03b56tw", + "https://example.com/cia5rhes702fshkjxlvaiv6rz/cia5rhes702fthkjxvkg1r7dy/cia5rhes702fuhkjx3txhokr4/cia5rhes702fvhkjxtvqvs9ei", + "https://example.com/cia5rhes702fwhkjx5mknq1w5/cia5rhes702fxhkjxrj6a3pub/cia5rhes702fyhkjxvhu05ms3/cia5rhes702fzhkjxjby42qra", + "https://example.com/cia5rhes702g0hkjxrcf4pcw1/cia5rhes702g1hkjxn081wq4r/cia5rhes702g2hkjxaf91n239/cia5rhes702g3hkjxxlcnut0h", + "https://example.com/cia5rhes702g4hkjxboifrcf9/cia5rhes702g5hkjxzdowoz5o/cia5rhes702g6hkjxukarx97t/cia5rhes702g7hkjxccz4m3ra", + "https://example.com/cia5rhes702g8hkjxcojon0ux/cia5rhes702g9hkjxldlady20/cia5rhes702gahkjxzy3fh4eg/cia5rhes702gbhkjxrbfe6e4i", + "https://example.com/cia5rhes702gchkjxk66e8nbf/cia5rhes702gdhkjxudeemkvv/cia5rhes702gehkjx3c3hpe66/cia5rhes702gfhkjxn9olbr7q", + "https://example.com/cia5rhes702gghkjxjutmvz9r/cia5rhes702ghhkjxevjnumc0/cia5rhes702gihkjxcsgdpbt7/cia5rhes702gjhkjxkajsb5n7", + "https://example.com/cia5rhes702gkhkjxpctjecch/cia5rhes702glhkjx4psglrrf/cia5rhes702gmhkjxqsa29brc/cia5rhes702gnhkjxtu5lc4me", + "https://example.com/cia5rhes702gohkjxy4ljuvei/cia5rhes702gphkjxscllm1ij/cia5rhes702gqhkjx1e8d9ndd/cia5rhes702grhkjxvt3mx80t", + "https://example.com/cia5rhes702gshkjxex3gg0nz/cia5rhes702gthkjxlonhrzjs/cia5rhes702guhkjxl4vdp4al/cia5rhes702gvhkjxvu5xtj65", + "https://example.com/cia5rhes702gwhkjx2eqa8s8p/cia5rhes702gxhkjxzs0d96f8/cia5rhes702gyhkjxh5qhyc6d/cia5rhes702gzhkjxit6h6kq6", + "https://example.com/cia5rhes702h0hkjxovyyxzzh/cia5rhes702h1hkjxumx2doq9/cia5rhes702h2hkjxe8rwx6ye/cia5rhes702h3hkjxd0biux3c", + "https://example.com/cia5rhes702h4hkjx0r9rhds4/cia5rhes802h5hkjxxe3pbik6/cia5rhes802h6hkjxkoyqybob/cia5rhes802h7hkjx0s2gcxkk", + "https://example.com/cia5rhes802h8hkjx1b212net/cia5rhes802h9hkjxhye14m2j/cia5rhes802hahkjxf87hamb3/cia5rhes802hbhkjxvqh1ek5s", + "https://example.com/cia5rhes802hchkjx5hdnwwul/cia5rhes802hdhkjxyc9ojtpr/cia5rhes802hehkjxdnrgdch1/cia5rhes802hfhkjxj6gwgjbt", + "https://example.com/cia5rhes802hghkjxehwpywy9/cia5rhes802hhhkjx4lbi6x6l/cia5rhes802hihkjxnpf2cz93/cia5rhes802hjhkjxv9bgej4e", + "https://example.com/cia5rhes802hkhkjxta1aj8pd/cia5rhes802hlhkjxqot5lx49/cia5rhes802hmhkjxs0uj77o1/cia5rhes802hnhkjx69uqlhl9", + "https://example.com/cia5rhes802hohkjxsetak465/cia5rhes802hphkjx7cc4cvnw/cia5rhes802hqhkjxyz2rd85f/cia5rhes802hrhkjxwwwj80zy", + "https://example.com/cia5rhes802hshkjxcxpfz2zy/cia5rhes802hthkjx0mg13xvr/cia5rhes802huhkjxl8tf2f1j/cia5rhes802hvhkjxkkdxui48", + "https://example.com/cia5rhes802hwhkjxnt5u3nhm/cia5rhes802hxhkjxbffb2x8l/cia5rhes802hyhkjxd0tm0h6e/cia5rhes802hzhkjxua697jh2", + "https://example.com/cia5rhes802i0hkjx5thy2y3q/cia5rhes802i1hkjx3jr1y269/cia5rhes802i2hkjxwwksi6eg/cia5rhes802i3hkjxor5nv0z2", + "https://example.com/cia5rhes802i4hkjx4ttg4je9/cia5rhes802i5hkjxqzq7w677/cia5rhes802i6hkjxeldnbsf2/cia5rhes802i7hkjxk8rmgjfv", + "https://example.com/cia5rhes802i8hkjx6eb7w4np/cia5rhes802i9hkjxstgvt28t/cia5rhes802iahkjx8b9vwdzr/cia5rhes802ibhkjx1pnrsc7b", + "https://example.com/cia5rhes802ichkjxvo1nrawf/cia5rhes802idhkjxgivthtjh/cia5rhes802iehkjxx967w9dk/cia5rhes802ifhkjxuu3hsee9", + "https://example.com/cia5rhes802ighkjxeijczff2/cia5rhes802ihhkjxer0knjjl/cia5rhes802iihkjx116p0tfc/cia5rhes802ijhkjxuomqb7a0", + "https://example.com/cia5rhes802ikhkjxzw0s6ejs/cia5rhes802ilhkjx1fgypntw/cia5rhes802imhkjx7jreimgw/cia5rhes802inhkjx3shm6234", + "https://example.com/cia5rhes802iohkjx28sv1ivu/cia5rhes802iphkjxr4p098ji/cia5rhes802iqhkjxdsotusgp/cia5rhes802irhkjx5kudhhd2", + "https://example.com/cia5rhes802ishkjxixiz6mp1/cia5rhes802ithkjxcagn4wzv/cia5rhes802iuhkjxnulj8edc/cia5rhes802ivhkjxkc73vmwx", + "https://example.com/cia5rhes802iwhkjx0gdh9w2o/cia5rhes802ixhkjxcl50g4e1/cia5rhes802iyhkjxrptys42g/cia5rhes802izhkjx4w62hqht", + "https://example.com/cia5rhes802j0hkjx692slfdu/cia5rhes802j1hkjxv3bwwytl/cia5rhes802j2hkjxx3gky18b/cia5rhes802j3hkjx3vsa5jra", + "https://example.com/cia5rhes802j4hkjx2xe9zlx3/cia5rhes802j5hkjx4f3wi9rl/cia5rhes802j6hkjx2qr5bzrp/cia5rhes802j7hkjxrw3fcfe5", + "https://example.com/cia5rhes802j8hkjx14k6emm1/cia5rhes802j9hkjxcll7rahj/cia5rhes802jahkjx6dmkabft/cia5rhes802jbhkjxj2d4kvm5", + "https://example.com/cia5rhes802jchkjxu3olmu84/cia5rhes802jdhkjx1kyxhqd9/cia5rhes802jehkjxmzlxuvus/cia5rhes802jfhkjxt7cvj5h1", + "https://example.com/cia5rhes802jghkjx80q77jzc/cia5rhes802jhhkjxtj8xxa1e/cia5rhes802jihkjxu4lnwkqf/cia5rhes802jjhkjx33w6a2yi", + "https://example.com/cia5rhes802jkhkjxefk60o55/cia5rhes802jlhkjx9yuz30ib/cia5rhes802jmhkjxhuhfcbcy/cia5rhes802jnhkjxf6wkt9ht", + "https://example.com/cia5rhes802johkjxbbd9f8zb/cia5rhes802jphkjxzk5mtk4f/cia5rhes802jqhkjxwb6eerfn/cia5rhes802jrhkjxpuyyqgqw", + "https://example.com/cia5rhes802jshkjxbgeqep0t/cia5rhes802jthkjxdkbxnh69/cia5rhes802juhkjx03vlhdpu/cia5rhes802jvhkjx8zhdnauu", + "https://example.com/cia5rhes802jwhkjxkw5bbsew/cia5rhes802jxhkjxgzdwzev2/cia5rhes802jyhkjxeutiz8ot/cia5rhes802jzhkjxeigt9qdf", + "https://example.com/cia5rhes802k0hkjxvmfunndw/cia5rhes802k1hkjx6j968gws/cia5rhes802k2hkjxjdz6yfk6/cia5rhes802k3hkjxnsoiuwfm", + "https://example.com/cia5rhes802k4hkjx7fezq8em/cia5rhes802k5hkjxsgmtvhig/cia5rhes802k6hkjx8h5r0ac5/cia5rhes802k7hkjxm0tczqr3", + "https://example.com/cia5rhes802k8hkjx3ej667en/cia5rhes802k9hkjxeta3mqrs/cia5rhes802kahkjxttm3mtbc/cia5rhes802kbhkjx08tnchxt", + "https://example.com/cia5rhes802kchkjxs9ys1d1h/cia5rhes802kdhkjxa7zfmkfh/cia5rhes802kehkjx0f1f3s5x/cia5rhes802kfhkjx5bkfptdv", + "https://example.com/cia5rhes802kghkjxfbx0j2be/cia5rhes802khhkjx2796rmnr/cia5rhes802kihkjxc8qjmfqv/cia5rhes802kjhkjx5l18ngbo", + "https://example.com/cia5rhes802kkhkjxuvxeycqp/cia5rhes802klhkjxt3xak01c/cia5rhes802kmhkjxsnbinf75/cia5rhes802knhkjxdke5f04u", + "https://example.com/cia5rhes802kohkjxlr2esas9/cia5rhes802kphkjxoi8bubek/cia5rhes802kqhkjx652tsdtk/cia5rhes802krhkjxx0d9sapx", + "https://example.com/cia5rhes802kshkjx01h9i4q2/cia5rhes802kthkjxnvp9j4x1/cia5rhes802kuhkjxyfb118if/cia5rhes802kvhkjxcajg7k7x", + "https://example.com/cia5rhes802kwhkjx1ahqqj6a/cia5rhes802kxhkjx576izsui/cia5rhes802kyhkjxdopj85lq/cia5rhes802kzhkjxrak3td4w", + "https://example.com/cia5rhes802l0hkjx4oalj3hp/cia5rhes802l1hkjxuiaufryz/cia5rhes802l2hkjx3yx8z13v/cia5rhes802l3hkjxql0nh4mw", + "https://example.com/cia5rhes802l4hkjxh2yk4att/cia5rhes802l5hkjx1ld7evsc/cia5rhes802l6hkjx64mt6pcs/cia5rhes802l7hkjxoa0hr513", + "https://example.com/cia5rhes802l8hkjxb8puz3pu/cia5rhes802l9hkjx40l0wzy4/cia5rhes802lahkjxvqaauxku/cia5rhes802lbhkjxxe2r13sb", + "https://example.com/cia5rhes802lchkjxp07dxy3z/cia5rhes802ldhkjx4p23lqcu/cia5rhes802lehkjxj1swfy96/cia5rhes802lfhkjxeppnm27y", + "https://example.com/cia5rhes802lghkjxowt32sxr/cia5rhes802lhhkjxr2wyl9ej/cia5rhes802lihkjx62orwsjq/cia5rhes802ljhkjxw99oj10o", + "https://example.com/cia5rhes802lkhkjxieavj07d/cia5rhes802llhkjxrglllbwb/cia5rhes802lmhkjxjbmalhyj/cia5rhes802lnhkjx54ff2569", + "https://example.com/cia5rhes802lohkjxgar3wut3/cia5rhes802lphkjx3y6byab9/cia5rhes802lqhkjx2ki1hks2/cia5rhes802lrhkjx867oulq5", + "https://example.com/cia5rhes802lshkjxb7hkzrqs/cia5rhes802lthkjxfu4yyljq/cia5rhes802luhkjxqswmaz83/cia5rhes802lvhkjxcgjxwpin", + "https://example.com/cia5rhes802lwhkjxnow6sr6f/cia5rhes802lxhkjxbtxn02ok/cia5rhes802lyhkjxtreu397w/cia5rhes802lzhkjx9fbk1l2s", + "https://example.com/cia5rhes802m0hkjxuov8bbjf/cia5rhes802m1hkjxfjxcjswu/cia5rhes802m2hkjxnuumriep/cia5rhes802m3hkjxv3abuieh", + "https://example.com/cia5rhes802m4hkjx7chzbj7m/cia5rhes802m5hkjxamwwacgg/cia5rhes802m6hkjxalw3n1b1/cia5rhes802m7hkjx4oobkiqi", + "https://example.com/cia5rhes802m8hkjx01qpkvwg/cia5rhes802m9hkjx8s23cjy5/cia5rhes802mahkjx7zklmzeg/cia5rhes802mbhkjxs9htzggq", + "https://example.com/cia5rhes802mchkjxn4kg4arq/cia5rhes802mdhkjxrlms5rxt/cia5rhes802mehkjx51y6d37q/cia5rhes802mfhkjxgq01e010", + "https://example.com/cia5rhes802mghkjxcuk2pmky/cia5rhes802mhhkjxcy28ajz9/cia5rhes802mihkjxm3xz72d2/cia5rhes802mjhkjxfecrsmb1", + "https://example.com/cia5rhes802mkhkjx976oo1q6/cia5rhes802mlhkjxt1d0ks1h/cia5rhes802mmhkjxlya2lnkr/cia5rhes802mnhkjxcjv6cg22", + "https://example.com/cia5rhes802mohkjxbmd2ljcc/cia5rhes802mphkjxqmpsf43e/cia5rhes802mqhkjx1018sa5h/cia5rhes802mrhkjx83lcx364", + "https://example.com/cia5rhes802mshkjxx317boaq/cia5rhes802mthkjxvgax0zmu/cia5rhes802muhkjxfgnulq5x/cia5rhes802mvhkjxg9czop5a", + "https://example.com/cia5rhes802mwhkjx3qru605e/cia5rhes802mxhkjxo14r6mbk/cia5rhes802myhkjxnxvtblhe/cia5rhes802mzhkjxp4fuyyvq", + "https://example.com/cia5rhes802n0hkjxta6r34nn/cia5rhes802n1hkjxn2des330/cia5rhes802n2hkjxut3wbscg/cia5rhes802n3hkjxi3sjsek8", + "https://example.com/cia5rhes802n4hkjxlrze879b/cia5rhes802n5hkjxl9d2bptv/cia5rhes802n6hkjxe2pyq523/cia5rhes802n7hkjx3d7uk0va", + "https://example.com/cia5rhes802n8hkjxsm87le7w/cia5rhes802n9hkjxilk0wcph/cia5rhes802nahkjx2n4ghjd4/cia5rhes802nbhkjx2z0n5kej", + "https://example.com/cia5rhes802nchkjxjyazwvt2/cia5rhes802ndhkjxg9kfrprk/cia5rhes802nehkjxbgdpif6f/cia5rhes802nfhkjxe0456keb", + "https://example.com/cia5rhes802nghkjxjhpr22o6/cia5rhes802nhhkjxplhnqcb8/cia5rhes802nihkjxzys0lxo2/cia5rhes802njhkjx2q01z427", + "https://example.com/cia5rhes802nkhkjx2bh3a4jg/cia5rhes802nlhkjxwr4hs5z6/cia5rhes802nmhkjxuj8y14q2/cia5rhes802nnhkjxuhl3zdhl", + "https://example.com/cia5rhes802nohkjxsghqd6qb/cia5rhes802nphkjxzwvmt5ut/cia5rhes802nqhkjxr3vatvee/cia5rhes802nrhkjx2bozv5k1", + "https://example.com/cia5rhes802nshkjx2d7r9wfy/cia5rhes802nthkjxcxj3kn6a/cia5rhes802nuhkjxdria7pkp/cia5rhes802nvhkjx6uliansr", + "https://example.com/cia5rhes802nwhkjx8nqjhhq1/cia5rhes802nxhkjxc2k2euc9/cia5rhes802nyhkjxdv6dq6vu/cia5rhes802nzhkjxidl9ujw8", + "https://example.com/cia5rhes802o0hkjxt3hs5pt1/cia5rhes802o1hkjxouvuo74k/cia5rhes802o2hkjx46xz3nds/cia5rhes802o3hkjxrrrkqadg", + "https://example.com/cia5rhes902o4hkjx2apdepej/cia5rhes902o5hkjxqqttkkkz/cia5rhes902o6hkjxh46l0jeu/cia5rhes902o7hkjxl7h17xdc", + "https://example.com/cia5rhes902o8hkjxbafzc6v5/cia5rhes902o9hkjxcuowkvn1/cia5rhes902oahkjxasvphtbh/cia5rhes902obhkjxgp6ckpu5", + "https://example.com/cia5rhes902ochkjxfb99zhss/cia5rhes902odhkjx0idz3cqv/cia5rhes902oehkjxy0f9nkn1/cia5rhes902ofhkjxnhrq2m1r", + "https://example.com/cia5rhes902oghkjx24kuk19k/cia5rhes902ohhkjx5hx5puqb/cia5rhes902oihkjxcaqprqtz/cia5rhes902ojhkjx3zh6ivhp", + "https://example.com/cia5rhes902okhkjxuk062elz/cia5rhes902olhkjxpv0ezkgb/cia5rhes902omhkjx6gkm3rj1/cia5rhes902onhkjxmckdzmmf", + "https://example.com/cia5rhes902oohkjx6667yepw/cia5rhes902ophkjxkhrilcux/cia5rhes902oqhkjxubgywv84/cia5rhes902orhkjxl2z5gfhv", + "https://example.com/cia5rhes902oshkjxwnznffds/cia5rhes902othkjx2nrd505l/cia5rhes902ouhkjxor8wvi62/cia5rhes902ovhkjxkknnf2c5", + "https://example.com/cia5rhes902owhkjx0xvzj6j4/cia5rhes902oxhkjxm8wjviav/cia5rhes902oyhkjxd48tw0nv/cia5rhes902ozhkjxy55fth1m", + "https://example.com/cia5rhes902p0hkjxhf2ln9fg/cia5rhes902p1hkjxn1kh849s/cia5rhes902p2hkjx7w18z1ij/cia5rhes902p3hkjx1iukw4f9", + "https://example.com/cia5rhes902p4hkjx94e9yno8/cia5rhes902p5hkjxgg7krrow/cia5rhes902p6hkjxs7qbcgio/cia5rhes902p7hkjxjy5ubg21", + "https://example.com/cia5rhes902p8hkjxc76syimq/cia5rhes902p9hkjxr4crms15/cia5rhes902pahkjxnijggak5/cia5rhes902pbhkjxzj7ajf4p", + "https://example.com/cia5rhes902pchkjxtq8dybc1/cia5rhes902pdhkjxwqg0v1ob/cia5rhes902pehkjxig150nfx/cia5rhes902pfhkjx4pn0r7va", + "https://example.com/cia5rhes902pghkjxg86s4zod/cia5rhes902phhkjxc16il6yq/cia5rhes902pihkjx25j53w11/cia5rhes902pjhkjxar484o36", + "https://example.com/cia5rhes902pkhkjxqjtgnf6o/cia5rhes902plhkjxx22y2p6c/cia5rhes902pmhkjxu72lfdom/cia5rhes902pnhkjxv7bb9e9q", + "https://example.com/cia5rhes902pohkjxxb029uj1/cia5rhes902pphkjx4ujdzzo5/cia5rhes902pqhkjxx4lnnhw7/cia5rhes902prhkjx6x2u79ck", + "https://example.com/cia5rhes902pshkjxd1hhakk6/cia5rhes902pthkjxmpu8mcyi/cia5rhes902puhkjxzpcbicof/cia5rhes902pvhkjxij383b25", + "https://example.com/cia5rhes902pwhkjxiur6rdbh/cia5rhes902pxhkjxyhkhpxrq/cia5rhes902pyhkjx29a11uyj/cia5rhes902pzhkjxf1p8g30r", + "https://example.com/cia5rhes902q0hkjxotowbqgb/cia5rhes902q1hkjxmb7p5sr6/cia5rhes902q2hkjx378apexd/cia5rhes902q3hkjxjkglr1c4", + "https://example.com/cia5rhes902q4hkjxxcw4jsq6/cia5rhes902q5hkjxqenj7c97/cia5rhes902q6hkjx2ye8s3q1/cia5rhes902q7hkjxtxm7sdya", + "https://example.com/cia5rhes902q8hkjxkc9vstb2/cia5rhes902q9hkjxzwok7ng9/cia5rhes902qahkjx8ygp04d1/cia5rhes902qbhkjx4qux7aki", + "https://example.com/cia5rhes902qchkjx57dfmt8h/cia5rhes902qdhkjx9b0035cy/cia5rhes902qehkjxvebgxts8/cia5rhes902qfhkjxu6yi37mb", + "https://example.com/cia5rhes902qghkjx6xi3dyjx/cia5rhes902qhhkjx9x0aclfr/cia5rhes902qihkjx7mxvg28t/cia5rhes902qjhkjx9q2wphpa", + "https://example.com/cia5rhes902qkhkjxu6s6q2q0/cia5rhes902qlhkjxlgcpgxpx/cia5rhes902qmhkjxc1dxbvr1/cia5rhes902qnhkjx6bvhf6hr", + "https://example.com/cia5rhes902qohkjx1tm0hkvs/cia5rhes902qphkjx3pu22rbr/cia5rhes902qqhkjxdjcth8ug/cia5rhes902qrhkjxwhg6mr88", + "https://example.com/cia5rhes902qshkjxji4arcck/cia5rhes902qthkjx5t1kjk9o/cia5rhes902quhkjx88zgme2o/cia5rhes902qvhkjxhs22agoc", + "https://example.com/cia5rhes902qwhkjxkqdfy8em/cia5rhes902qxhkjxo11waca0/cia5rhes902qyhkjxthqzds0b/cia5rhes902qzhkjx890jrftn", + "https://example.com/cia5rhes902r0hkjx2scv74kv/cia5rhes902r1hkjxhczgr5iw/cia5rhes902r2hkjxd9v3ewx1/cia5rhes902r3hkjxx4tpj5xh", + "https://example.com/cia5rhes902r4hkjx8217649m/cia5rhes902r5hkjx954lwwvc/cia5rhes902r6hkjxzczxe9o4/cia5rhes902r7hkjxadtvbtm3", + "https://example.com/cia5rhes902r8hkjx1su72qpn/cia5rhes902r9hkjxexh45oq0/cia5rhes902rahkjxah76ntxr/cia5rhes902rbhkjxnnfojf19", + "https://example.com/cia5rhes902rchkjxc85n1zzu/cia5rhes902rdhkjxix5w6nkz/cia5rhes902rehkjxogcyyb50/cia5rhes902rfhkjx3r7glwov", + "https://example.com/cia5rhes902rghkjxtck9dhwc/cia5rhes902rhhkjxru36hy4a/cia5rhes902rihkjxmmyc9tpx/cia5rhes902rjhkjxmbsypxaq", + "https://example.com/cia5rhes902rkhkjx2fo040pu/cia5rhes902rlhkjxr65jltb9/cia5rhes902rmhkjxnzf86rqg/cia5rhes902rnhkjxca9gnhfv", + "https://example.com/cia5rhes902rohkjx3t12qfew/cia5rhes902rphkjx0uy0q6x0/cia5rhes902rqhkjxytr1mozv/cia5rhes902rrhkjxti5cpfhq", + "https://example.com/cia5rhes902rshkjxtzuesbvw/cia5rhes902rthkjx4imx7yq2/cia5rhes902ruhkjxv5rwbdfw/cia5rhes902rvhkjxx9dyruvh", + "https://example.com/cia5rhes902rwhkjx80skj5fy/cia5rhes902rxhkjxs2roo0or/cia5rhes902ryhkjx0f0egqew/cia5rhes902rzhkjx2qyobgwd", + "https://example.com/cia5rhes902s0hkjxwzjb0ibj/cia5rhes902s1hkjxthhdzgdb/cia5rhes902s2hkjxmp0am5hc/cia5rhes902s3hkjxou8fe0bw", + "https://example.com/cia5rhes902s4hkjxy807y0wz/cia5rhes902s5hkjxyi0ucjpj/cia5rhes902s6hkjx57r4913i/cia5rhes902s7hkjx5zyg25co", + "https://example.com/cia5rhes902s8hkjxtv0y9qsr/cia5rhes902s9hkjxmara3sln/cia5rhes902sahkjx16zbww31/cia5rhes902sbhkjxk3yfnqrf", + "https://example.com/cia5rhes902schkjxmqs7wb8e/cia5rhes902sdhkjxbzqsikjf/cia5rhes902sehkjxifnkxd42/cia5rhes902sfhkjxeslnix9t", + "https://example.com/cia5rhes902sghkjx9csqi025/cia5rhes902shhkjx03m41rdk/cia5rhes902sihkjx7o16p436/cia5rhes902sjhkjxuopqyoaf", + "https://example.com/cia5rhes902skhkjxkj9lox0l/cia5rhes902slhkjx4siwdfz6/cia5rhes902smhkjxkz6smrk5/cia5rhes902snhkjxbydhx9sr", + "https://example.com/cia5rhes902sohkjxmt7rn0m7/cia5rhes902sphkjxbr1rrero/cia5rhes902sqhkjxgsa5faxo/cia5rhes902srhkjxkaypi7hq", + "https://example.com/cia5rhes902sshkjxcdabjgaq/cia5rhes902sthkjxdj1l2sdw/cia5rhes902suhkjx4w18whjz/cia5rhes902svhkjx00bsy24i", + "https://example.com/cia5rhes902swhkjx1sxzd3bs/cia5rhes902sxhkjxwihbb32s/cia5rhes902syhkjxjv82ql1y/cia5rhes902szhkjxhx2p1tjw", + "https://example.com/cia5rhes902t0hkjxlq1v45l8/cia5rhes902t1hkjxpcb65x6c/cia5rhes902t2hkjxqd79lp9t/cia5rhes902t3hkjxzlu0vgsq", + "https://example.com/cia5rhes902t4hkjxchoh1xz9/cia5rhes902t5hkjxi7ja8w34/cia5rhes902t6hkjxcibihy5j/cia5rhes902t7hkjxzxhj2llf", + "https://example.com/cia5rhes902t8hkjxe6kjteus/cia5rhes902t9hkjxct0osy9c/cia5rhes902tahkjxkpn37x26/cia5rhes902tbhkjxs1i3y06r", + "https://example.com/cia5rhes902tchkjxkijsvrry/cia5rhes902tdhkjx478e7b15/cia5rhes902tehkjxe15r2zp0/cia5rhes902tfhkjx0xdr6u4g", + "https://example.com/cia5rhes902tghkjxre727axs/cia5rhes902thhkjx8tjhkncn/cia5rhes902tihkjxfwe9moa8/cia5rhes902tjhkjxrw37is68", + "https://example.com/cia5rhes902tkhkjx1vha7oxy/cia5rhes902tlhkjxorgrss4a/cia5rhes902tmhkjx5v2vjvpc/cia5rhes902tnhkjxe13xjwvn", + "https://example.com/cia5rhes902tohkjxhh415ghg/cia5rhes902tphkjxewddudgl/cia5rhes902tqhkjxlt904su4/cia5rhes902trhkjxvox3ueb9", + "https://example.com/cia5rhes902tshkjx565cdwgu/cia5rhes902tthkjx7v8dxnp1/cia5rhes902tuhkjx9lkhhc8x/cia5rhes902tvhkjxet30fwnm", + "https://example.com/cia5rhes902twhkjx50zbd0gj/cia5rhes902txhkjxcxmjzp6i/cia5rhes902tyhkjx4wwog6sc/cia5rhes902tzhkjxl5k35m8y", + "https://example.com/cia5rhes902u0hkjxmixf873e/cia5rhes902u1hkjxqkzx249g/cia5rhes902u2hkjxq1h6e73c/cia5rhes902u3hkjxy0raorlv", + "https://example.com/cia5rhes902u4hkjxp7qu708r/cia5rhes902u5hkjxjq511roe/cia5rhes902u6hkjx24zsjlw7/cia5rhes902u7hkjxxao19ibw", + "https://example.com/cia5rhes902u8hkjxjj8e4qjy/cia5rhes902u9hkjxnpfmyyee/cia5rhesa02uahkjx2cbqjqlj/cia5rhesa02ubhkjx2sb57ho9", + "https://example.com/cia5rhesa02uchkjxi7stcunb/cia5rhesa02udhkjxf13m1va9/cia5rhesa02uehkjxbrbvzlts/cia5rhesa02ufhkjxv7c5sg8p", + "https://example.com/cia5rhesa02ughkjxc4bg17mm/cia5rhesa02uhhkjx1fodi8bu/cia5rhesa02uihkjx1pgynm8w/cia5rhesa02ujhkjx21oibarf", + "https://example.com/cia5rhesa02ukhkjx9l592wdv/cia5rhesa02ulhkjxvbp05nkt/cia5rhesa02umhkjxosf9qynb/cia5rhesa02unhkjx7bb91ukh", + "https://example.com/cia5rhesa02uohkjxkss6ccme/cia5rhesa02uphkjxvd93brv7/cia5rhesa02uqhkjxoc61qqcx/cia5rhesa02urhkjxawfbw41u", + "https://example.com/cia5rhesa02ushkjxgxz51hyw/cia5rhesa02uthkjxewu4vl7k/cia5rhesa02uuhkjxknapklva/cia5rhesa02uvhkjxmdf6weyv", + "https://example.com/cia5rhesa02uwhkjx9egqxjsi/cia5rhesa02uxhkjxfzhkd5yr/cia5rhesa02uyhkjx8hv3p08k/cia5rhesa02uzhkjx45psji1y", + "https://example.com/cia5rhesa02v0hkjx4l8lsl3u/cia5rhesa02v1hkjxngdy1ar6/cia5rhesa02v2hkjx6jo5h3qu/cia5rhesa02v3hkjxzbv3dpni", + "https://example.com/cia5rhesa02v4hkjxtcyq3hrj/cia5rhesa02v5hkjxi1yvcnxy/cia5rhesa02v6hkjxw7v5871t/cia5rhesa02v7hkjxe8a1rpaz", + "https://example.com/cia5rhesa02v8hkjx11e94e28/cia5rhesa02v9hkjxy79y9wsa/cia5rhesa02vahkjx4x1k6e7p/cia5rhesa02vbhkjx4nugadg0", + "https://example.com/cia5rhesa02vchkjxyff973f9/cia5rhesa02vdhkjxxeylqp99/cia5rhesa02vehkjxup7kbh2i/cia5rhesa02vfhkjxvik2bnru", + "https://example.com/cia5rhesa02vghkjxojpmez35/cia5rhesa02vhhkjxrsr1rbtw/cia5rhesa02vihkjxz51r21kh/cia5rhesa02vjhkjxnry87ysd", + "https://example.com/cia5rhesa02vkhkjxc7kcgnod/cia5rhesa02vlhkjxou8csehx/cia5rhesa02vmhkjx4g46j5vv/cia5rhesa02vnhkjx0xdxordo", + "https://example.com/cia5rhesa02vohkjxcpd7futv/cia5rhesa02vphkjxjhhvuq13/cia5rhesa02vqhkjx1jx0mwyq/cia5rhesa02vrhkjxatheqhre", + "https://example.com/cia5rhesa02vshkjxhoxrm7du/cia5rhesa02vthkjxp3j2d7pl/cia5rhesa02vuhkjxfajs3kp2/cia5rhesa02vvhkjx094w7t5z", + "https://example.com/cia5rhesa02vwhkjx8zsoc546/cia5rhesa02vxhkjxbbwesmgs/cia5rhesa02vyhkjxah7vbsl2/cia5rhesa02vzhkjxccc1osvb", + "https://example.com/cia5rhesa02w0hkjxilmp1gcj/cia5rhesa02w1hkjxpax3mj4u/cia5rhesa02w2hkjxl4830fix/cia5rhesa02w3hkjxushwofrd", + "https://example.com/cia5rhesa02w4hkjx0ayq3lna/cia5rhesa02w5hkjxtyfjinxi/cia5rhesa02w6hkjx6v3jk6np/cia5rhesa02w7hkjxb3kzmwfz", + "https://example.com/cia5rhesa02w8hkjxpfztdog3/cia5rhesa02w9hkjxxu1jj9ro/cia5rhesa02wahkjx9x02t4s6/cia5rhesa02wbhkjxmudm4let", + "https://example.com/cia5rhesa02wchkjxf8gwzm46/cia5rhesa02wdhkjxnogroqj5/cia5rhesa02wehkjxzcswjm19/cia5rhesa02wfhkjxd7sq70cn", + "https://example.com/cia5rhesa02wghkjxzl71wo9i/cia5rhesa02whhkjx4qzdc4en/cia5rhesa02wihkjxqcwczavg/cia5rhesa02wjhkjx5v1mo7io", + "https://example.com/cia5rhesa02wkhkjx17rwy1u4/cia5rhesa02wlhkjxupgzmlhz/cia5rhesa02wmhkjxy4guynpo/cia5rhesa02wnhkjx7hwclzmy", + "https://example.com/cia5rhesa02wohkjxbhrfwyae/cia5rhesa02wphkjxg54k6a1v/cia5rhesa02wqhkjxxx6ovpcu/cia5rhesa02wrhkjx7chazbg3", + "https://example.com/cia5rhesa02wshkjxsqxk0429/cia5rhesa02wthkjxjmhikrl3/cia5rhesa02wuhkjxojb6ebr1/cia5rhesa02wvhkjxrcv5ezdg", + "https://example.com/cia5rhesa02wwhkjx6oc3w8ov/cia5rhesa02wxhkjxm2i72vec/cia5rhesa02wyhkjx3fh9ne9a/cia5rhesa02wzhkjx0971hhm1", + "https://example.com/cia5rhesa02x0hkjxwenvo26l/cia5rhesa02x1hkjxtfilhs8a/cia5rhesa02x2hkjxpqvnoyqk/cia5rhesa02x3hkjx23vjztdc", + "https://example.com/cia5rhesa02x4hkjxsgmx5os7/cia5rhesa02x5hkjxgrehs28q/cia5rhesa02x6hkjxvxtnze9l/cia5rhesa02x7hkjx0vlv9z1s", + "https://example.com/cia5rhesa02x8hkjxge35kywm/cia5rhesa02x9hkjxw2tbefo5/cia5rhesa02xahkjxv137f9qt/cia5rhesa02xbhkjxnz9ep47k", + "https://example.com/cia5rhesa02xchkjx00anlyr6/cia5rhesa02xdhkjx79zjud7w/cia5rhesa02xehkjxrb6rk7rw/cia5rhesa02xfhkjxphslyr6m", + "https://example.com/cia5rhesa02xghkjxv656h0en/cia5rhesa02xhhkjxwt9sllti/cia5rhesa02xihkjxbblv9n51/cia5rhesa02xjhkjxoms9ldox", + "https://example.com/cia5rhesa02xkhkjxb0ljdnru/cia5rhesa02xlhkjxuysb8km6/cia5rhesa02xmhkjx7sdb3ap1/cia5rhesa02xnhkjx556d8gld", + "https://example.com/cia5rhesa02xohkjxmh07tx4r/cia5rhesa02xphkjxzekp4dcp/cia5rhesa02xqhkjxywdkdqe0/cia5rhesa02xrhkjxn2exhmk3", + "https://example.com/cia5rhesa02xshkjxot0nxe5o/cia5rhesa02xthkjxxns5dc6l/cia5rhesa02xuhkjxppx37eq1/cia5rhesa02xvhkjxa0n1ft75", + "https://example.com/cia5rhesa02xwhkjxdlz42y7u/cia5rhesa02xxhkjxyyrh6ehu/cia5rhesa02xyhkjxc2snloyu/cia5rhesa02xzhkjx7hniv0l2", + "https://example.com/cia5rhesa02y0hkjx1ufllm5d/cia5rhesa02y1hkjxyciovmxi/cia5rhesa02y2hkjxeecnhafz/cia5rhesa02y3hkjxka899vl8", + "https://example.com/cia5rhesa02y4hkjxas1scma5/cia5rhesa02y5hkjx7hx7cgry/cia5rhesa02y6hkjxt4r854cj/cia5rhesa02y7hkjxl2d58z7z", + "https://example.com/cia5rhesa02y8hkjx6uvep3v0/cia5rhesa02y9hkjxzkox5acn/cia5rhesa02yahkjxbsttsd42/cia5rhesa02ybhkjx9s0eooqr", + "https://example.com/cia5rhesa02ychkjxs38kr0ju/cia5rhesa02ydhkjxp7i0dm4v/cia5rhesa02yehkjxytdwg00m/cia5rhesa02yfhkjx6kojbk3h", + "https://example.com/cia5rhesa02yghkjxu47g5rne/cia5rhesa02yhhkjxp5jiqzck/cia5rhesa02yihkjx9n0k7a86/cia5rhesa02yjhkjxawsmggmg", + "https://example.com/cia5rhesa02ykhkjxpz5t3big/cia5rhesa02ylhkjxd7mv52ko/cia5rhesa02ymhkjxaq7e3qjp/cia5rhesa02ynhkjxx6n59eel", + "https://example.com/cia5rhesa02yohkjx151ccq4m/cia5rhesa02yphkjxs6vfdnyb/cia5rhesa02yqhkjxakcfphvj/cia5rhesa02yrhkjxrtgqqlkl", + "https://example.com/cia5rhesa02yshkjx5nthrj0p/cia5rhesa02ythkjxaoa6xfw1/cia5rhesa02yuhkjxsyk94k5s/cia5rhesa02yvhkjxp1fsbr6q", + "https://example.com/cia5rhesa02ywhkjxxrjgdzm9/cia5rhesa02yxhkjxmt4liicf/cia5rhesa02yyhkjxqnavwf3w/cia5rhesa02yzhkjxwb6efk2q", + "https://example.com/cia5rhesa02z0hkjxai4gwv4i/cia5rhesb02z1hkjxotmfrbh1/cia5rhesb02z2hkjxcorstega/cia5rhesb02z3hkjxmm6qrl72", + "https://example.com/cia5rhesb02z4hkjx119h63fe/cia5rhesb02z5hkjx51zhx95d/cia5rhesb02z6hkjx13iaxgj7/cia5rhesb02z7hkjxnhttadyh", + "https://example.com/cia5rhesb02z8hkjx7n279k7d/cia5rhesb02z9hkjxtyhtwvh4/cia5rhesb02zahkjxuxc8tnjw/cia5rhesb02zbhkjx9f5w1igd", + "https://example.com/cia5rhesb02zchkjxu6u03gpq/cia5rhesb02zdhkjxad5fmie8/cia5rhesb02zehkjx82hi1ubw/cia5rhesb02zfhkjxlz014bc9", + "https://example.com/cia5rhesb02zghkjxpcp41mh4/cia5rhesb02zhhkjxwtmgx1un/cia5rhesb02zihkjxvdlzs9gj/cia5rhesb02zjhkjxxnbuzjtx", + "https://example.com/cia5rhesb02zkhkjxszpr5g1b/cia5rhesb02zlhkjx5r4u5x2d/cia5rhesb02zmhkjxj9k1c9lb/cia5rhesb02znhkjx76dnsetw", + "https://example.com/cia5rhesb02zohkjxk9w5hbj0/cia5rhesb02zphkjxsz3yi7na/cia5rhesb02zqhkjxn35x1ss7/cia5rhesb02zrhkjxcmyedvkx", + "https://example.com/cia5rhesb02zshkjxhuw9xl6g/cia5rhesb02zthkjxnkalu85l/cia5rhesb02zuhkjx0nb3kn0f/cia5rhesb02zvhkjxekzyryxq", + "https://example.com/cia5rhesb02zwhkjxl2y2pyxt/cia5rhesb02zxhkjxeqoaa6v8/cia5rhesb02zyhkjxu5g8zso5/cia5rhesb02zzhkjx4t9www0y", + "https://example.com/cia5rhesb0300hkjxdl9hwbgu/cia5rhesb0301hkjxed3e759g/cia5rhesb0302hkjxhsksjcb6/cia5rhesb0303hkjx3fn4nqpg", + "https://example.com/cia5rhesb0304hkjx0j1w4t7r/cia5rhesb0305hkjx8v918aao/cia5rhesb0306hkjxvdvahvsq/cia5rhesb0307hkjxooa62xp7", + "https://example.com/cia5rhesb0308hkjx33xkvxg7/cia5rhesb0309hkjxws0twzy4/cia5rhesb030ahkjxtpn8o3r0/cia5rhesb030bhkjxh3wusg8f", + "https://example.com/cia5rhesb030chkjxt4rz9ksr/cia5rhesb030dhkjxklvupy26/cia5rhesb030ehkjxxf7wxi4e/cia5rhesb030fhkjxa11i0b7u", + "https://example.com/cia5rhesb030ghkjxc1beup56/cia5rhesb030hhkjxxcgs2kpz/cia5rhesb030ihkjxc7a9g482/cia5rhesb030jhkjxjb51smqy", + "https://example.com/cia5rhesb030khkjxu6a7pi1g/cia5rhesb030lhkjxo0qkosjn/cia5rhesb030mhkjxucjtxcjj/cia5rhesb030nhkjxbn92q469", + "https://example.com/cia5rhesb030ohkjxalklya9k/cia5rhesb030phkjx2s6kj633/cia5rhesb030qhkjx5dus8zhl/cia5rhesb030rhkjxexszt1qv", + "https://example.com/cia5rhesb030shkjxs325mfs5/cia5rhesb030thkjxhj36rw16/cia5rhesb030uhkjxzs6na9ek/cia5rhesb030vhkjxcahu5xdq", + "https://example.com/cia5rhesb030whkjxbq6vx29f/cia5rhesb030xhkjxzcefm4gv/cia5rhesb030yhkjxcx0o6obs/cia5rhesb030zhkjxy6a654xp", + "https://example.com/cia5rhesb0310hkjx74o7x2ol/cia5rhesb0311hkjxbaa60v2j/cia5rhesb0312hkjxws59xi68/cia5rhesb0313hkjxe024wl25", + "https://example.com/cia5rhesb0314hkjxsmo6r7qy/cia5rhesb0315hkjxyo4vsu02/cia5rhesb0316hkjxq7bv4sl0/cia5rhesb0317hkjxnu8xcpds", + "https://example.com/cia5rhesb0318hkjxfllzbwdp/cia5rhesb0319hkjx9jpu3qeb/cia5rhesb031ahkjx1yw2rxmr/cia5rhesb031bhkjx0kh6iq8e", + "https://example.com/cia5rhesb031chkjxdjrdwexa/cia5rhesb031dhkjxpdkelmj0/cia5rhesb031ehkjxo0q4659i/cia5rhesb031fhkjxqj6po4aw", + "https://example.com/cia5rhesb031ghkjxydbw4cnp/cia5rhesb031hhkjx7ak9k5ib/cia5rhesb031ihkjxshjg8guf/cia5rhesb031jhkjxgc7isxom", + "https://example.com/cia5rhesb031khkjxilu3xhrq/cia5rhesb031lhkjx7qa2tmqv/cia5rhesb031mhkjx2gqx175d/cia5rhesb031nhkjxcclzxfj5", + "https://example.com/cia5rhesb031ohkjxnqa5u0yu/cia5rhesb031phkjxvj65rgc0/cia5rhesb031qhkjxps94ct2m/cia5rhesb031rhkjx13vf7hqf", + "https://example.com/cia5rhesb031shkjx4c0rxkqe/cia5rhesb031thkjx2f1rlhtc/cia5rhesb031uhkjxc6mmo6r9/cia5rhesb031vhkjxmkdbf7tz", + "https://example.com/cia5rhesb031whkjx4wrcwah4/cia5rhesb031xhkjxjy564fgv/cia5rhesb031yhkjxel00ycv5/cia5rhesb031zhkjxwv42qge9", + "https://example.com/cia5rhesb0320hkjx579rdytw/cia5rhesb0321hkjxqwktfaa3/cia5rhesb0322hkjx5uguvgm4/cia5rhesb0323hkjxod265vm7", + "https://example.com/cia5rhesb0324hkjxo91kfm12/cia5rhesb0325hkjx7eeoo34p/cia5rhesb0326hkjxkcbju4fy/cia5rhesb0327hkjx9rgv9jej", + "https://example.com/cia5rhesb0328hkjxu29htifz/cia5rhesb0329hkjx833v3icl/cia5rhesb032ahkjxp93q4nqo/cia5rhesb032bhkjx4tktxa61", + "https://example.com/cia5rhesb032chkjxli18annx/cia5rhesb032dhkjxoin4rpsb/cia5rhesb032ehkjxkezkkq9n/cia5rhesb032fhkjxxq4syq15", + "https://example.com/cia5rhesb032ghkjxjr48ia8g/cia5rhesb032hhkjxaz6zhm4c/cia5rhesb032ihkjxriefifyj/cia5rhesb032jhkjxt06hn2ix", + "https://example.com/cia5rhesb032khkjxl0o2c8hq/cia5rhesb032lhkjxvlfg1dcu/cia5rhesb032mhkjxa6neghbc/cia5rhesb032nhkjxomcdlu3w", + "https://example.com/cia5rhesb032ohkjxnrefhx6j/cia5rhesb032phkjx05xbd8mi/cia5rhesb032qhkjx22ncbb1j/cia5rhesb032rhkjx8mqw8vvb", + "https://example.com/cia5rhesb032shkjxzw7wur7z/cia5rhesb032thkjxdybqu2ix/cia5rhesb032uhkjxjqrudsu0/cia5rhesb032vhkjx60p88zgu", + "https://example.com/cia5rhesb032whkjxsj2cgd7r/cia5rhesb032xhkjxjv4oyt79/cia5rhesb032yhkjxlzlkj3x2/cia5rhesb032zhkjxhkvllyb6", + "https://example.com/cia5rhesb0330hkjxhduykvat/cia5rhesb0331hkjxqg1x6769/cia5rhesb0332hkjx8scwhj5n/cia5rhesb0333hkjxous8ibmx", +]; diff --git a/dom/cache/test/mochitest/message_receiver.html b/dom/cache/test/mochitest/message_receiver.html new file mode 100644 index 0000000000..82cb587c72 --- /dev/null +++ b/dom/cache/test/mochitest/message_receiver.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<script> + navigator.serviceWorker.onmessage = function(e) { + window.parent.postMessage(e.data, "*"); + }; +</script> diff --git a/dom/cache/test/mochitest/mirror.sjs b/dom/cache/test/mochitest/mirror.sjs new file mode 100644 index 0000000000..0006aba8f0 --- /dev/null +++ b/dom/cache/test/mochitest/mirror.sjs @@ -0,0 +1,5 @@ +function handleRequest(request, response) { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Mirrored", request.getHeader("Mirror")); + response.write(request.getHeader("Mirror")); +} diff --git a/dom/cache/test/mochitest/mochitest.ini b/dom/cache/test/mochitest/mochitest.ini new file mode 100644 index 0000000000..51dc9917be --- /dev/null +++ b/dom/cache/test/mochitest/mochitest.ini @@ -0,0 +1,66 @@ +[DEFAULT] +tags = condprof +support-files = + test_cache.js + test_cache_add.js + worker_driver.js + worker_wrapper.js + frame.html + message_receiver.html + driver.js + serviceworker_driver.js + test_cache_match_request.js + test_cache_matchAll_request.js + test_cache_overwrite.js + mirror.sjs + test_cache_match_vary.js + vary.sjs + test_caches.js + test_cache_keys.js + test_cache_put.js + test_cache_requestCache.js + test_cache_delete.js + test_cache_put_reorder.js + test_cache_redirect.js + test_cache_https.js + large_url_list.js + empty.html + idle_worker.js + +[test_cache.html] +[test_cache_add.html] +[test_cache_match_request.html] +[test_cache_matchAll_request.html] +[test_cache_overwrite.html] +skip-if = + http3 +[test_cache_match_vary.html] +[test_caches.html] +[test_cache_keys.html] +[test_cache_put.html] +[test_cache_requestCache.html] +[test_cache_delete.html] +[test_cache_put_reorder.html] +[test_cache_https.html] +[test_cache_redirect.html] +[test_cache_restart.html] +[test_cache_shrink.html] +[test_cache_orphaned_cache.html] +[test_cache_orphaned_body.html] +scheme=https +skip-if = + os == "linux" && bits == 64 && debug # Bug 1749068 +[test_cache_padding.html] +skip-if = verify + os == 'android' # bug 1468434 for android + condprof #: "The opaque response should be removed by caches.delete() and cache.delete()" +[test_cache_untrusted.html] +skip-if = + http3 +[test_cache_updateUsage.html] +[test_chrome_constructor.html] +[test_cache_worker_gc.html] +scheme=https +skip-if = + os == "linux" && bits == 64 && debug # Bug 1749068 +[test_cache_tons_of_fd.html] diff --git a/dom/cache/test/mochitest/serviceworker_driver.js b/dom/cache/test/mochitest/serviceworker_driver.js new file mode 100644 index 0000000000..3406cdcc05 --- /dev/null +++ b/dom/cache/test/mochitest/serviceworker_driver.js @@ -0,0 +1,38 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +function serviceWorkerTestExec(testFile) { + return new Promise(function (resolve, reject) { + function setupSW(registration) { + var worker = + registration.installing || registration.waiting || registration.active; + var iframe; + + window.addEventListener("message", function onMessage(event) { + if (event.data.context != "ServiceWorker") { + return; + } + if (event.data.type == "finish") { + window.removeEventListener("message", onMessage); + iframe.remove(); + registration.unregister().then(resolve).catch(reject); + } else if (event.data.type == "status") { + ok(event.data.status, event.data.context + ": " + event.data.msg); + } + }); + + worker.onerror = reject; + + iframe = document.createElement("iframe"); + iframe.src = "message_receiver.html"; + iframe.onload = function () { + worker.postMessage({ script: testFile }); + }; + document.body.appendChild(iframe); + } + + navigator.serviceWorker + .register("worker_wrapper.js", { scope: "." }) + .then(setupSW); + }); +} diff --git a/dom/cache/test/mochitest/test_cache.html b/dom/cache/test/mochitest/test_cache.html new file mode 100644 index 0000000000..1ac3447120 --- /dev/null +++ b/dom/cache/test/mochitest/test_cache.html @@ -0,0 +1,20 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Validate Interfaces Exposed to Workers</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="driver.js"></script> +</head> +<body> +<iframe id="frame"></iframe> +<script class="testbody" type="text/javascript"> + runTests("test_cache.js") + .then(function() { + SimpleTest.finish(); + }); +</script> +</body> +</html> diff --git a/dom/cache/test/mochitest/test_cache.js b/dom/cache/test/mochitest/test_cache.js new file mode 100644 index 0000000000..7d6d01b34b --- /dev/null +++ b/dom/cache/test/mochitest/test_cache.js @@ -0,0 +1,195 @@ +/* global context testDone:true */ + +var c = null; +var request = "https://example.com/hmm?q=foobar" + context; +var response = new Response("This is some Response!"); +var name = "snafu" + context; +var foobar = "foobar" + context; + +ok(!!caches, "caches object should be available on global"); +caches + .open(name) + .then(function (openCache) { + ok( + openCache instanceof Cache, + "cache object should be resolved from caches.open" + ); + return caches.has(name); + }) + .then(function (hasResult) { + ok(hasResult, "caches.has() should resolve true"); + return caches.keys(); + }) + .then(function (keys) { + ok(!!keys, "caches.keys() should resolve to a truthy value"); + ok( + keys.length >= 1, + "caches.keys() should resolve to an array of length at least 1" + ); + ok( + keys.includes(name), + "caches.keys() should resolve to an array containing key" + ); + return caches.delete(name); + }) + .then(function (deleteResult) { + ok(deleteResult, "caches.delete() should resolve true"); + return caches.has(name); + }) + .then(function (hasMissingCache) { + ok(!hasMissingCache, "missing key should return false from has"); + }) + .then(function () { + return caches.open(name); + }) + .then(function (snafu) { + return snafu.keys(); + }) + .then(function (empty) { + is(0, empty.length, "cache.keys() should resolve to an array of length 0"); + }) + .then(function () { + return caches.open(name); + }) + .then(function (snafu) { + var req = "./cachekey"; + var res = new Response("Hello world"); + return snafu + .put("ftp://invalid", res) + .then(function () { + ok(false, "This should fail"); + }) + .catch(function (err) { + is( + err.name, + "TypeError", + "put() should throw TypeError for invalid scheme" + ); + return snafu.put(req, res); + }) + .then(function (v) { + return snafu; + }); + }) + .then(function (snafu) { + return Promise.all([snafu, snafu.keys()]); + }) + .then(function (args) { + var snafu = args[0]; + var keys = args[1]; + is(1, keys.length, "cache.keys() should resolve to an array of length 1"); + ok(keys[0] instanceof Request, "key should be a Request"); + ok(keys[0].url.match(/cachekey$/), "Request URL should match original"); + return Promise.all([ + snafu, + snafu.match(keys[0]), + snafu.match("ftp://invalid"), + ]); + }) + .then(function (args) { + var snafu = args[0]; + var res = args[1]; + ok(res instanceof Response, "value should be a Response"); + is(res.status, 200, "Response status should be 200"); + is( + undefined, + args[2], + "Match with invalid scheme should resolve undefined" + ); + return Promise.all([snafu, snafu.put("./cachekey2", res)]); + }) + .then(function (args) { + var snafu = args[0]; + return snafu.match("./cachekey2"); + }) + .then(function (res) { + return res.text().then(function (v) { + is(v, "Hello world", "Response body should match original"); + }); + }) + .then(function () { + // FIXME(nsm): Can't use a Request object for now since the operations + // consume it's 'body'. See + // https://github.com/slightlyoff/ServiceWorker/issues/510. + return caches.open(foobar); + }) + .then(function (openCache) { + c = openCache; + return c.put(request, response); + }) + .then(function (putResponse) { + is(putResponse, undefined, "The promise should resolve to undefined"); + return c.keys(request); + }) + .then(function (keys) { + ok(keys, "Valid keys object expected"); + is(keys.length, 1, "Only one key is expected"); + return c.keys(); + }) + .then(function (keys) { + ok(keys, "Valid keys object expected"); + is(keys.length, 1, "Only one key is expected"); + return c.matchAll(request); + }) + .then(function (matchAllResponses) { + ok(matchAllResponses, "matchAll should succeed"); + is(matchAllResponses.length, 1, "Only one match is expected"); + return c.match(request); + }) + .then(function (matchResponse) { + ok(matchResponse, "match should succeed"); + return caches.match(request); + }) + .then(function (storageMatchResponse) { + ok(storageMatchResponse, "storage match should succeed"); + return caches.match(request, { cacheName: foobar }); + }) + .then(function (storageMatchResponse) { + ok(storageMatchResponse, "storage match with cacheName should succeed"); + var request2 = new Request("https://example.com/hmm?q=snafu" + context); + return c.match(request2, { ignoreSearch: true }); + }) + .then(function (match2Response) { + ok(match2Response, "match should succeed"); + return c.delete(request); + }) + .then(function (deleteResult) { + ok(deleteResult, "delete should succeed"); + return c.keys(); + }) + .then(function (keys) { + ok(keys, "Valid keys object expected"); + is(keys.length, 0, "Zero keys is expected"); + return c.matchAll(request); + }) + .then(function (matchAll2Responses) { + ok(matchAll2Responses, "matchAll should succeed"); + is(matchAll2Responses.length, 0, "Zero matches is expected"); + return caches.has(foobar); + }) + .then(function (hasResult) { + ok(hasResult, "has should succeed"); + return caches.keys(); + }) + .then(function (keys) { + ok(keys, "Valid keys object expected"); + ok(keys.length >= 2, "At least two keys are expected"); + ok(keys.includes(name), "snafu should exist"); + ok( + keys.indexOf(foobar) >= keys.indexOf(name), + "foobar should come after it" + ); + return caches.delete(foobar); + }) + .then(function (deleteResult) { + ok(deleteResult, "delete should succeed"); + return caches.has(foobar); + }) + .then(function (hasMissingCache) { + ok(!hasMissingCache, "has should have a result"); + return caches.delete(name); + }) + .then(function (deleteResult) { + ok(deleteResult, "delete should succeed"); + testDone(); + }); diff --git a/dom/cache/test/mochitest/test_cache_add.html b/dom/cache/test/mochitest/test_cache_add.html new file mode 100644 index 0000000000..e8a141dd64 --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_add.html @@ -0,0 +1,20 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Validate Interfaces Exposed to Workers</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="driver.js"></script> +</head> +<body> +<iframe id="frame"></iframe> +<script class="testbody" type="text/javascript"> + runTests("test_cache_add.js") + .then(function() { + SimpleTest.finish(); + }); +</script> +</body> +</html> diff --git a/dom/cache/test/mochitest/test_cache_add.js b/dom/cache/test/mochitest/test_cache_add.js new file mode 100644 index 0000000000..9f96b985f0 --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_add.js @@ -0,0 +1,75 @@ +/* global context testDone:true */ + +var singleUrl = "./test_cache_add.js"; +var urlList = ["./empty.html", "./frame.html", "./test_cache.js"]; +var cache; +var name = "adder" + context; +caches + .open(name) + .then(function (openCache) { + cache = openCache; + return cache.add("ftp://example.com/invalid" + context); + }) + .catch(function (err) { + is( + err.name, + "TypeError", + "add() should throw TypeError for invalid scheme" + ); + return cache.addAll([ + "https://example.com/valid" + context, + "ftp://example.com/invalid" + context, + ]); + }) + .catch(function (err) { + is( + err.name, + "TypeError", + "addAll() should throw TypeError for invalid scheme" + ); + var promiseList = urlList.map(function (url) { + return cache.match(url); + }); + promiseList.push(cache.match(singleUrl)); + return Promise.all(promiseList); + }) + .then(function (resultList) { + is(urlList.length + 1, resultList.length, "Expected number of results"); + resultList.every(function (result) { + is(undefined, result, "URLs should not already be in the cache"); + }); + return cache.add(singleUrl); + }) + .then(function (result) { + is(undefined, result, "Successful add() should resolve undefined"); + return cache.addAll(urlList); + }) + .then(function (result) { + is(undefined, result, "Successful addAll() should resolve undefined"); + var promiseList = urlList.map(function (url) { + return cache.match(url); + }); + promiseList.push(cache.match(singleUrl)); + return Promise.all(promiseList); + }) + .then(function (resultList) { + is(urlList.length + 1, resultList.length, "Expected number of results"); + resultList.every(function (result) { + ok(!!result, "Responses should now be in cache for each URL."); + }); + return cache.matchAll(); + }) + .then(function (resultList) { + is(urlList.length + 1, resultList.length, "Expected number of results"); + resultList.every(function (result) { + ok(!!result, "Responses should now be in cache for each URL."); + }); + return caches.delete(name); + }) + .then(function () { + testDone(); + }) + .catch(function (err) { + ok(false, "Caught error: " + err); + testDone(); + }); diff --git a/dom/cache/test/mochitest/test_cache_delete.html b/dom/cache/test/mochitest/test_cache_delete.html new file mode 100644 index 0000000000..3ec7a9666c --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_delete.html @@ -0,0 +1,20 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Validate the Cache.delete() method</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="driver.js"></script> +</head> +<body> +<iframe id="frame"></iframe> +<script class="testbody" type="text/javascript"> + runTests("test_cache_delete.js") + .then(function() { + SimpleTest.finish(); + }); +</script> +</body> +</html> diff --git a/dom/cache/test/mochitest/test_cache_delete.js b/dom/cache/test/mochitest/test_cache_delete.js new file mode 100644 index 0000000000..67ded1affa --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_delete.js @@ -0,0 +1,132 @@ +/* global context testDone:true */ + +var name = "delete" + context; + +function setupTest(reqs) { + return new Promise(function (resolve, reject) { + var cache; + caches + .open(name) + .then(function (c) { + cache = c; + return c.addAll(reqs); + }) + .then(function () { + resolve(cache); + }) + .catch(function (err) { + reject(err); + }); + }); +} + +function testBasics() { + var tests = [ + "//mochi.test:8888/?foo" + context, + "//mochi.test:8888/?bar" + context, + ]; + var cache; + return setupTest(tests) + .then(function (c) { + cache = c; + return cache.delete("//mochi.test:8888/?baz"); + }) + .then(function (deleted) { + ok(!deleted, "Deleting a non-existing entry should fail"); + return cache.keys(); + }) + .then(function (keys) { + is(keys.length, 2, "No entries from the cache should be deleted"); + return cache.delete(tests[0]); + }) + .then(function (deleted) { + ok(deleted, "Deleting an existing entry should succeed"); + return cache.keys(); + }) + .then(function (keys) { + is(keys.length, 1, "Only one entry should exist now"); + ok(keys[0].url.includes(tests[1]), "The correct entry must be deleted"); + }); +} + +function testFragment() { + var tests = [ + "//mochi.test:8888/?foo" + context, + "//mochi.test:8888/?bar" + context, + "//mochi.test:8888/?baz" + context + "#fragment", + ]; + var cache; + return setupTest(tests) + .then(function (c) { + cache = c; + return cache.delete(tests[0] + "#fragment"); + }) + .then(function (deleted) { + ok(deleted, "Deleting an existing entry should succeed"); + return cache.keys(); + }) + .then(function (keys) { + is(keys.length, 2, "Only one entry should exist now"); + ok(keys[0].url.includes(tests[1]), "The correct entry must be deleted"); + ok( + keys[1].url.includes(tests[2].replace("#fragment", "")), + "The correct entry must be deleted" + ); + // Now, delete a request that was added with a fragment + return cache.delete("//mochi.test:8888/?baz" + context); + }) + .then(function (deleted) { + ok(deleted, "Deleting an existing entry should succeed"); + return cache.keys(); + }) + .then(function (keys) { + is(keys.length, 1, "Only one entry should exist now"); + ok(keys[0].url.includes(tests[1]), "3The correct entry must be deleted"); + }); +} + +function testInterleaved() { + var tests = [ + "//mochi.test:8888/?foo" + context, + "//mochi.test:8888/?bar" + context, + ]; + var newURL = "//mochi.test:8888/?baz" + context; + var cache; + return setupTest(tests) + .then(function (c) { + cache = c; + // Simultaneously add and delete a request + return Promise.all([cache.delete(newURL), cache.add(newURL)]); + }) + .then(function (result) { + ok(!result[1], "deletion should fail"); + return cache.keys(); + }) + .then(function (keys) { + is(keys.length, 3, "Tree entries should still exist"); + ok(keys[0].url.includes(tests[0]), "The correct entry must be deleted"); + ok(keys[1].url.includes(tests[1]), "The correct entry must be deleted"); + ok( + keys[2].url.includes(newURL), + "The new entry should be correctly inserted" + ); + }); +} + +// Make sure to clean up after each test step. +function step(testPromise) { + return testPromise.then(function () { + caches.delete(name); + }); +} + +step(testBasics()) + .then(function () { + return step(testFragment()); + }) + .then(function () { + return step(testInterleaved()); + }) + .then(function () { + testDone(); + }); diff --git a/dom/cache/test/mochitest/test_cache_https.html b/dom/cache/test/mochitest/test_cache_https.html new file mode 100644 index 0000000000..82a2823379 --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_https.html @@ -0,0 +1,20 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Validate Interfaces Exposed to Workers</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="driver.js"></script> +</head> +<body> +<iframe id="frame"></iframe> +<script class="testbody" type="text/javascript"> + runTests("test_cache_https.js") + .then(function() { + SimpleTest.finish(); + }); +</script> +</body> +</html> diff --git a/dom/cache/test/mochitest/test_cache_https.js b/dom/cache/test/mochitest/test_cache_https.js new file mode 100644 index 0000000000..682938a664 --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_https.js @@ -0,0 +1,36 @@ +/* global context testDone:true */ + +var cache = null; +var name = "https_" + context; +var urlBase = "https://example.com/tests/dom/cache/test/mochitest"; +var url1 = urlBase + "/test_cache.js"; +var url2 = urlBase + "/test_cache_add.js"; + +function addOpaque(c, url) { + return fetch(new Request(url, { mode: "no-cors" })).then(function (response) { + return c.put(url, response); + }); +} + +caches + .open(name) + .then(function (c) { + cache = c; + return Promise.all([addOpaque(cache, url1), addOpaque(cache, url2)]); + }) + .then(function () { + return cache.delete(url1); + }) + .then(function (result) { + ok(result, "Cache entry should be deleted"); + return cache.delete(url2); + }) + .then(function (result) { + ok(result, "Cache entry should be deleted"); + cache = null; + return caches.delete(name); + }) + .then(function (result) { + ok(result, "Cache should be deleted"); + testDone(); + }); diff --git a/dom/cache/test/mochitest/test_cache_keys.html b/dom/cache/test/mochitest/test_cache_keys.html new file mode 100644 index 0000000000..30aa5c9d19 --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_keys.html @@ -0,0 +1,20 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Validate the Cache.keys() method</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="driver.js"></script> +</head> +<body> +<iframe id="frame"></iframe> +<script class="testbody" type="text/javascript"> + runTests("test_cache_keys.js") + .then(function() { + SimpleTest.finish(); + }); +</script> +</body> +</html> diff --git a/dom/cache/test/mochitest/test_cache_keys.js b/dom/cache/test/mochitest/test_cache_keys.js new file mode 100644 index 0000000000..1eda988af2 --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_keys.js @@ -0,0 +1,98 @@ +/* global context testDone:true */ + +var name = "keys" + context; +var c; + +var tests = [ + "//mochi.test:8888/?page" + context, + "//mochi.test:8888/?another" + context, +]; + +caches + .open(name) + .then(function (cache) { + c = cache; + return c.addAll(tests); + }) + .then(function () { + // Add another cache entry using Cache.add + var another = "//mochi.test:8888/?yetanother" + context; + tests.push(another); + return c.add(another); + }) + .then(function () { + // Add another cache entry with URL fragment using Cache.add + var anotherWithFragment = + "//mochi.test:8888/?fragment" + context + "#fragment"; + tests.push(anotherWithFragment); + return c.add(anotherWithFragment); + }) + .then(function () { + return c.keys(); + }) + .then(function (keys) { + is(keys.length, tests.length, "Same number of elements"); + // Verify both the insertion order of the requests and their validity. + keys.forEach(function (r, i) { + ok(r instanceof Request, "Valid request object"); + ok(r.url.includes(tests[i]), "Valid URL"); + }); + // Try searching for just one request + return c.keys(tests[1]); + }) + .then(function (keys) { + is(keys.length, 1, "One match should be found"); + ok(keys[0].url.includes(tests[1]), "Valid URL"); + // Try to see if ignoreSearch works as expected. + return c.keys(new Request("//mochi.test:8888/?foo"), { + ignoreSearch: true, + }); + }) + .then(function (key_arr) { + is(key_arr.length, tests.length, "Same number of elements"); + key_arr.forEach(function (r, i) { + ok(r instanceof Request, "Valid request object"); + ok(r.url.includes(tests[i]), "Valid URL"); + }); + // Try to see if ignoreMethod works as expected + return Promise.all( + ["POST", "PUT", "DELETE", "OPTIONS"].map(function (method) { + var req = new Request(tests[2], { method }); + return c + .keys(req) + .then(function (keys) { + is( + keys.length, + 0, + "No request should be matched without ignoreMethod" + ); + return c.keys(req, { ignoreMethod: true }); + }) + .then(function (keys) { + is(keys.length, 1, "One match should be found"); + ok(keys[0].url.includes(tests[2]), "Valid URL"); + }); + }) + ); + }) + .then(function () { + // But HEAD should be allowed only when ignoreMethod is ture + return c.keys(new Request(tests[0], { method: "HEAD" }), { + ignoreMethod: true, + }); + }) + .then(function (keys) { + is(keys.length, 1, "One match should be found"); + ok(keys[0].url.includes(tests[0]), "Valid URL"); + // Make sure cacheName is ignored. + return c.keys(tests[0], { cacheName: "non-existing-cache" }); + }) + .then(function (keys) { + is(keys.length, 1, "One match should be found"); + ok(keys[0].url.includes(tests[0]), "Valid URL"); + return caches.delete(name); + }) + .then(function (deleted) { + ok(deleted, "The cache should be successfully deleted"); + testDone(); + }); diff --git a/dom/cache/test/mochitest/test_cache_matchAll_request.html b/dom/cache/test/mochitest/test_cache_matchAll_request.html new file mode 100644 index 0000000000..2536f75bb6 --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_matchAll_request.html @@ -0,0 +1,20 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Validate calling matchAll with a Request object</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="driver.js"></script> +</head> +<body> +<iframe id="frame"></iframe> +<script class="testbody" type="text/javascript"> + runTests("test_cache_matchAll_request.js") + .then(function() { + SimpleTest.finish(); + }); +</script> +</body> +</html> diff --git a/dom/cache/test/mochitest/test_cache_matchAll_request.js b/dom/cache/test/mochitest/test_cache_matchAll_request.js new file mode 100644 index 0000000000..615a69b6c6 --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_matchAll_request.js @@ -0,0 +1,273 @@ +/* global context testDone:true */ + +var req1 = new Request("//mochi.test:8888/?1&" + context + "#fragment"); +var req2 = new Request("//mochi.test:8888/?2&" + context); +var req3 = new Request("//mochi.test:8888/?3&" + context); +var requestWithAltQS = new Request("//mochi.test:8888/?queryString"); +var unknownRequest = new Request( + "//mochi.test:8888/non/existing/path?" + context +); +var response1, response3; +var c; +var response1Text, response3Text; +var name = "matchAll-request" + context; + +function checkResponse(r, response, responseText) { + ok(r !== response, "The objects should not be the same"); + is( + r.url, + response.url.replace("#fragment", ""), + "The URLs should be the same" + ); + is(r.status, response.status, "The status codes should be the same"); + is(r.type, response.type, "The response types should be the same"); + is(r.ok, response.ok, "Both responses should have succeeded"); + is( + r.statusText, + response.statusText, + "Both responses should have the same status text" + ); + return r.text().then(function (text) { + // Avoid dumping out the large response text to the log if they're equal. + if (text !== responseText) { + is(text, responseText, "The response body should be correct"); + } + }); +} + +fetch(new Request(req1)) + .then(function (r) { + response1 = r; + return response1.text(); + }) + .then(function (text) { + response1Text = text; + return fetch(new Request(req3)); + }) + .then(function (r) { + response3 = r; + return response3.text(); + }) + .then(function (text) { + response3Text = text; + return testRequest( + req1, + req2, + req3, + unknownRequest, + requestWithAltQS, + req1.url.replace("#fragment", "#other") + ); + }) + .then(function () { + return testRequest( + req1.url, + req2.url, + req3.url, + unknownRequest.url, + requestWithAltQS.url, + req1.url.replace("#fragment", "#other") + ); + }) + .then(function () { + testDone(); + }); + +// The request arguments can either be a URL string, or a Request object. +function testRequest( + request1, + request2, + request3, + unknownReq, + requestWithAlternateQueryString, + requestWithDifferentFragment +) { + return caches + .open(name) + .then(function (cache) { + c = cache; + return c.add(request1); + }) + .then(function () { + return c.add(request3); + }) + .then(function () { + return Promise.all( + ["HEAD", "POST", "PUT", "DELETE", "OPTIONS"].map(function (method) { + var r = new Request(request1, { method }); + return c.add(r).then( + function () { + ok(false, "Promise should be rejected"); + }, + function (err) { + is( + err.name, + "TypeError", + "Adding a request with type '" + method + "' should fail" + ); + } + ); + }) + ); + }) + .then(function () { + return c.matchAll(request1); + }) + .then(function (r) { + is(r.length, 1, "Should only find 1 item"); + return checkResponse(r[0], response1, response1Text); + }) + .then(function () { + return c.matchAll(new Request(request1, { method: "HEAD" })); + }) + .then(function (r) { + is(r.length, 0, "Should be an empty array when match a HEAD request."); + return c.matchAll(new Request(request1, { method: "HEAD" }), { + ignoreMethod: true, + }); + }) + .then(function (r) { + is(r.length, 1, "Should only find 1 item"); + return checkResponse(r[0], response1, response1Text); + }) + .then(function () { + return Promise.all( + ["HEAD", "POST", "PUT", "DELETE", "OPTIONS"].map(function (method) { + var req = new Request(request1, { method }); + return c + .matchAll(req) + .then(function (r) { + is( + r.length, + 0, + "Searching for a request with a non-GET method should not succeed" + ); + return c.matchAll(req, { ignoreMethod: true }); + }) + .then(function (r) { + is(r.length, 1, "Should only find 1 item"); + return checkResponse(r[0], response1, response1Text); + }); + }) + ); + }) + .then(function () { + return c.matchAll(requestWithDifferentFragment); + }) + .then(function (r) { + is(r.length, 1, "Should only find 1 item"); + return checkResponse(r[0], response1, response1Text); + }) + .then(function () { + return c.matchAll(requestWithAlternateQueryString, { + ignoreSearch: true, + }); + }) + .then(function (r) { + is(r.length, 2, "Should find 2 items"); + return Promise.all([ + checkResponse(r[0], response1, response1Text), + checkResponse(r[1], response3, response3Text), + ]); + }) + .then(function () { + return c.matchAll(request3); + }) + .then(function (r) { + is(r.length, 1, "Should only find 1 item"); + return checkResponse(r[0], response3, response3Text); + }) + .then(function () { + return c.matchAll(); + }) + .then(function (r) { + is(r.length, 2, "Should find 2 items"); + return Promise.all([ + checkResponse(r[0], response1, response1Text), + checkResponse(r[1], response3, response3Text), + ]); + }) + .then(function () { + return caches + .match(request1, { cacheName: name + "mambojambo" }) + .then(function () { + is( + typeof r, + "undefined", + "Searching in the wrong cache should resolve to undefined" + ); + return caches.has(name + "mambojambo"); + }) + .then(function (hasCache) { + ok(!hasCache, "The wrong cache should still not exist"); + }); + }) + .then(function () { + return c.matchAll(unknownReq); + }) + .then(function (r) { + is(r.length, 0, "Searching for an unknown request should not succeed"); + return caches.match(unknownReq, { cacheName: name }); + }) + .then(function (r) { + is( + typeof r, + "undefined", + "Searching for an unknown request should not succeed" + ); + // Make sure that cacheName is ignored on Cache + return c.matchAll(request1, { cacheName: name + "mambojambo" }); + }) + .then(function (r) { + is(r.length, 1, "Should only find 1 item"); + return checkResponse(r[0], response1, response1Text); + }) + .then(function () { + return caches.delete(name); + }) + .then(function (success) { + ok(success, "We should be able to delete the cache successfully"); + // Make sure that the cache is still usable after deletion. + return c.matchAll(request1); + }) + .then(function (r) { + is(r.length, 1, "Should only find 1 item"); + return checkResponse(r[0], response1, response1Text); + }) + .then(function () { + return c.matchAll(request3); + }) + .then(function (r) { + is(r.length, 1, "Should only find 1 item"); + return checkResponse(r[0], response3, response3Text); + }) + .then(function () { + return c.matchAll(); + }) + .then(function (r) { + is(r.length, 2, "Should find 2 items"); + return Promise.all([ + checkResponse(r[0], response1, response1Text), + checkResponse(r[1], response3, response3Text), + ]); + }) + .then(function () { + // Now, drop the cache, reopen and verify that we can't find the request any more. + c = null; + return caches.open(name); + }) + .then(function (cache) { + return cache.matchAll(); + }) + .then(function (r) { + is( + r.length, + 0, + "Searching in the cache after deletion should not succeed" + ); + return caches.delete(name); + }) + .then(function (deleted) { + ok(deleted, "The cache should be deleted successfully"); + }); +} diff --git a/dom/cache/test/mochitest/test_cache_match_request.html b/dom/cache/test/mochitest/test_cache_match_request.html new file mode 100644 index 0000000000..2cd6132b11 --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_match_request.html @@ -0,0 +1,20 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Validate calling match with a Request object</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="driver.js"></script> +</head> +<body> +<iframe id="frame"></iframe> +<script class="testbody" type="text/javascript"> + runTests("test_cache_match_request.js") + .then(function() { + SimpleTest.finish(); + }); +</script> +</body> +</html> diff --git a/dom/cache/test/mochitest/test_cache_match_request.js b/dom/cache/test/mochitest/test_cache_match_request.js new file mode 100644 index 0000000000..f7a7001444 --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_match_request.js @@ -0,0 +1,238 @@ +/* global context testDone:true */ + +var req = new Request("//mochi.test:8888/?" + context + "#fragment"); +var requestWithAltQS = new Request("//mochi.test:8888/?queryString"); +var unknownReq = new Request("//mochi.test:8888/non/existing/path?" + context); +var response; +var c; +var responseText; +var name = "match-request" + context; + +function checkResponse(r, expectedBody) { + if (expectedBody === undefined) { + expectedBody = responseText; + } + ok(r !== response, "The objects should not be the same"); + is( + r.url, + response.url.replace("#fragment", ""), + "The URLs should be the same" + ); + is(r.status, response.status, "The status codes should be the same"); + is(r.type, response.type, "The response types should be the same"); + is(r.ok, response.ok, "Both responses should have succeeded"); + is( + r.statusText, + response.statusText, + "Both responses should have the same status text" + ); + return r.text().then(function (text) { + // Avoid dumping out the large response text to the log if they're equal. + if (text !== expectedBody) { + is(text, responseText, "The response body should be correct"); + } + }); +} +fetch(new Request(req)) + .then(function (r) { + response = r; + return response.text(); + }) + .then(function (text) { + responseText = text; + return testRequest( + req, + unknownReq, + requestWithAltQS, + req.url.replace("#fragment", "#other") + ); + }) + .then(function () { + return testRequest( + req.url, + unknownReq.url, + requestWithAltQS.url, + req.url.replace("#fragment", "#other") + ); + }) + .then(function () { + testDone(); + }); +// The request argument can either be a URL string, or a Request object. +function testRequest( + request, + unknownRequest, + requestWithAlternateQueryString, + requestWithDifferentFragment +) { + return caches + .open(name) + .then(function (cache) { + c = cache; + return c.add(request); + }) + .then(function () { + return Promise.all( + ["HEAD", "POST", "PUT", "DELETE", "OPTIONS"].map(function (method) { + var r = new Request(request, { method }); + return c.add(r).then( + function () { + ok(false, "Promise should be rejected"); + }, + function (err) { + is( + err.name, + "TypeError", + "Adding a request with type '" + method + "' should fail" + ); + } + ); + }) + ); + }) + .then(function () { + return c.match(request); + }) + .then(function (r) { + return checkResponse(r); + }) + .then(function () { + return c.match(new Request(request, { method: "HEAD" })); + }) + .then(function (r) { + is( + typeof r, + "undefined", + "Searching for an HEAD request should not succeed" + ); + return c.match(new Request(request, { method: "HEAD" }), { + ignoreMethod: true, + }); + }) + .then(function (r) { + return checkResponse(r); + }) + .then(function () { + return Promise.all( + ["HEAD", "POST", "PUT", "DELETE", "OPTIONS"].map(function (method) { + var req1 = new Request(request, { method }); + return c + .match(req1) + .then(function (r) { + is( + typeof r, + "undefined", + "Searching for a request with a non-GET method should not succeed" + ); + return c.match(req1, { ignoreMethod: true }); + }) + .then(function (r) { + return checkResponse(r); + }); + }) + ); + }) + .then(function () { + return caches.match(request); + }) + .then(function (r) { + return checkResponse(r); + }) + .then(function () { + return caches.match(requestWithDifferentFragment); + }) + .then(function (r) { + return checkResponse(r); + }) + .then(function () { + return caches.match(requestWithAlternateQueryString, { + ignoreSearch: true, + cacheName: name, + }); + }) + .then(function (r) { + return checkResponse(r); + }) + .then(function () { + return caches.match(request, { cacheName: name }); + }) + .then(function (r) { + return checkResponse(r); + }) + .then(function () { + return caches + .match(request, { cacheName: name + "mambojambo" }) + .then(function (result) { + is( + typeof r, + "undefined", + "Searching in the wrong cache should resolve to undefined" + ); + return caches.has(name + "mambojambo"); + }) + .then(function (hasCache) { + ok(!hasCache, "The wrong cache should still not exist"); + }); + }) + .then(function () { + // Make sure that cacheName is ignored on Cache + return c.match(request, { cacheName: name + "mambojambo" }); + }) + .then(function (r) { + return checkResponse(r); + }) + .then(function () { + return c.match(unknownRequest); + }) + .then(function (r) { + is( + typeof r, + "undefined", + "Searching for an unknown request should not succeed" + ); + return caches.match(unknownRequest); + }) + .then(function (r) { + is( + typeof r, + "undefined", + "Searching for an unknown request should not succeed" + ); + return caches.match(unknownRequest, { cacheName: name }); + }) + .then(function (r) { + is( + typeof r, + "undefined", + "Searching for an unknown request should not succeed" + ); + return caches.delete(name); + }) + .then(function (success) { + ok(success, "We should be able to delete the cache successfully"); + // Make sure that the cache is still usable after deletion. + return c.match(request); + }) + .then(function (r) { + return checkResponse(r); + }) + .then(function () { + // Now, drop the cache, reopen and verify that we can't find the request any more. + c = null; + return caches.open(name); + }) + .then(function (cache) { + return cache.match(request); + }) + .then(function (r) { + is( + typeof r, + "undefined", + "Searching in the cache after deletion should not succeed" + ); + return caches.delete(name); + }) + .then(function (deleted) { + ok(deleted, "The cache should be deleted successfully"); + }); +} diff --git a/dom/cache/test/mochitest/test_cache_match_vary.html b/dom/cache/test/mochitest/test_cache_match_vary.html new file mode 100644 index 0000000000..17b7a4a9a5 --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_match_vary.html @@ -0,0 +1,20 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Validate calling match with requests involving the Vary header</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="driver.js"></script> +</head> +<body> +<iframe id="frame"></iframe> +<script class="testbody" type="text/javascript"> + runTests("test_cache_match_vary.js") + .then(function() { + SimpleTest.finish(); + }); +</script> +</body> +</html> diff --git a/dom/cache/test/mochitest/test_cache_match_vary.js b/dom/cache/test/mochitest/test_cache_match_vary.js new file mode 100644 index 0000000000..87e21826db --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_match_vary.js @@ -0,0 +1,545 @@ +/* global context testDone:true */ + +var requestURL = + "//mochi.test:8888/tests/dom/cache/test/mochitest/vary.sjs?" + context; +var name = "match-vary" + context; + +function checkResponse(r, response, responseText) { + ok(r !== response, "The objects should not be the same"); + is( + r.url, + response.url.replace("#fragment", ""), + "The URLs should be the same" + ); + is(r.status, response.status, "The status codes should be the same"); + is(r.type, response.type, "The response types should be the same"); + is(r.ok, response.ok, "Both responses should have succeeded"); + is( + r.statusText, + response.statusText, + "Both responses should have the same status text" + ); + is( + r.headers.get("Vary"), + response.headers.get("Vary"), + "Both responses should have the same Vary header" + ); + return r.text().then(function (text) { + is(text, responseText, "The response body should be correct"); + }); +} + +// Returns a Promise that will be resolved to an object with the following +// properties: +// * cache: A Cache object that contains one entry fetched with headers. +// * response: A Response object which is the result of fetching a request +// with the specified headers. +// * responseText: The body of the above response object. +function setupTest(headers) { + return setupTestMultipleEntries([headers]).then(function (test) { + return { + response: test.response[0], + responseText: test.responseText[0], + cache: test.cache, + }; + }); +} +function setupTestMultipleEntries(headers) { + ok(Array.isArray(headers), "headers should be an array"); + return new Promise(function (resolve, reject) { + var response, responseText, cache; + Promise.all( + headers.map(function (h) { + return fetch(requestURL, { headers: h }); + }) + ) + .then(function (res) { + response = res; + return Promise.all( + response.map(function (r) { + return r.text(); + }) + ); + }) + .then(function (text) { + responseText = text; + return caches.open(name); + }) + .then(function (c) { + cache = c; + return Promise.all( + headers.map(function (h) { + return c.add(new Request(requestURL, { headers: h })); + }) + ); + }) + .then(function () { + resolve({ response, responseText, cache }); + }) + .catch(function (err) { + reject(err); + }); + }); +} + +function testBasics() { + var test; + return setupTest({ WhatToVary: "Custom" }) + .then(function (t) { + test = t; + // Ensure that searching without specifying a Custom header succeeds. + return test.cache.match(requestURL); + }) + .then(function (r) { + return checkResponse(r, test.response, test.responseText); + }) + .then(function () { + // Ensure that searching with a non-matching value for the Custom header fails. + return test.cache.match( + new Request(requestURL, { headers: { Custom: "foo=bar" } }) + ); + }) + .then(function (r) { + is( + typeof r, + "undefined", + "Searching for a request with an unknown Vary header should not succeed" + ); + // Ensure that searching with a non-matching value for the Custom header but with ignoreVary set succeeds. + return test.cache.match( + new Request(requestURL, { headers: { Custom: "foo=bar" } }), + { ignoreVary: true } + ); + }) + .then(function (r) { + return checkResponse(r, test.response, test.responseText); + }); +} + +function testBasicKeys() { + function checkRequest(reqs) { + is(reqs.length, 1, "One request expected"); + ok(reqs[0].url.includes(requestURL), "The correct request expected"); + ok( + reqs[0].headers.get("WhatToVary"), + "Custom", + "The correct request headers expected" + ); + } + var test; + return setupTest({ WhatToVary: "Custom" }) + .then(function (t) { + test = t; + // Ensure that searching without specifying a Custom header succeeds. + return test.cache.keys(requestURL); + }) + .then(function (r) { + return checkRequest(r); + }) + .then(function () { + // Ensure that searching with a non-matching value for the Custom header fails. + return test.cache.keys( + new Request(requestURL, { headers: { Custom: "foo=bar" } }) + ); + }) + .then(function (r) { + is( + r.length, + 0, + "Searching for a request with an unknown Vary header should not succeed" + ); + // Ensure that searching with a non-matching value for the Custom header but with ignoreVary set succeeds. + return test.cache.keys( + new Request(requestURL, { headers: { Custom: "foo=bar" } }), + { ignoreVary: true } + ); + }) + .then(function (r) { + return checkRequest(r); + }); +} + +function testStar() { + function ensurePromiseRejected(promise) { + return promise.then( + function () { + ok(false, "Promise should be rejected"); + }, + function (err) { + is( + err.name, + "TypeError", + "Attempting to store a Response with a Vary:* header must fail" + ); + } + ); + } + return new Promise(function (resolve, reject) { + var cache; + caches.open(name).then(function (c) { + cache = c; + Promise.all([ + ensurePromiseRejected( + cache.add( + new Request(requestURL + "1", { headers: { WhatToVary: "*" } }) + ) + ), + ensurePromiseRejected( + cache.addAll([ + new Request(requestURL + "2", { headers: { WhatToVary: "*" } }), + requestURL + "3", + ]) + ), + ensurePromiseRejected( + fetch( + new Request(requestURL + "4", { headers: { WhatToVary: "*" } }) + ).then(function (response) { + return cache.put(requestURL + "4", response); + }) + ), + ensurePromiseRejected( + cache.add( + new Request(requestURL + "5", { + headers: { WhatToVary: "*,User-Agent" }, + }) + ) + ), + ensurePromiseRejected( + cache.addAll([ + new Request(requestURL + "6", { + headers: { WhatToVary: "*,User-Agent" }, + }), + requestURL + "7", + ]) + ), + ensurePromiseRejected( + fetch( + new Request(requestURL + "8", { + headers: { WhatToVary: "*,User-Agent" }, + }) + ).then(function (response) { + return cache.put(requestURL + "8", response); + }) + ), + ensurePromiseRejected( + cache.add( + new Request(requestURL + "9", { + headers: { WhatToVary: "User-Agent,*" }, + }) + ) + ), + ensurePromiseRejected( + cache.addAll([ + new Request(requestURL + "10", { + headers: { WhatToVary: "User-Agent,*" }, + }), + requestURL + "10", + ]) + ), + ensurePromiseRejected( + fetch( + new Request(requestURL + "11", { + headers: { WhatToVary: "User-Agent,*" }, + }) + ).then(function (response) { + return cache.put(requestURL + "11", response); + }) + ), + ]).then(reject, resolve); + }); + }); +} + +function testMatch() { + var test; + return setupTest({ WhatToVary: "Custom", Custom: "foo=bar" }) + .then(function (t) { + test = t; + // Ensure that searching with a different Custom header fails. + return test.cache.match( + new Request(requestURL, { headers: { Custom: "bar=baz" } }) + ); + }) + .then(function (r) { + is( + typeof r, + "undefined", + "Searching for a request with a non-matching Custom header should not succeed" + ); + // Ensure that searching with the same Custom header succeeds. + return test.cache.match( + new Request(requestURL, { headers: { Custom: "foo=bar" } }) + ); + }) + .then(function (r) { + return checkResponse(r, test.response, test.responseText); + }); +} + +function testInvalidHeaderName() { + var test; + return setupTest({ WhatToVary: "Foo/Bar, Custom-User-Agent" }) + .then(function (t) { + test = t; + // Ensure that searching with a different User-Agent header fails. + return test.cache.match( + new Request(requestURL, { headers: { "Custom-User-Agent": "MyUA" } }) + ); + }) + .then(function (r) { + is( + typeof r, + "undefined", + "Searching for a request with a non-matching Custom-User-Agent header should not succeed" + ); + // Ensure that searching with a different Custom-User-Agent header but with ignoreVary succeeds. + return test.cache.match( + new Request(requestURL, { headers: { "Custom-User-Agent": "MyUA" } }), + { ignoreVary: true } + ); + }) + .then(function (r) { + return checkResponse(r, test.response, test.responseText); + }) + .then(function () { + // Ensure that we do not mistakenly recognize the tokens in the invalid header name. + return test.cache.match( + new Request(requestURL, { headers: { Foo: "foobar" } }) + ); + }) + .then(function (r) { + return checkResponse(r, test.response, test.responseText); + }); +} + +function testMultipleHeaders() { + var test; + return setupTest({ WhatToVary: "Custom-Referer,\tCustom-Accept-Encoding" }) + .then(function (t) { + test = t; + // Ensure that searching with a different Referer header fails. + return test.cache.match( + new Request(requestURL, { + headers: { "Custom-Referer": "https://somesite.com/" }, + }) + ); + }) + .then(function (r) { + is( + typeof r, + "undefined", + "Searching for a request with a non-matching Custom-Referer header should not succeed" + ); + // Ensure that searching with a different Custom-Referer header but with ignoreVary succeeds. + return test.cache.match( + new Request(requestURL, { + headers: { "Custom-Referer": "https://somesite.com/" }, + }), + { ignoreVary: true } + ); + }) + .then(function (r) { + return checkResponse(r, test.response, test.responseText); + }) + .then(function () { + // Ensure that searching with a different Custom-Accept-Encoding header fails. + return test.cache.match( + new Request(requestURL, { + headers: { "Custom-Accept-Encoding": "myencoding" }, + }) + ); + }) + .then(function (r) { + is( + typeof r, + "undefined", + "Searching for a request with a non-matching Custom-Accept-Encoding header should not succeed" + ); + // Ensure that searching with a different Custom-Accept-Encoding header but with ignoreVary succeeds. + return test.cache.match( + new Request(requestURL, { + headers: { "Custom-Accept-Encoding": "myencoding" }, + }), + { ignoreVary: true } + ); + }) + .then(function (r) { + return checkResponse(r, test.response, test.responseText); + }) + .then(function () { + // Ensure that searching with an empty Custom-Referer header succeeds. + return test.cache.match( + new Request(requestURL, { headers: { "Custom-Referer": "" } }) + ); + }) + .then(function (r) { + return checkResponse(r, test.response, test.responseText); + }) + .then(function () { + // Ensure that searching with an empty Custom-Accept-Encoding header succeeds. + return test.cache.match( + new Request(requestURL, { headers: { "Custom-Accept-Encoding": "" } }) + ); + }) + .then(function (r) { + return checkResponse(r, test.response, test.responseText); + }) + .then(function () { + // Ensure that searching with an empty Custom-Referer header but with a different Custom-Accept-Encoding header fails. + return test.cache.match( + new Request(requestURL, { + headers: { + "Custom-Referer": "", + "Custom-Accept-Encoding": "myencoding", + }, + }) + ); + }) + .then(function (r) { + is( + typeof r, + "undefined", + "Searching for a request with a non-matching Custom-Accept-Encoding header should not succeed" + ); + // Ensure that searching with an empty Custom-Referer header but with a different Custom-Accept-Encoding header and ignoreVary succeeds. + return test.cache.match( + new Request(requestURL, { + headers: { + "Custom-Referer": "", + "Custom-Accept-Encoding": "myencoding", + }, + }), + { ignoreVary: true } + ); + }) + .then(function (r) { + return checkResponse(r, test.response, test.responseText); + }); +} + +function testMultipleCacheEntries() { + var test; + return setupTestMultipleEntries([ + { WhatToVary: "Accept-Language", "Accept-Language": "en-US" }, + { WhatToVary: "Accept-Language", "Accept-Language": "en-US, fa-IR" }, + ]) + .then(function (t) { + test = t; + return test.cache.matchAll(); + }) + .then(function (r) { + is(r.length, 2, "Two cache entries should be stored in the DB"); + // Ensure that searching without specifying an Accept-Language header fails. + return test.cache.matchAll(requestURL); + }) + .then(function (r) { + is( + r.length, + 0, + "Searching for a request without specifying an Accept-Language header should not succeed" + ); + // Ensure that searching without specifying an Accept-Language header but with ignoreVary succeeds. + return test.cache.matchAll(requestURL, { ignoreVary: true }); + }) + .then(function (r) { + return Promise.all([ + checkResponse(r[0], test.response[0], test.responseText[0]), + checkResponse(r[1], test.response[1], test.responseText[1]), + ]); + }) + .then(function () { + // Ensure that searching with Accept-Language: en-US succeeds. + return test.cache.matchAll( + new Request(requestURL, { headers: { "Accept-Language": "en-US" } }) + ); + }) + .then(function (r) { + is(r.length, 1, "One cache entry should be found"); + return checkResponse(r[0], test.response[0], test.responseText[0]); + }) + .then(function () { + // Ensure that searching with Accept-Language: en-US,fa-IR succeeds. + return test.cache.matchAll( + new Request(requestURL, { + headers: { "Accept-Language": "en-US, fa-IR" }, + }) + ); + }) + .then(function (r) { + is(r.length, 1, "One cache entry should be found"); + return checkResponse(r[0], test.response[1], test.responseText[1]); + }) + .then(function () { + // Ensure that searching with a valid Accept-Language header but with ignoreVary returns both entries. + return test.cache.matchAll( + new Request(requestURL, { headers: { "Accept-Language": "en-US" } }), + { ignoreVary: true } + ); + }) + .then(function (r) { + return Promise.all([ + checkResponse(r[0], test.response[0], test.responseText[0]), + checkResponse(r[1], test.response[1], test.responseText[1]), + ]); + }) + .then(function () { + // Ensure that searching with Accept-Language: fa-IR fails. + return test.cache.matchAll( + new Request(requestURL, { headers: { "Accept-Language": "fa-IR" } }) + ); + }) + .then(function (r) { + is( + r.length, + 0, + "Searching for a request with a different Accept-Language header should not succeed" + ); + // Ensure that searching with Accept-Language: fa-IR but with ignoreVary should succeed. + return test.cache.matchAll( + new Request(requestURL, { headers: { "Accept-Language": "fa-IR" } }), + { ignoreVary: true } + ); + }) + .then(function (r) { + is(r.length, 2, "Two cache entries should be found"); + return Promise.all([ + checkResponse(r[0], test.response[0], test.responseText[0]), + checkResponse(r[1], test.response[1], test.responseText[1]), + ]); + }); +} + +// Make sure to clean up after each test step. +function step(testPromise) { + return testPromise.then( + function () { + caches.delete(name); + }, + function () { + caches.delete(name); + } + ); +} + +step(testBasics()) + .then(function () { + return step(testBasicKeys()); + }) + .then(function () { + return step(testStar()); + }) + .then(function () { + return step(testMatch()); + }) + .then(function () { + return step(testInvalidHeaderName()); + }) + .then(function () { + return step(testMultipleHeaders()); + }) + .then(function () { + return step(testMultipleCacheEntries()); + }) + .then(function () { + testDone(); + }); diff --git a/dom/cache/test/mochitest/test_cache_orphaned_body.html b/dom/cache/test/mochitest/test_cache_orphaned_body.html new file mode 100644 index 0000000000..337fa075c1 --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_orphaned_body.html @@ -0,0 +1,241 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Test Cache with QuotaManager Restart</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="large_url_list.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +function setupTestIframe() { + return new Promise(function(resolve) { + var iframe = document.createElement("iframe"); + iframe.src = "empty.html"; + iframe.onload = function() { + window.caches = iframe.contentWindow.caches; + resolve(); + }; + document.body.appendChild(iframe); + }); +} + +function clearStorage() { + return new Promise(function(resolve, reject) { + var qms = SpecialPowers.Services.qms; + var principal = SpecialPowers.wrap(document).nodePrincipal; + var request = qms.clearStoragesForPrincipal(principal); + var cb = SpecialPowers.wrapCallback(resolve); + request.callback = cb; + }); +} + +function storageUsage() { + return new Promise(function(resolve, reject) { + var qms = SpecialPowers.Services.qms; + var principal = SpecialPowers.wrap(document).nodePrincipal; + var cb = SpecialPowers.wrapCallback(function(request) { + var result = request.result; + resolve(result.usage); + }); + qms.getUsageForPrincipal(principal, cb); + }); +} + +function groupUsage() { + return new Promise(function(resolve, reject) { + navigator.storage.estimate().then(storageEstimation => { + resolve(storageEstimation.usage, 0); + }); + }); +} + +function workerGroupUsage() { + return new Promise(function(resolve, reject) { + function workerScript() { + navigator.storage.estimate().then(storageEstimation => { + postMessage(storageEstimation.usage); + }); + } + + let url = + URL.createObjectURL(new Blob(["(", workerScript.toString(), ")()"])); + + let worker = new Worker(url); + worker.onmessage = function(e) { + resolve(e.data, 0); + }; + }); +} + +function resetStorage() { + return new Promise(function(resolve, reject) { + var qms = SpecialPowers.Services.qms; + var request = qms.reset(); + var cb = SpecialPowers.wrapCallback(resolve); + request.callback = cb; + }); +} + +function gc() { + return new Promise(function(resolve, reject) { + SpecialPowers.exactGC(resolve); + }); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({ + "set": [["dom.caches.enabled", true], + ["dom.caches.testing.enabled", true], + ["dom.quotaManager.testing", true], + ["dom.storageManager.enabled", true], + ["privacy.partition.always_partition_third_party_non_cookie_storage", false]], +}, async function() { + // Bug 1746646: Make mochitests work with TCP enabled (cookieBehavior = 5) + // Acquire storage access permission here so that the Cache API is avaialable + SpecialPowers.wrap(document).notifyUserGestureActivation(); + await SpecialPowers.addPermission("storageAccessAPI", true, window.location.href); + await SpecialPowers.wrap(document).requestStorageAccess(); + + var name = "orphanedBodyOwner"; + var cache = null; + var response = null; + var initialUsage = 0; + var fullUsage = 0; + var endUsage = 0; + var url = "test_cache_add.js"; + + // start from a fresh origin directory so other tests do not influence our + // results + setupTestIframe().then(function() { + return clearStorage(); + }).then(function() { + return storageUsage(); + }).then(function(usage) { + is(0, usage, "disk usage should be zero to start"); + }) + + // Initialize and populate an initial cache to get the base sqlite pages + // and directory structure allocated. + .then(function() { + return caches.open(name); + }).then(function(c) { + return c.add(url); + }).then(function() { + return gc(); + }).then(function() { + return caches.delete(name); + }).then(function(deleted) { + ok(deleted, "cache should be deleted"); + + // This is a bit superfluous, but its necessary to make sure the Cache is + // fully deleted before we proceed. The deletion actually takes place in + // two async steps. We don't want to resetStorage() until the second step + // has taken place. This extra Cache operation ensure that all the + // runnables have been flushed through the threads, etc. + return caches.has(name); + }) + + // Now measure initial disk usage + .then(function() { + return resetStorage(); + }).then(function() { + return storageUsage(); + }).then(function(usage) { + initialUsage = usage; + }) + + // Now re-populate the Cache object + .then(function() { + return caches.open(name); + }).then(function(c) { + cache = c; + return cache.add(url); + }) + + // Get a reference to the body we've stored in the Cache. + .then(function() { + return cache.match(url); + }).then(function(r) { + response = r; + return cache.delete(url); + }).then(function(result) { + ok(result, "Cache entry should be deleted"); + }) + + // Reset the quota dir while the cache entry is deleted, but still referenced + // from the DOM. This forces the body to be orphaned. + .then(function() { + return resetStorage(); + }).then(function() { + return storageUsage(); + }).then(function(usage) { + fullUsage = usage; + ok(fullUsage > initialUsage, "disk usage should have grown"); + }) + + // Test groupUsage() + .then(function() { + return resetStorage(); + }).then(function() { + return groupUsage(); + }).then(function(usage) { + fullUsage = usage; + ok(fullUsage > initialUsage, "disk group usage should have grown"); + }) + + // Test workerGroupUsage() + .then(function() { + return resetStorage(); + }).then(function() { + return workerGroupUsage(); + }).then(function(usage) { + fullUsage = usage; + ok(fullUsage > initialUsage, "disk group usage on worker should have grown"); + }) + + // Now perform a new Cache operation that will reopen the origin. This + // should clean up the orphaned body. + .then(function() { + return caches.match(url); + }).then(function(r) { + ok(!r, "response should not exist in storage"); + }) + + // Finally, verify orphaned data was cleaned up by re-checking the disk + // usage. Reset the storage first to ensure any WAL transaction files + // are flushed before measuring the usage. + .then(function() { + return resetStorage(); + }).then(function() { + return storageUsage(); + }).then(function(usage) { + endUsage = usage; + dump("### ### initial:" + initialUsage + ", full:" + fullUsage + + ", end:" + endUsage + "\n"); + ok(endUsage < fullUsage, "disk usage should have shrank"); + is(endUsage, initialUsage, "disk usage should return to original"); + }) + + // Verify that the stale, orphaned response cannot be put back into + // the cache. + .then(function() { + ok(!response.bodyUsed, "response body should not be considered used"); + return cache.put(url, response).then(function() { + ok(false, "Should not be able to store stale orphaned body."); + }).catch(function(e) { + is(e.name, "TypeError", "storing a stale orphaned body should throw TypeError"); + }); + }).then(function() { + ok(response.bodyUsed, "attempting to store response should mark body used"); + }) + + .then(function() { + SimpleTest.finish(); + }); +}); +</script> +</body> +</html> diff --git a/dom/cache/test/mochitest/test_cache_orphaned_cache.html b/dom/cache/test/mochitest/test_cache_orphaned_cache.html new file mode 100644 index 0000000000..8f32e83b2a --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_orphaned_cache.html @@ -0,0 +1,171 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Test Cache with QuotaManager Restart</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="large_url_list.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +function setupTestIframe() { + return new Promise(function(resolve) { + var iframe = document.createElement("iframe"); + iframe.src = "empty.html"; + iframe.onload = function() { + window.caches = iframe.contentWindow.caches; + resolve(); + }; + document.body.appendChild(iframe); + }); +} + +function clearStorage() { + return new Promise(function(resolve, reject) { + var qms = SpecialPowers.Services.qms; + var principal = SpecialPowers.wrap(document).nodePrincipal; + var request = qms.clearStoragesForPrincipal(principal); + var cb = SpecialPowers.wrapCallback(resolve); + request.callback = cb; + }); +} + +function storageUsage() { + return new Promise(function(resolve, reject) { + var qms = SpecialPowers.Services.qms; + var principal = SpecialPowers.wrap(document).nodePrincipal; + var cb = SpecialPowers.wrapCallback(function(request) { + var result = request.result; + resolve(result.usage); + }); + qms.getUsageForPrincipal(principal, cb); + }); +} + +function resetStorage() { + return new Promise(function(resolve, reject) { + var qms = SpecialPowers.Services.qms; + var request = qms.reset(); + var cb = SpecialPowers.wrapCallback(resolve); + request.callback = cb; + }); +} + +function gc() { + return new Promise(function(resolve, reject) { + SpecialPowers.exactGC(resolve); + }); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({ + "set": [["dom.caches.enabled", true], + ["dom.caches.testing.enabled", true], + ["dom.quotaManager.testing", true], + ["privacy.partition.always_partition_third_party_non_cookie_storage", false]], +}, async function() { + // Bug 1746646: Make mochitests work with TCP enabled (cookieBehavior = 5) + // Acquire storage access permission here so that the Cache API is avaialable + SpecialPowers.wrap(document).notifyUserGestureActivation(); + await SpecialPowers.addPermission("storageAccessAPI", true, window.location.href); + await SpecialPowers.wrap(document).requestStorageAccess(); + + var name = "toBeOrphaned"; + var cache = null; + var initialUsage = 0; + var fullUsage = 0; + var endUsage = 0; + var url = "test_cache_add.js"; + + // start from a fresh origin directory so other tests do not influence our + // results + setupTestIframe().then(function() { + return clearStorage(); + }).then(function() { + return storageUsage(); + }).then(function(usage) { + is(0, usage, "disk usage should be zero to start"); + }) + + // Initialize and populate an initial cache to get the base sqlite pages + // and directory structure allocated. + .then(function() { + return caches.open(name); + }).then(function(c) { + return c.add(url); + }).then(function() { + return gc(); + }).then(function() { + return caches.delete(name); + }).then(function(deleted) { + ok(deleted, "cache should be deleted"); + + // This is a bit superfluous, but its necessary to make sure the Cache is + // fully deleted before we proceed. The deletion actually takes place in + // two async steps. We don't want to resetStorage() until the second step + // has taken place. This extra Cache operation ensure that all the + // runnables have been flushed through the threads, etc. + return caches.has(name); + }) + + // Now measure initial disk usage + .then(function() { + return resetStorage(); + }).then(function() { + return storageUsage(); + }).then(function(usage) { + initialUsage = usage; + }) + + // Now re-populate the Cache object + .then(function() { + return caches.open(name); + }).then(function(c) { + cache = c; + return cache.add(url); + }).then(function() { + return caches.delete(name); + }).then(function(deleted) { + ok(deleted, "cache should be deleted"); + }) + + // Reset the quota dir while the cache is deleted, but still referenced + // from the DOM. This forces it to be orphaned. + .then(function() { + return resetStorage(); + }).then(function() { + return storageUsage(); + }).then(function(usage) { + fullUsage = usage; + ok(fullUsage > initialUsage, "disk usage should have grown"); + }) + + // Now perform a new Cache operation that will reopen the origin. This + // should clean up the orphaned Cache data. + .then(function() { + return caches.has(name); + }).then(function(result) { + ok(!result, "cache should not exist in storage"); + }) + + // Finally, verify orphaned data was cleaned up by re-checking the disk + // usage. Reset the storage first to ensure any WAL transaction files + // are flushed before measuring the usage. + .then(function() { + return resetStorage(); + }).then(function() { + return storageUsage(); + }).then(function(usage) { + endUsage = usage; + dump("### ### initial:" + initialUsage + ", full:" + fullUsage + + ", end:" + endUsage + "\n"); + ok(endUsage < fullUsage, "disk usage should have shrank"); + is(endUsage, initialUsage, "disk usage should return to original"); + SimpleTest.finish(); + }); +}); +</script> +</body> +</html> diff --git a/dom/cache/test/mochitest/test_cache_overwrite.html b/dom/cache/test/mochitest/test_cache_overwrite.html new file mode 100644 index 0000000000..7d2b9c797a --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_overwrite.html @@ -0,0 +1,20 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Test what happens when you overwrite a cache entry</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="driver.js"></script> +</head> +<body> +<iframe id="frame"></iframe> +<script class="testbody" type="text/javascript"> + runTests("test_cache_overwrite.js") + .then(function() { + SimpleTest.finish(); + }); +</script> +</body> +</html> diff --git a/dom/cache/test/mochitest/test_cache_overwrite.js b/dom/cache/test/mochitest/test_cache_overwrite.js new file mode 100644 index 0000000000..dd6b3c5848 --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_overwrite.js @@ -0,0 +1,71 @@ +/* global context testDone:true */ + +var requestURL = + "//mochi.test:8888/tests/dom/cache/test/mochitest/mirror.sjs?" + context; +var response; +var c; +var responseText; +var name = "match-mirror" + context; + +function checkResponse(r) { + ok(r !== response, "The objects should not be the same"); + is( + r.url, + response.url.replace("#fragment", ""), + "The URLs should be the same" + ); + is(r.status, response.status, "The status codes should be the same"); + is(r.type, response.type, "The response types should be the same"); + is(r.ok, response.ok, "Both responses should have succeeded"); + is( + r.statusText, + response.statusText, + "Both responses should have the same status text" + ); + is( + r.headers.get("Mirrored"), + response.headers.get("Mirrored"), + "Both responses should have the same Mirrored header" + ); + return r.text().then(function (text) { + is(text, responseText, "The response body should be correct"); + }); +} + +fetch(new Request(requestURL, { headers: { Mirror: "bar" } })) + .then(function (r) { + is( + r.headers.get("Mirrored"), + "bar", + "The server should give back the correct header" + ); + response = r; + return response.text(); + }) + .then(function (text) { + responseText = text; + return caches.open(name); + }) + .then(function (cache) { + c = cache; + return c.add(new Request(requestURL, { headers: { Mirror: "foo" } })); + }) + .then(function () { + // Overwrite the request, to replace the entry stored in response_headers + // with a different value. + return c.add(new Request(requestURL, { headers: { Mirror: "bar" } })); + }) + .then(function () { + return c.matchAll(); + }) + .then(function (r) { + is(r.length, 1, "Only one request should be in the cache"); + return checkResponse(r[0]); + }) + .then(function () { + return caches.delete(name); + }) + .then(function (deleted) { + ok(deleted, "The cache should be deleted successfully"); + testDone(); + }); diff --git a/dom/cache/test/mochitest/test_cache_padding.html b/dom/cache/test/mochitest/test_cache_padding.html new file mode 100644 index 0000000000..b05dadb304 --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_padding.html @@ -0,0 +1,203 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Test Cache generate padding size for opaque repsonse</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="large_url_list.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +function setupTestIframe() { + return new Promise(function(resolve) { + var iframe = document.createElement("iframe"); + iframe.src = "empty.html"; + iframe.onload = function() { + window.caches = iframe.contentWindow.caches; + resolve(); + }; + document.body.appendChild(iframe); + }); +} + +function clearStorage() { + return new Promise(function(resolve, reject) { + var qms = SpecialPowers.Services.qms; + var principal = SpecialPowers.wrap(document).nodePrincipal; + var request = qms.clearStoragesForPrincipal(principal); + var cb = SpecialPowers.wrapCallback(resolve); + request.callback = cb; + }); +} + +function resetStorage() { + return new Promise(function(resolve, reject) { + var qms = SpecialPowers.Services.qms; + var request = qms.reset(); + var cb = SpecialPowers.wrapCallback(resolve); + request.callback = cb; + }); +} + +function getStorageUsage(fromMemory) { + return new Promise(function(resolve, reject) { + var qms = SpecialPowers.Services.qms; + var principal = SpecialPowers.wrap(document).nodePrincipal; + var cb = SpecialPowers.wrapCallback(function(request) { + var result = request.result; + resolve(result.usage); + }); + + // Actually, the flag is used to distingulish getting group usage and origin + // usage, but we utilize this to get usage from in-memory and the disk. + // Default value for "fromMemory" is false. + qms.getUsageForPrincipal(principal, cb, !!fromMemory); + }); +} + +async function verifyUsage() { + // Although it returns group usage when passing true, it calculate the usage + // from tracking usage object (in-memory object) in QuotaManager. + let memoryUsage = await getStorageUsage(/* fromMemory */ true); + // This will returns the origin usage by re-calculating usage from directory. + let diskUsage = await getStorageUsage(/* fromMemory */ false); + + is(memoryUsage, diskUsage, + "In-memory usage and disk usage should be the same."); + return memoryUsage; +} + +async function waitForIOToComplete(cache, request) { + info("Wait for IO complete."); + // The following lines ensure we've deleted orphaned body. + // First, wait for cache operation delete the orphaned body. + await cache.match(request); + + // Finally, wait for -wal file finish its job. + return resetStorage(); +} + +function fetchOpaqueResponse(url) { + return fetch(url, { mode: "no-cors" }); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({ + "set": [["dom.caches.enabled", true], + ["dom.caches.testing.enabled", true], + ["dom.quotaManager.testing", true], + ["privacy.partition.always_partition_third_party_non_cookie_storage", false]], +}, async function() { + // This test is mainly to verify we only generate different padding size for + // the opaque response which is comming from netwrok. + // Besides, this test utilizes verifyUsage() to ensure Cache Acions does + // update thier usage/padding size to the QM, does record padding size to + // the directory padding file and does do above two things synchronously. + // So that, opaque response's size is bigger than the normal response's size + // and we always have the same usage bewteen from in-memory and from + // the file-system. + // Note: For the cloned and cached opaque response, the padding size shouldn't + // be changed. Thus, it makes the attacker harder to get the padding size. + + // Bug 1746646: Make mochitests work with TCP enabled (cookieBehavior = 5) + // Acquire storage access permission here so that the Cache API is avaialable + SpecialPowers.wrap(document).notifyUserGestureActivation(); + await SpecialPowers.addPermission("storageAccessAPI", true, window.location.href); + await SpecialPowers.wrap(document).requestStorageAccess(); + + const name = "cachePadding"; + const other_name = "cachePaddingOther"; + const cors_base = "https://example.com/tests/dom/cache/test/mochitest/"; + const url = "test_cache_add.js"; + + await setupTestIframe(); + + info("Stage 1: Clean storage."); + await clearStorage(); + + let cache = await caches.open(name); + await waitForIOToComplete(cache, url); + let usage1 = await verifyUsage(); + + info("Stage 2: Verify opaque responses have padding."); + cache = await caches.open(name); + await cache.add(url); + await waitForIOToComplete(cache, url); + let usage2 = await verifyUsage(); + let sizeForNormalResponse = usage2 - usage1; + + let opaqueResponse = await fetchOpaqueResponse(cors_base + url); + cache = await caches.open(name); + await cache.put(cors_base + url, opaqueResponse.clone()); + await waitForIOToComplete(cache, url); + let usage3 = await verifyUsage(); + let sizeForOpaqueResponse = usage3 - usage2; + ok(sizeForOpaqueResponse > sizeForNormalResponse, + "The opaque response should have larger size than the normal response."); + + info("Stage 3: Verify the cloned response has the same size."); + cache = await caches.open(name); + await cache.put(cors_base + url, opaqueResponse.clone()); + await waitForIOToComplete(cache, url); + let usage4 = await verifyUsage(); + // Since we put the same request and response again, the size should be the + // same (DOM Cache removes the previous cached request and response) + ok(usage4 == usage3, + "We won't generate different padding for cloned response"); + + info("Stage 4: Verify the cached response has the same size."); + cache = await caches.open(name); + opaqueResponse = await cache.match(cors_base + url); + ok(opaqueResponse); + + await cache.put(cors_base + url, opaqueResponse); + await waitForIOToComplete(cache, url); + let usage5 = await verifyUsage(); + ok(usage5 == usage3, + "We won't generate different padding for cached response"); + + info("Stage 5: Verify padding size may changes in different fetch()s."); + let paddingSizeChange = false; + // Since we randomly generate padding size and rounding the overall size up, + // we will probably have the same size. So, fetch it multiple times. + for (let i = 0; i < 10; i++) { + opaqueResponse = await fetchOpaqueResponse(cors_base + url); + cache = await caches.open(name); + await cache.put(cors_base + url, opaqueResponse); + await waitForIOToComplete(cache, url); + let usage6 = await verifyUsage(); + if (usage6 != usage5) { + paddingSizeChange = true; + break; + } + } + ok(paddingSizeChange, + "We should generate different padding size for fetching response"); + + info("Stage 6: Verify the padding is removed once on caches.delete() and " + + "cache.delete()."); + // Add an opauqe response on other cache storage and then delete that storage. + cache = await caches.open(other_name); + opaqueResponse = await fetchOpaqueResponse(cors_base + url); + await cache.put(cors_base + url, opaqueResponse); + await caches.delete(other_name); + await caches.has(other_name); + // Force remove orphaned cached in the next action + await resetStorage(); + + // Delete the opauqe repsonse on current cache storage. + cache = await caches.open(name); + await cache.delete(cors_base + url); + await waitForIOToComplete(cache, url); + let usage7 = await verifyUsage(); + ok(usage7 == usage2, + "The opaque response should be removed by caches.delete() and " + + "cache.delete()"); + + await SimpleTest.finish(); +}); +</script> +</body> +</html> diff --git a/dom/cache/test/mochitest/test_cache_put.html b/dom/cache/test/mochitest/test_cache_put.html new file mode 100644 index 0000000000..19b67f6c55 --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_put.html @@ -0,0 +1,20 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Validate Interfaces Exposed to Workers</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="driver.js"></script> +</head> +<body> +<iframe id="frame"></iframe> +<script class="testbody" type="text/javascript"> + runTests("test_cache_put.js") + .then(function() { + SimpleTest.finish(); + }); +</script> +</body> +</html> diff --git a/dom/cache/test/mochitest/test_cache_put.js b/dom/cache/test/mochitest/test_cache_put.js new file mode 100644 index 0000000000..a9229a46da --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_put.js @@ -0,0 +1,72 @@ +/* global context testDone:true */ + +var url = "test_cache.js"; +var cache; +var fetchResponse; +Promise.all([fetch(url), caches.open("putter" + context)]) + .then(function (results) { + fetchResponse = results[0]; + cache = results[1]; + return cache.put(url, fetchResponse.clone()); + }) + .then(function (result) { + is(undefined, result, "Successful put() should resolve undefined"); + return cache.match(url); + }) + .then(function (response) { + ok(response, "match() should find resppnse that was previously put()"); + ok( + response.url.endsWith(url), + "matched response should match original url" + ); + return Promise.all([fetchResponse.text(), response.text()]); + }) + .then(function (results) { + // suppress large assert spam unless it's relevent + if (results[0] !== results[1]) { + is(results[0], results[1], "stored response body should match original"); + } + + // Now, try to overwrite the request with a different response object. + return cache.put(url, new Response("overwritten")); + }) + .then(function () { + return cache.matchAll(url); + }) + .then(function (result) { + is(result.length, 1, "Only one entry should exist"); + return result[0].text(); + }) + .then(function (body) { + is( + body, + "overwritten", + "The cache entry should be successfully overwritten" + ); + + // Now, try to write a URL with a fragment + return cache.put(url + "#fragment", new Response("more overwritten")); + }) + .then(function () { + return cache.matchAll(url + "#differentFragment"); + }) + .then(function (result) { + is(result.length, 1, "Only one entry should exist"); + return result[0].text(); + }) + .then(function (body) { + is( + body, + "more overwritten", + "The cache entry should be successfully overwritten" + ); + + // TODO: Verify that trying to store a response with an error raises a TypeError + // when bug 1147178 is fixed. + + return caches.delete("putter" + context); + }) + .then(function (deleted) { + ok(deleted, "The cache should be deleted successfully"); + testDone(); + }); diff --git a/dom/cache/test/mochitest/test_cache_put_reorder.html b/dom/cache/test/mochitest/test_cache_put_reorder.html new file mode 100644 index 0000000000..f9b360f137 --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_put_reorder.html @@ -0,0 +1,20 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Ensure that using Cache.put to overwrite an entry will change its insertion order</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="driver.js"></script> +</head> +<body> +<iframe id="frame"></iframe> +<script class="testbody" type="text/javascript"> + runTests("test_cache_put_reorder.js") + .then(function() { + SimpleTest.finish(); + }); +</script> +</body> +</html> diff --git a/dom/cache/test/mochitest/test_cache_put_reorder.js b/dom/cache/test/mochitest/test_cache_put_reorder.js new file mode 100644 index 0000000000..c5875c2c9d --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_put_reorder.js @@ -0,0 +1,47 @@ +/* global context testDone:true */ + +var name = "putreorder" + context; +var c; + +var reqs = [ + "//mochi.test:8888/?foo" + context, + "//mochi.test:8888/?bar" + context, + "//mochi.test:8888/?baz" + context, +]; + +caches + .open(name) + .then(function (cache) { + c = cache; + return c.addAll(reqs); + }) + .then(function () { + return c.put(reqs[1], new Response("overwritten")); + }) + .then(function () { + return c.keys(); + }) + .then(function (keys) { + is(keys.length, 3, "Correct number of entries expected"); + ok(keys[0].url.includes(reqs[0]), "The first entry should be untouched"); + ok( + keys[2].url.includes(reqs[1]), + "The second entry should be moved to the end" + ); + ok( + keys[1].url.includes(reqs[2]), + "The third entry should now be the second one" + ); + return c.match(reqs[1]); + }) + .then(function (r) { + return r.text(); + }) + .then(function (body) { + is(body, "overwritten", "The body should be overwritten"); + return caches.delete(name); + }) + .then(function (deleted) { + ok(deleted, "The cache should be deleted successfully"); + testDone(); + }); diff --git a/dom/cache/test/mochitest/test_cache_redirect.html b/dom/cache/test/mochitest/test_cache_redirect.html new file mode 100644 index 0000000000..50d040141e --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_redirect.html @@ -0,0 +1,20 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Validate Cache storage of redirect responses</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="driver.js"></script> +</head> +<body> +<iframe id="frame"></iframe> +<script class="testbody" type="text/javascript"> + runTests("test_cache_redirect.js") + .then(function() { + SimpleTest.finish(); + }); +</script> +</body> +</html> diff --git a/dom/cache/test/mochitest/test_cache_redirect.js b/dom/cache/test/mochitest/test_cache_redirect.js new file mode 100644 index 0000000000..7d62970b7b --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_redirect.js @@ -0,0 +1,20 @@ +/* global context testDone:true */ + +let cache; +let url = "foo.html"; +let redirectURL = "https://example.com/foo-bar.html"; +caches + .open("redirect-" + context) + .then(c => { + cache = c; + var response = Response.redirect(redirectURL); + is(response.headers.get("Location"), redirectURL); + return cache.put(url, response); + }) + .then(_ => { + return cache.match(url); + }) + .then(response => { + is(response.headers.get("Location"), redirectURL); + testDone(); + }); diff --git a/dom/cache/test/mochitest/test_cache_requestCache.html b/dom/cache/test/mochitest/test_cache_requestCache.html new file mode 100644 index 0000000000..8f991ec2d3 --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_requestCache.html @@ -0,0 +1,20 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Validate the Cache.keys() method</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="driver.js"></script> +</head> +<body> +<iframe id="frame"></iframe> +<script class="testbody" type="text/javascript"> + runTests("test_cache_requestCache.js") + .then(function() { + SimpleTest.finish(); + }); +</script> +</body> +</html> diff --git a/dom/cache/test/mochitest/test_cache_requestCache.js b/dom/cache/test/mochitest/test_cache_requestCache.js new file mode 100644 index 0000000000..a6ae2a3418 --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_requestCache.js @@ -0,0 +1,35 @@ +/* global context testDone:true */ + +var name = "requestCache" + context; +var c; + +var reqWithoutCache = new Request("//mochi.test:8888/?noCache" + context); +var reqWithCache = new Request("//mochi.test:8888/?withCache" + context, { + cache: "force-cache", +}); + +// Sanity check +is(reqWithoutCache.cache, "default", "Correct default value"); +is(reqWithCache.cache, "force-cache", "Correct value set by the ctor"); + +caches + .open(name) + .then(function (cache) { + c = cache; + return c.addAll([reqWithoutCache, reqWithCache]); + }) + .then(function () { + return c.keys(); + }) + .then(function (keys) { + is(keys.length, 2, "Correct number of requests"); + is(keys[0].url, reqWithoutCache.url, "Correct URL"); + is(keys[0].cache, reqWithoutCache.cache, "Correct cache attribute"); + is(keys[1].url, reqWithCache.url, "Correct URL"); + is(keys[1].cache, reqWithCache.cache, "Correct cache attribute"); + return caches.delete(name); + }) + .then(function (deleted) { + ok(deleted, "The cache should be successfully deleted"); + testDone(); + }); diff --git a/dom/cache/test/mochitest/test_cache_restart.html b/dom/cache/test/mochitest/test_cache_restart.html new file mode 100644 index 0000000000..b122ba5e3c --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_restart.html @@ -0,0 +1,70 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Test Cache with QuotaManager Restart</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +function setupTestIframe() { + return new Promise(function(resolve) { + var iframe = document.createElement("iframe"); + iframe.src = "empty.html"; + iframe.onload = function() { + window.caches = iframe.contentWindow.caches; + resolve(); + }; + document.body.appendChild(iframe); + }); +} + +function resetStorage() { + return new Promise(function(resolve, reject) { + var qms = SpecialPowers.Services.qms; + var request = qms.reset(); + var cb = SpecialPowers.wrapCallback(resolve); + request.callback = cb; + }); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({ + "set": [["dom.caches.enabled", true], + ["dom.caches.testing.enabled", true], + ["dom.quotaManager.testing", true]], +}, function() { + var name = "foo"; + var url = "./test_cache_add.js"; + var cache; + setupTestIframe().then(function() { + return caches.open(name); + }).then(function(c) { + cache = c; + return cache.add(url); + }).then(function() { + return resetStorage(); + }).then(function() { + return cache.match(url).then(function(resp) { + ok(false, "old cache reference should not work after reset"); + }).catch(function(err) { + ok(true, "old cache reference should not work after reset"); + }); + }).then(function() { + return caches.open(name); + }).then(function(c) { + cache = c; + return cache.match(url); + }).then(function(resp) { + ok(!!resp, "cache should work after QM reset"); + return caches.delete(name); + }).then(function(success) { + ok(success, "cache should be deleted"); + SimpleTest.finish(); + }); +}); +</script> +</body> +</html> diff --git a/dom/cache/test/mochitest/test_cache_shrink.html b/dom/cache/test/mochitest/test_cache_shrink.html new file mode 100644 index 0000000000..387d180fef --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_shrink.html @@ -0,0 +1,139 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Test Cache with QuotaManager Restart</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="large_url_list.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +function setupTestIframe() { + return new Promise(function(resolve) { + var iframe = document.createElement("iframe"); + iframe.src = "empty.html"; + iframe.onload = function() { + window.caches = iframe.contentWindow.caches; + resolve(); + }; + document.body.appendChild(iframe); + }); +} + +function clearStorage() { + return new Promise(function(resolve, reject) { + var qms = SpecialPowers.Services.qms; + var principal = SpecialPowers.wrap(document).nodePrincipal; + var request = qms.clearStoragesForPrincipal(principal); + var cb = SpecialPowers.wrapCallback(resolve); + request.callback = cb; + }); +} + +function storageUsage() { + return new Promise(function(resolve, reject) { + var qms = SpecialPowers.Services.qms; + var principal = SpecialPowers.wrap(document).nodePrincipal; + var cb = SpecialPowers.wrapCallback(function(request) { + var result = request.result; + resolve(result.usage); + }); + qms.getUsageForPrincipal(principal, cb); + }); +} + +function resetStorage() { + return new Promise(function(resolve, reject) { + var qms = SpecialPowers.Services.qms; + var request = qms.reset(); + var cb = SpecialPowers.wrapCallback(resolve); + request.callback = cb; + }); +} + +function gc() { + return new Promise(function(resolve, reject) { + SpecialPowers.exactGC(resolve); + }); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({ + "set": [["dom.caches.enabled", true], + ["dom.caches.testing.enabled", true], + ["dom.quotaManager.testing", true], + ["privacy.partition.always_partition_third_party_non_cookie_storage", false]], +}, async function() { + // Bug 1746646: Make mochitests work with TCP enabled (cookieBehavior = 5) + // Acquire storage access permission here so that the Cache API is avaialable + SpecialPowers.wrap(document).notifyUserGestureActivation(); + await SpecialPowers.addPermission("storageAccessAPI", true, window.location.href); + await SpecialPowers.wrap(document).requestStorageAccess(); + + var name = "foo"; + var cache = null; + var initialUsage = 0; + var fullUsage = 0; + var endUsage = 0; + // start from a fresh origin directory so other tests do not influence our + // results + setupTestIframe().then(function() { + return clearStorage(); + }).then(function() { + return storageUsage(); + }).then(function(usage) { + is(0, usage, "disk usage should be zero to start"); + return caches.open(name); + }).then(function(c) { + cache = c; + return storageUsage(); + }).then(function(usage) { + initialUsage = usage; + return Promise.all(largeUrlList.map(function(url) { + return cache.put(new Request(url), new Response()); + })); + }).then(function() { + return cache.keys(); + }).then(function(keyList) { + is(keyList.length, largeUrlList.length, "Large URL list is stored in cache"); + cache = null; + // Ensure the Cache DOM object is gone before proceeding. If its alive + // it will keep the related entries on-disk as well. + return gc(); + }).then(function() { + // reset the quota manager storage to ensure the DB connection is flushed + return resetStorage(); + }).then(function() { + return storageUsage(); + }).then(function(usage) { + fullUsage = usage; + ok(fullUsage > initialUsage, "disk usage should have grown"); + return caches.delete(name); + }).then(function(result) { + ok(result, "cache should be deleted"); + // This is a bit superfluous, but its necessary to make sure the Cache is + // fully deleted before we proceed. The deletion actually takes place in + // two async steps. We don't want to resetStorage() until the second step + // has taken place. This extra Cache operation ensure that all the + // runnables have been flushed through the threads, etc. + return caches.has(name); + }).then(function(result) { + ok(!result, "cache should not exist in storage"); + // reset the quota manager storage to ensure the DB connection is flushed + return resetStorage(); + }).then(function() { + return storageUsage(); + }).then(function(usage) { + endUsage = usage; + dump("### ### initial:" + initialUsage + ", full:" + fullUsage + + ", end:" + endUsage + "\n"); + ok(endUsage < (fullUsage / 2), "disk usage should have shrank significantly"); + ok(endUsage > initialUsage, "disk usage should not shrink back to orig size"); + SimpleTest.finish(); + }); +}); +</script> +</body> +</html> diff --git a/dom/cache/test/mochitest/test_cache_tons_of_fd.html b/dom/cache/test/mochitest/test_cache_tons_of_fd.html new file mode 100644 index 0000000000..59d24769f6 --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_tons_of_fd.html @@ -0,0 +1,118 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Test cache to create tons of fds</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="driver.js"></script> +</head> +<body> +<script class="testbody" type="text/javascript"> + function setupTestIframe() { + return new Promise(function(resolve) { + var iframe = document.createElement("iframe"); + iframe.src = "empty.html"; + iframe.onload = function() { + window.caches = iframe.contentWindow.caches; + resolve(); + }; + document.body.appendChild(iframe); + }); +} + +function clearStorage() { + return new Promise(function(resolve, reject) { + var qms = SpecialPowers.Services.qms; + var principal = SpecialPowers.wrap(document).nodePrincipal; + var request = qms.clearStoragesForPrincipal(principal); + var cb = SpecialPowers.wrapCallback(resolve); + request.callback = cb; + }); +} + +async function testCreateTonsOfFD() { + const number_of_fd = 5120; + const name = "cacheTonsOfFD"; + const url = "foo.com"; + const body = "This is a body"; + + info("Stage A: Cached a Request/Response pairs"); + let cache = await caches.open(name); + let request = new Request(url); + let response = new Response(body); + await cache.put(request, response); + + info("Stage B: Read the cached response mutliple times"); + let promise_array = []; + for (let i = 0; i < number_of_fd; ++i) { + let promise = cache.match(request); + promise_array.push(promise); + } + let cached_response_array = []; + try { + cached_response_array = await Promise.all(promise_array); + } catch (e) { + throw new Error("Fail to open tons of files with error: " + e); + } + + if (cached_response_array.length != number_of_fd) { + throw new Error("Fail to cache.match the cached responses"); + } + + info("Stage C: Consume the cached body"); + for (let i = 0; i < number_of_fd; ++i) { + if (!cached_response_array[i]) { + // Reduce the checking message. + throw new Error("The cached response doesn't exist"); + } + + let bodyText = ""; + try { + bodyText = await cached_response_array[i].text(); + } catch (e) { + throw new Error("Fail to consume the cached response's body with error: " + e); + } + + if (bodyText != body) { + // Reduce the checking message. + throw new Error("The cached body doeen't be the same as original one"); + } + } + + ok(true, "Doesn't crash or timeout"); + return Promise.resolve(); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({ + "set": [["dom.caches.enabled", true], + ["dom.caches.testing.enabled", true], + ["dom.quotaManager.testing", true], + ["privacy.partition.always_partition_third_party_non_cookie_storage", false]], +}, async function() { + // Bug 1746646: Make mochitests work with TCP enabled (cookieBehavior = 5) + // Acquire storage access permission here so that the Cache API is avaialable + SpecialPowers.wrap(document).notifyUserGestureActivation(); + await SpecialPowers.addPermission("storageAccessAPI", true, window.location.href); + await SpecialPowers.wrap(document).requestStorageAccess(); + + await setupTestIframe(); + + info("Stage 1: Clean storage."); + await clearStorage(); + + info("Stage 2: Verify open lots of files at the same time doesn't crash " + + "the browser"); + try { + await testCreateTonsOfFD(); + } catch (e) { + ok(false, e); + } + + await SimpleTest.finish(); +}); +</script> +</body> +</html> diff --git a/dom/cache/test/mochitest/test_cache_untrusted.html b/dom/cache/test/mochitest/test_cache_untrusted.html new file mode 100644 index 0000000000..e835c3076b --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_untrusted.html @@ -0,0 +1,32 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Test Cache in untrusted context</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="large_url_list.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +function setupTestIframe() { + return new Promise(function(resolve) { + var iframe = document.createElement("iframe"); + iframe.src = "empty.html"; + iframe.onload = function() { + window.caches = iframe.contentWindow.caches; + resolve(); + }; + document.body.appendChild(iframe); + }); +} + +SimpleTest.waitForExplicitFinish(); +setupTestIframe().then(function() { + is(typeof window.caches, "undefined", "caches should not be usable in untrusted http origin"); + SimpleTest.finish(); +}); +</script> +</body> +</html> diff --git a/dom/cache/test/mochitest/test_cache_updateUsage.html b/dom/cache/test/mochitest/test_cache_updateUsage.html new file mode 100644 index 0000000000..5abc080f14 --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_updateUsage.html @@ -0,0 +1,189 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Test Cache update its usage to QuotaManager</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="large_url_list.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> +function setupTestIframe() { + return new Promise(function(resolve) { + var iframe = document.createElement("iframe"); + iframe.src = "empty.html"; + iframe.onload = function() { + window.caches = iframe.contentWindow.caches; + resolve(); + }; + document.body.appendChild(iframe); + }); +} + +function clearStorage() { + return new Promise(function(resolve, reject) { + var qms = SpecialPowers.Services.qms; + var principal = SpecialPowers.wrap(document).nodePrincipal; + var request = qms.clearStoragesForPrincipal(principal); + var cb = SpecialPowers.wrapCallback(resolve); + request.callback = cb; + }); +} + +function resetStorage() { + return new Promise(function(resolve, reject) { + var qms = SpecialPowers.Services.qms; + var request = qms.reset(); + var cb = SpecialPowers.wrapCallback(resolve); + request.callback = cb; + }); +} + +function getStorageUsage(fromMemory) { + return new Promise(function(resolve, reject) { + var qms = SpecialPowers.Services.qms; + var principal = SpecialPowers.wrap(document).nodePrincipal; + var cb = SpecialPowers.wrapCallback(function(request) { + var result = request.result; + resolve(result.usage); + }); + + // Actually, the flag is used to distingulish getting group usage and origin + // usage, but we utilize this to get usage from in-memory and the disk. + // Default value for "fromMemory" is false. + qms.getUsageForPrincipal(principal, cb, !!fromMemory); + }); +} + +async function verifyUsage() { + // Although it returns group usage when passing true, it calculate the usage + // from tracking usage object (in-memory object) in QuotaManager. + let memoryUsage = await getStorageUsage(/* fromMemory */ true); + // This will returns the origin usage by re-calculating usage from directory. + let diskUsage = await getStorageUsage(/* fromMemory */ false); + + is(memoryUsage, diskUsage, + "In-memory usage and disk usage should be the same."); + return memoryUsage; +} + +async function waitForIOToComplete(cache, request) { + info("Wait for IO complete."); + // The following lines ensure we've deleted orphaned body. + // First, wait for cache operation delete the orphaned body. + await cache.match(request); + + // Finally, wait for -wal file finish its job. + return resetStorage(); +} + +SimpleTest.waitForExplicitFinish(); +SpecialPowers.pushPrefEnv({ + "set": [["dom.caches.enabled", true], + ["dom.caches.testing.enabled", true], + ["dom.quotaManager.testing", true], + ["privacy.partition.always_partition_third_party_non_cookie_storage", false]], +}, async function() { + // Bug 1746646: Make mochitests work with TCP enabled (cookieBehavior = 5) + // Acquire storage access permission here so that the Cache API is avaialable + SpecialPowers.wrap(document).notifyUserGestureActivation(); + await SpecialPowers.addPermission("storageAccessAPI", true, window.location.href); + await SpecialPowers.wrap(document).requestStorageAccess(); + + const name = "cacheUpdateUsage"; + const url = "test_cache_add.js"; + const other_url = "test_cache_put.js"; + + // This test mainly ensure DOM Cache updates its usage to QuotaManager when + // executing an operation which creates/deletes files. To do this, we verify + // usage by calling getUsageFromPrincipal twice with different flag(aGroup). + // The reason is we get group usage by collecting in-memory data, and getting + // origin usage by collecting storage usage from files. + + await setupTestIframe(); + + info("Stage 1: Clean storage."); + await clearStorage(); + await verifyUsage(); + + info("Stage 2: Verify CacheStorage."); + info("Stage 2.1: Verify caches.open."); + await caches.open(name); + await verifyUsage(); + + info("Stage 2.2: Verify caches.delete."); + var deleted = await caches.delete(name); + ok(deleted, "Cache storage should be deleted"); + // Reference from test_cache_orphanced_body.html. It ensures that all + // the runnables have been flushed through the threads. + await caches.has(name); + await resetStorage(); + await verifyUsage(); + + info("Stage 3: Verify Cache."); + let cache = await caches.open(name); + info("Stage 3.1: Verify cache.addAll."); + await cache.addAll([url, other_url]); + await verifyUsage(); + info("Stage 3.1.1: Delete all cached requests."); + await cache.delete(url); + await cache.delete(other_url); + await waitForIOToComplete(cache, other_url); + let emptyUsage1 = await verifyUsage(); + + info("Stage 3.2: Verify cache.add."); + cache = await caches.open(name); + await cache.add(url); + await verifyUsage(); + info("Stage 3.2.1: Delete cache."); + await cache.delete(url); + await waitForIOToComplete(cache, url); + let emptyUsage2 = await verifyUsage(); + + info("Stage 3.3: Verify cache.put."); + cache = await caches.open(name); + let response = await fetch(other_url); + await cache.put(other_url, response); + await verifyUsage(); + info("Stage 3.3.1: Delete cache."); + await cache.delete(other_url); + await waitForIOToComplete(cache, other_url); + let emptyUsage3 = await verifyUsage(); + + // Adding same requests twice will make Cache create morgue file twice, and + // then delete the previous one. + info("Stage 4: Add same request twice to make removing a morgue file."); + cache = await caches.open(name); + info("Stage 4.1: First cache.add."); + await cache.add(url); + await verifyUsage(); + + info("Stage 4.2: Second cache.add."); + await cache.add(url); + + // Since we trigger the action to delete orphaned body, we need to wait for + // the action is done. + await waitForIOToComplete(cache, url); + await verifyUsage(); + cache = await caches.open(name); + info("Stage 4.2.1: cache.delete."); + await cache.delete(url); + await waitForIOToComplete(cache, url); + let emptyUsage4 = await verifyUsage(); + + info("Stage 5: Clean caches."); + await caches.delete(name); + + info("Stage 6: Final Check."); + ok(emptyUsage1 == emptyUsage2 && + emptyUsage1 == emptyUsage3 && + emptyUsage1 == emptyUsage4, + "Empty usages should be the same"); + + await SimpleTest.finish(); +}); +</script> +</body> +</html> diff --git a/dom/cache/test/mochitest/test_cache_worker_gc.html b/dom/cache/test/mochitest/test_cache_worker_gc.html new file mode 100644 index 0000000000..798bafaf9b --- /dev/null +++ b/dom/cache/test/mochitest/test_cache_worker_gc.html @@ -0,0 +1,58 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Test CacheStorage does not prevent worker GC</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="driver.js"></script> +</head> +<body> +<iframe id="frame"></iframe> +<script class="testbody" type="text/javascript"> +function startWorker() { + return new Promise(resolve => { + var w = new Worker("idle_worker.js"); + w.addEventListener("message", function onMessage(evt) { + if (evt.data === "LOADED") { + w.removeEventListener("message", onMessage); + resolve(w); + } + }); + }); +} + +function gc() { + return new Promise(resolve => { + SpecialPowers.exactGC(resolve); + }); +} + +SimpleTest.waitForExplicitFinish(); + +async function test() { + let w = await startWorker(); + let weakWorker = SpecialPowers.Cu.getWeakReference(w); + ok(weakWorker, "worker supports weak reference"); + w = null; + await gc(); + await gc(); + ok(!weakWorker.get(), "worker weak reference should be garbage collected"); + SimpleTest.finish(); +} +// Bug 1746646: Make mochitests work with TCP enabled (cookieBehavior = 5) +// Acquire storage access permission here so that the Cache API is avaialable +SpecialPowers.wrap(document).notifyUserGestureActivation(); +SpecialPowers.addPermission("storageAccessAPI", true, window.location.href).then(() => { + SpecialPowers.wrap(document).requestStorageAccess().then(() => { + SpecialPowers.pushPrefEnv({ + set: [["privacy.partition.always_partition_third_party_non_cookie_storage", false]], + }).then(() => { + test(); + }); + }); +}); +</script> +</body> +</html> diff --git a/dom/cache/test/mochitest/test_caches.html b/dom/cache/test/mochitest/test_caches.html new file mode 100644 index 0000000000..63e43bc139 --- /dev/null +++ b/dom/cache/test/mochitest/test_caches.html @@ -0,0 +1,22 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Test the CacheStorage API</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + <script type="text/javascript" src="driver.js"></script> +</head> +<body> +<iframe id="frame"></iframe> +<script class="testbody" type="text/javascript"> + // These tests can only be run in sequential mode because we need to be able + // to rely on the global state of the CacheStorage at all times. + runTests("test_caches.js", "sequential") + .then(function() { + SimpleTest.finish(); + }); +</script> +</body> +</html> diff --git a/dom/cache/test/mochitest/test_caches.js b/dom/cache/test/mochitest/test_caches.js new file mode 100644 index 0000000000..c1d3abaff1 --- /dev/null +++ b/dom/cache/test/mochitest/test_caches.js @@ -0,0 +1,177 @@ +/* global context testDone:true */ + +function arraysHaveSameContent(arr1, arr2) { + if (arr1.length != arr2.length) { + return false; + } + return arr1.every(function (value, index) { + return arr2[index] === value; + }); +} + +function testHas() { + var name = "caches-has" + context; + return caches + .has(name) + .then(function (has) { + ok(!has, name + " should not exist yet"); + return caches.open(name); + }) + .then(function (c) { + return caches.has(name); + }) + .then(function (has) { + ok(has, name + " should now exist"); + return caches.delete(name); + }) + .then(function (deleted) { + ok(deleted, "The deletion should finish successfully"); + return caches.has(name); + }) + .then(function (has) { + ok(!has, name + " should not exist any more"); + }); +} + +function testKeys() { + var names = [ + // The names here are intentionally unsorted, to ensure the insertion order + // and make sure we don't confuse it with an alphabetically sorted list. + "caches-keys4" + context, + "caches-keys0" + context, + "caches-keys1" + context, + "caches-keys3" + context, + ]; + return caches + .keys() + .then(function (keys) { + is(keys.length, 0, "No keys should exist yet"); + return Promise.all( + names.map(function (name) { + return caches.open(name); + }) + ); + }) + .then(function () { + return caches.keys(); + }) + .then(function (keys) { + ok( + arraysHaveSameContent(keys, names), + "Keys must match in insertion order" + ); + return Promise.all( + names.map(function (name) { + return caches.delete(name); + }) + ); + }) + .then(function (deleted) { + ok( + arraysHaveSameContent(deleted, [true, true, true, true]), + "All deletions must succeed" + ); + return caches.keys(); + }) + .then(function (keys) { + is(keys.length, 0, "No keys should exist any more"); + }); +} + +function testMatchAcrossCaches() { + var tests = [ + // The names here are intentionally unsorted, to ensure the insertion order + // and make sure we don't confuse it with an alphabetically sorted list. + { + name: "caches-xmatch5" + context, + request: "//mochi.test:8888/?5" + context, + }, + { + name: "caches-xmatch2" + context, + request: + "//mochi.test:8888/tests/dom/cache/test/mochitest/test_caches.js?2" + + context, + }, + { + name: "caches-xmatch4" + context, + request: "//mochi.test:8888/?4" + context, + }, + ]; + return Promise.all( + tests.map(function (test) { + return caches.open(test.name).then(function (c) { + return c.add(test.request); + }); + }) + ) + .then(function () { + return caches.match("//mochi.test:8888/?5" + context, { + ignoreSearch: true, + }); + }) + .then(function (match) { + ok(match.url.indexOf("?5") > 0, "Match should come from the first cache"); + return caches.delete("caches-xmatch2" + context); // This should not change anything! + }) + .then(function (deleted) { + ok(deleted, "Deletion should finish successfully"); + return caches.match("//mochi.test:8888/?" + context, { + ignoreSearch: true, + }); + }) + .then(function (match) { + ok( + match.url.indexOf("?5") > 0, + "Match should still come from the first cache" + ); + return caches.delete("caches-xmatch5" + context); // This should eliminate the first match! + }) + .then(function (deleted) { + ok(deleted, "Deletion should finish successfully"); + return caches.match("//mochi.test:8888/?" + context, { + ignoreSearch: true, + }); + }) + .then(function (match) { + ok(match.url.indexOf("?4") > 0, "Match should come from the third cache"); + return caches.delete("caches-xmatch4" + context); // Game over! + }) + .then(function (deleted) { + ok(deleted, "Deletion should finish successfully"); + return caches.match("//mochi.test:8888/?" + context, { + ignoreSearch: true, + }); + }) + .then(function (match) { + is(typeof match, "undefined", "No matches should be found"); + }); +} + +function testDelete() { + return caches + .delete("delete" + context) + .then(function (deleted) { + ok(!deleted, "Attempting to delete a non-existing cache should fail"); + return caches.open("delete" + context); + }) + .then(function () { + return caches.delete("delete" + context); + }) + .then(function (deleted) { + ok(deleted, "Delete should now succeed"); + }); +} + +testHas() + .then(function () { + return testKeys(); + }) + .then(function () { + return testMatchAcrossCaches(); + }) + .then(function () { + return testDelete(); + }) + .then(function () { + testDone(); + }); diff --git a/dom/cache/test/mochitest/test_chrome_constructor.html b/dom/cache/test/mochitest/test_chrome_constructor.html new file mode 100644 index 0000000000..fbce2d0162 --- /dev/null +++ b/dom/cache/test/mochitest/test_chrome_constructor.html @@ -0,0 +1,44 @@ +<!-- Any copyright is dedicated to the Public Domain. + - http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE HTML> +<html> +<head> + <title>Validate Interfaces Exposed to Workers</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<script class="testbody" type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + + SpecialPowers.pushPrefEnv({ + "set": [["dom.caches.enabled", true], + ["dom.caches.testing.enabled", true]], + }, function() { + // attach to a different origin's CacheStorage + var url = "https://example.com/"; + var storage = SpecialPowers.createChromeCache("content", url); + + // verify we can use the other origin's CacheStorage as normal + var req = new Request("https://example.com/index.html"); + var res = new Response("hello world"); + var cache; + storage.open("foo").then(function(c) { + cache = c; + ok(cache, "storage should create cache"); + return cache.put(req, res.clone()); + }).then(function() { + return cache.match(req); + }).then(function(foundResponse) { + return Promise.all([res.text(), foundResponse.text()]); + }).then(function(results) { + is(results[0], results[1], "cache should contain response"); + return storage.delete("foo"); + }).then(function(deleted) { + ok(deleted, "storage should delete cache"); + SimpleTest.finish(); + }); + }); +</script> +</body> +</html> diff --git a/dom/cache/test/mochitest/vary.sjs b/dom/cache/test/mochitest/vary.sjs new file mode 100644 index 0000000000..6659ac852b --- /dev/null +++ b/dom/cache/test/mochitest/vary.sjs @@ -0,0 +1,9 @@ +function handleRequest(request, response) { + response.setStatusLine(request.httpVersion, 200, "OK"); + var header = "no WhatToVary header"; + if (request.hasHeader("WhatToVary")) { + header = request.getHeader("WhatToVary"); + response.setHeader("Vary", header); + } + response.write(header); +} diff --git a/dom/cache/test/mochitest/worker_driver.js b/dom/cache/test/mochitest/worker_driver.js new file mode 100644 index 0000000000..03535c9839 --- /dev/null +++ b/dom/cache/test/mochitest/worker_driver.js @@ -0,0 +1,81 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ +// +// Utility script for writing worker tests. In your main document do: +// +// <script type="text/javascript" src="worker_driver.js"></script> +// <script type="text/javascript"> +// workerTestExec('myWorkerTestCase.js') +// </script> +// +// This will then spawn a worker, define some utility functions, and then +// execute the code in myWorkerTestCase.js. You can then use these +// functions in your worker-side test: +// +// ok() - like the SimpleTest assert +// is() - like the SimpleTest assert +// workerTestDone() - like SimpleTest.finish() indicating the test is complete +// +// There are also some functions for requesting information that requires +// SpecialPowers or other main-thread-only resources: +// +// workerTestGetPrefs() - request an array of prefs value from the main thread +// workerTestGetPermissions() - request an array permissions from the MT +// workerTestGetVersion() - request the current version string from the MT +// workerTestGetUserAgent() - request the user agent string from the MT +// +// For an example see test_worker_interfaces.html and test_worker_interfaces.js. + +function workerTestExec(script) { + return new Promise(function (resolve, reject) { + var worker = new Worker("worker_wrapper.js"); + worker.onmessage = function (event) { + is( + event.data.context, + "Worker", + "Correct context for messages received on the worker" + ); + if (event.data.type == "finish") { + worker.terminate(); + SpecialPowers.forceGC(); + resolve(); + } else if (event.data.type == "status") { + ok(event.data.status, event.data.context + ": " + event.data.msg); + } else if (event.data.type == "getPrefs") { + let result = {}; + event.data.prefs.forEach(function (pref) { + result[pref] = SpecialPowers.Services.prefs.getBoolPref(pref); + }); + worker.postMessage({ + type: "returnPrefs", + prefs: event.data.prefs, + result, + }); + } else if (event.data.type == "getPermissions") { + let result = {}; + event.data.permissions.forEach(function (permission) { + result[permission] = SpecialPowers.hasPermission( + permission, + window.document + ); + }); + worker.postMessage({ + type: "returnPermissions", + permissions: event.data.permissions, + result, + }); + } else if (event.data.type == "getUserAgent") { + worker.postMessage({ + type: "returnUserAgent", + result: navigator.userAgent, + }); + } + }; + + worker.onerror = function (event) { + reject("Worker had an error: " + event.data); + }; + + worker.postMessage({ script }); + }); +} diff --git a/dom/cache/test/mochitest/worker_wrapper.js b/dom/cache/test/mochitest/worker_wrapper.js new file mode 100644 index 0000000000..e18015b220 --- /dev/null +++ b/dom/cache/test/mochitest/worker_wrapper.js @@ -0,0 +1,141 @@ +/* eslint-env worker */ + +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ +// +// ServiceWorker equivalent of worker_wrapper.js. + +var client; +var context; + +function ok(a, msg) { + client.postMessage({ + type: "status", + status: !!a, + msg: a + ": " + msg, + context, + }); +} + +function is(a, b, msg) { + client.postMessage({ + type: "status", + status: a === b, + msg: a + " === " + b + ": " + msg, + context, + }); +} + +function workerTestArrayEquals(a, b) { + if (!Array.isArray(a) || !Array.isArray(b) || a.length != b.length) { + return false; + } + for (var i = 0, n = a.length; i < n; ++i) { + if (a[i] !== b[i]) { + return false; + } + } + return true; +} + +function testDone() { + client.postMessage({ type: "finish", context }); +} + +function workerTestGetPrefs(prefs, cb) { + addEventListener("message", function workerTestGetPrefsCB(e) { + if ( + e.data.type != "returnPrefs" || + !workerTestArrayEquals(prefs, e.data.prefs) + ) { + return; + } + removeEventListener("message", workerTestGetPrefsCB); + cb(e.data.result); + }); + client.postMessage({ + type: "getPrefs", + context, + prefs, + }); +} + +function workerTestGetPermissions(permissions, cb) { + addEventListener("message", function workerTestGetPermissionsCB(e) { + if ( + e.data.type != "returnPermissions" || + !workerTestArrayEquals(permissions, e.data.permissions) + ) { + return; + } + removeEventListener("message", workerTestGetPermissionsCB); + cb(e.data.result); + }); + client.postMessage({ + type: "getPermissions", + context, + permissions, + }); +} + +function workerTestGetUserAgent(cb) { + addEventListener("message", function workerTestGetUserAgentCB(e) { + if (e.data.type !== "returnUserAgent") { + return; + } + removeEventListener("message", workerTestGetUserAgentCB); + cb(e.data.result); + }); + client.postMessage({ + context, + type: "getUserAgent", + }); +} + +var completeInstall = null; + +addEventListener("message", function workerWrapperOnMessage(e) { + removeEventListener("message", workerWrapperOnMessage); + var data = e.data; + function runScript() { + try { + importScripts(data.script); + } catch (err) { + client.postMessage({ + type: "status", + status: false, + context, + msg: + "worker failed to import " + data.script + "; error: " + err.message, + }); + } + } + if ("ServiceWorker" in self) { + self.clients + .matchAll({ includeUncontrolled: true }) + .then(function (clients) { + for (var i = 0; i < clients.length; ++i) { + if (clients[i].url.indexOf("message_receiver.html") > -1) { + client = clients[i]; + break; + } + } + if (!client) { + dump( + "We couldn't find the message_receiver window, the test will fail\n" + ); + } + context = "ServiceWorker"; + runScript(); + completeInstall(); + }); + } else { + client = self; + context = "Worker"; + runScript(); + } +}); + +addEventListener("install", e => { + e.waitUntil(new Promise(resolve => (completeInstall = resolve))); +}); diff --git a/dom/cache/test/xpcshell/bug1425146_profile.zip b/dom/cache/test/xpcshell/bug1425146_profile.zip Binary files differnew file mode 100644 index 0000000000..fc57bc1043 --- /dev/null +++ b/dom/cache/test/xpcshell/bug1425146_profile.zip diff --git a/dom/cache/test/xpcshell/head.js b/dom/cache/test/xpcshell/head.js new file mode 100644 index 0000000000..58717f0f68 --- /dev/null +++ b/dom/cache/test/xpcshell/head.js @@ -0,0 +1,208 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + * + * All images in schema_15_profile.zip are from https://github.com/mdn/sw-test/ + * and are CC licensed by https://www.flickr.com/photos/legofenris/. + */ + +// testSteps is expected to be defined by the file including this file. +/* global testSteps */ + +const NS_APP_USER_PROFILE_50_DIR = "ProfD"; +const osWindowsName = "WINNT"; +const pathDelimiter = "/"; + +const persistentPersistence = "persistent"; +const defaultPersistence = "default"; + +const storageDirName = "storage"; +const persistentPersistenceDirName = "permanent"; +const defaultPersistenceDirName = "default"; + +function cacheClientDirName() { + return "cache"; +} + +// services required be initialized in order to run CacheStorage +var ss = Cc["@mozilla.org/storage/service;1"].createInstance( + Ci.mozIStorageService +); +var sts = Cc["@mozilla.org/network/stream-transport-service;1"].getService( + Ci.nsIStreamTransportService +); +var hash = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash); + +class RequestError extends Error { + constructor(resultCode, resultName) { + super(`Request failed (code: ${resultCode}, name: ${resultName})`); + this.name = "RequestError"; + this.resultCode = resultCode; + this.resultName = resultName; + } +} + +add_setup(function () { + do_get_profile(); + + enableTesting(); + + // Expose Cache and Fetch symbols on the global + Cu.importGlobalProperties(["caches", "fetch"]); + + registerCleanupFunction(resetTesting); +}); + +function enableTesting() { + Services.prefs.setBoolPref("dom.simpleDB.enabled", true); + Services.prefs.setBoolPref("dom.quotaManager.testing", true); +} + +function resetTesting() { + Services.prefs.clearUserPref("dom.quotaManager.testing"); + Services.prefs.clearUserPref("dom.simpleDB.enabled"); +} + +function initStorage() { + return Services.qms.init(); +} + +function initTemporaryStorage() { + return Services.qms.initTemporaryStorage(); +} + +function initPersistentOrigin(principal) { + return Services.qms.initializePersistentOrigin(principal); +} + +function initTemporaryOrigin(principal) { + return Services.qms.initializeTemporaryOrigin("default", principal); +} + +function clearOrigin(principal, persistence) { + let request = Services.qms.clearStoragesForPrincipal(principal, persistence); + + return request; +} + +function reset() { + return Services.qms.reset(); +} + +async function requestFinished(request) { + await new Promise(function (resolve) { + request.callback = function () { + resolve(); + }; + }); + + if (request.resultCode !== Cr.NS_OK) { + throw new RequestError(request.resultCode, request.resultName); + } + + return request.result; +} + +// Extract a zip file into the profile +function create_test_profile(zipFileName) { + var directoryService = Services.dirsvc; + + var profileDir = directoryService.get(NS_APP_USER_PROFILE_50_DIR, Ci.nsIFile); + var currentDir = directoryService.get("CurWorkD", Ci.nsIFile); + + var packageFile = currentDir.clone(); + packageFile.append(zipFileName); + + var zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].createInstance( + Ci.nsIZipReader + ); + zipReader.open(packageFile); + + var entryNames = Array.from(zipReader.findEntries(null)); + entryNames.sort(); + + for (var entryName of entryNames) { + var zipentry = zipReader.getEntry(entryName); + + var file = profileDir.clone(); + entryName.split(pathDelimiter).forEach(function (part) { + file.append(part); + }); + + if (zipentry.isDirectory) { + file.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8)); + } else { + var istream = zipReader.getInputStream(entryName); + + var ostream = Cc[ + "@mozilla.org/network/file-output-stream;1" + ].createInstance(Ci.nsIFileOutputStream); + ostream.init(file, -1, parseInt("0644", 8), 0); + + var bostream = Cc[ + "@mozilla.org/network/buffered-output-stream;1" + ].createInstance(Ci.nsIBufferedOutputStream); + bostream.init(ostream, 32 * 1024); + + bostream.writeFrom(istream, istream.available()); + + istream.close(); + bostream.close(); + } + } + + zipReader.close(); +} + +function getCacheDir() { + return getRelativeFile( + `${storageDirName}/${defaultPersistenceDirName}/chrome/${cacheClientDirName()}` + ); +} + +function getPrincipal(url, attrs) { + let uri = Services.io.newURI(url); + if (!attrs) { + attrs = {}; + } + return Services.scriptSecurityManager.createContentPrincipal(uri, attrs); +} + +function getDefaultPrincipal() { + return getPrincipal("http://example.com"); +} + +function getRelativeFile(relativePath) { + let file = Services.dirsvc + .get(NS_APP_USER_PROFILE_50_DIR, Ci.nsIFile) + .clone(); + + if (Services.appinfo.OS === osWindowsName) { + let winFile = file.QueryInterface(Ci.nsILocalFileWin); + winFile.useDOSDevicePathSyntax = true; + } + + relativePath.split(pathDelimiter).forEach(function (component) { + if (component == "..") { + file = file.parent; + } else { + file.append(component); + } + }); + + return file; +} + +function getSimpleDatabase(principal, persistence) { + let connection = Cc["@mozilla.org/dom/sdb-connection;1"].createInstance( + Ci.nsISDBConnection + ); + + if (!principal) { + principal = getDefaultPrincipal(); + } + + connection.init(principal, persistence); + + return connection; +} diff --git a/dom/cache/test/xpcshell/make_profile.js b/dom/cache/test/xpcshell/make_profile.js new file mode 100644 index 0000000000..b7b1a9042b --- /dev/null +++ b/dom/cache/test/xpcshell/make_profile.js @@ -0,0 +1,137 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + * + * All images in schema_15_profile.zip are from https://github.com/mdn/sw-test/ + * and are CC licensed by https://www.flickr.com/photos/legofenris/. + */ + +// Enumerate the directory tree and store results in entryList as +// +// { path: 'a/b/c', file: <nsIFile> } +// +// The algorithm starts with the first entry already in entryList. +function enumerate_tree(entryList) { + for (var index = 0; index < entryList.length; ++index) { + var path = entryList[index].path; + var file = entryList[index].file; + + if (file.isDirectory()) { + var dirList = file.directoryEntries; + while (dirList.hasMoreElements()) { + var dirFile = dirList.nextFile; + entryList.push({ path: path + "/" + dirFile.leafName, file: dirFile }); + } + } + } +} + +function zip_profile(zipFile, profileDir) { + var zipWriter = Cc["@mozilla.org/zipwriter;1"].createInstance( + Ci.nsIZipWriter + ); + zipWriter.open(zipFile, 0x04 | 0x08 | 0x20); + + var root = profileDir.clone(); + root.append("storage"); + root.append("default"); + root.append("chrome"); + + var entryList = [{ path: "storage/default/chrome", file: root }]; + enumerate_tree(entryList); + + entryList.forEach(function (entry) { + if (entry.file.isDirectory()) { + zipWriter.addEntryDirectory( + entry.path, + entry.file.lastModifiedTime, + false + ); + } else { + var istream = Cc[ + "@mozilla.org/network/file-input-stream;1" + ].createInstance(Ci.nsIFileInputStream); + istream.init(entry.file, -1, -1, 0); + zipWriter.addEntryStream( + entry.path, + entry.file.lastModifiedTime, + Ci.nsIZipWriter.COMPRESSION_DEFAULT, + istream, + false + ); + istream.close(); + } + }); + + zipWriter.close(); +} + +function exactGC() { + return new Promise(function (resolve) { + var count = 0; + function doPreciseGCandCC() { + function scheduleGCCallback() { + Cu.forceCC(); + + if (++count < 2) { + doPreciseGCandCC(); + } else { + resolve(); + } + } + Cu.schedulePreciseGC(scheduleGCCallback); + } + doPreciseGCandCC(); + }); +} + +function resetQuotaManager() { + return new Promise(function (resolve) { + var prefService = Services.prefs; + + // enable quota manager testing mode + var pref = "dom.quotaManager.testing"; + prefService.getBranch(null).setBoolPref(pref, true); + + var request = Services.qms.reset(); + request.callback = resolve; + + // disable quota manager testing mode + // prefService.getBranch(null).setBoolPref(pref, false); + }); +} + +function run_test() { + do_test_pending(); + do_get_profile(); + + var directoryService = Services.dirsvc; + var profileDir = directoryService.get("ProfD", Ci.nsIFile); + var currentDir = directoryService.get("CurWorkD", Ci.nsIFile); + + var zipFile = currentDir.clone(); + zipFile.append("new_profile.zip"); + if (zipFile.exists()) { + zipFile.remove(false); + } + ok(!zipFile.exists()); + + caches + .open("xpcshell-test") + .then(function (c) { + var request = new Request("http://example.com/index.html"); + var response = new Response("hello world"); + return c.put(request, response); + }) + .then(exactGC) + .then(resetQuotaManager) + .then(function () { + zip_profile(zipFile, profileDir); + dump("### ### created zip at: " + zipFile.path + "\n"); + do_test_finished(); + }) + .catch(function (e) { + do_test_finished(); + ok(false, e); + }); +} diff --git a/dom/cache/test/xpcshell/schema_15_profile.zip b/dom/cache/test/xpcshell/schema_15_profile.zip Binary files differnew file mode 100644 index 0000000000..32cc8f2eeb --- /dev/null +++ b/dom/cache/test/xpcshell/schema_15_profile.zip diff --git a/dom/cache/test/xpcshell/schema_25_profile.zip b/dom/cache/test/xpcshell/schema_25_profile.zip Binary files differnew file mode 100644 index 0000000000..626aa8f625 --- /dev/null +++ b/dom/cache/test/xpcshell/schema_25_profile.zip diff --git a/dom/cache/test/xpcshell/test_bug1425146.js b/dom/cache/test/xpcshell/test_bug1425146.js new file mode 100644 index 0000000000..d678bb3745 --- /dev/null +++ b/dom/cache/test/xpcshell/test_bug1425146.js @@ -0,0 +1,51 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This test is mainly to verify that we are able to recover from a situation +// in which the padding file and the padding column are missing during origin +// initialization. This was originally reported in bug 1425146 comment 39. +// The situation can be described as follows: +// 1. A profile is used in FF57. The storage version is 2.1. There's no cache +// storage for http://www.mozilla.org +// 2. The same profile is used in FF56. The storage version is still 2.1 which +// doesn't prevent storage system from working since minor upgrades are +// backwards-compatible. The cache storage for http://www.mozilla.org is +// created with schema version 25 (without any padding stuff). +// 3. The profile is used in FF57 again. Zero padding files for existing cache +// storages are not created because storage is already at version 2.1. +// Storage is being initialized and a missing padding file triggers padding +// size computation from the cache database with schema version 25. Since +// the computation happens before any real DOM cache operation, the database +// is not upgraded to schema version 26, so the padding column is missing. + +add_task(async function testSteps() { + // The profile contains one cache storage, a script for cache creation and + // the storage database: + // - storage/default/http+++www.mozilla.org/cache + // - create_cache.js + // - storage.sqlite + // The file create_cache.js in the package was run locally, specifically it + // was temporarily added to xpcshell.ini and then executed: + // mach xpcshell-test --interactive dom/cache/test/xpcshell/create_cache.js + // Note: it must be executed in FF56 and it only creates the directory + // "storage/default/chrome/cache" and the file "storage.sqlite". To make it + // become the profile in the test, additional manual steps are needed. + // 1. Create "http+++www.mozilla.org" folder under the ""storage/default". + // 2. Copy the "cache" folder under the "storage/default/chrome" to + // "storage/default/http+++www.mozilla.org". + // 3. Remove the folder "storage/default/chrome" + // 4. Remove the folder "storage/temporary". + // 5. Add "create_cache.js". + // 6. Replace the "storage.sqlite" created by FF56 (storage v2.0) with the + // "storage.sqlite" created by FF57 (storage v2.1) + create_test_profile("bug1425146_profile.zip"); + + try { + await caches.open("test"); + ok(true, "Should not have thrown"); + } catch (ex) { + ok(false, "Should not have thrown"); + } +}); diff --git a/dom/cache/test/xpcshell/test_empty_directories.js b/dom/cache/test/xpcshell/test_empty_directories.js new file mode 100644 index 0000000000..2e43ba67bd --- /dev/null +++ b/dom/cache/test/xpcshell/test_empty_directories.js @@ -0,0 +1,85 @@ +/** + * This test is mainly to verify cache won't leave emptry directory. + */ + +function resetStorage() { + return new Promise(function (resolve, reject) { + var qms = Services.qms; + var request = qms.reset(); + request.callback = resolve; + }); +} + +async function setUpEnv() { + Services.prefs.setBoolPref("dom.quotaManager.testing", true); + + // We need this for generating the basic profile path + create_test_profile("schema_25_profile.zip"); + + // Trigger storage upgrade + await caches.open("test"); + const cacheDir = getCacheDir(); + let morgueDir = cacheDir.clone(); + morgueDir.append("morgue"); + + // clean the cache directoy + for (let dir of morgueDir.directoryEntries) { + for (let file of dir.directoryEntries) { + file.remove(false); + } + } + + await resetStorage(); +} + +// This function ensure the directory with file shouldn't have been deleted and +// ensure the directory without file should've been deleted. +function verifyResult() { + const cacheDir = getCacheDir(); + let morgueDir = cacheDir.clone(); + morgueDir.append("morgue"); + + let foundEmpty = false; + for (let dir of morgueDir.directoryEntries) { + let empty = true; + // eslint-disable-next-line no-unused-vars + for (let file of dir.directoryEntries) { + empty = false; + } + + foundEmpty = foundEmpty || empty; + } + return !foundEmpty; +} + +add_task(async function testSteps() { + const url = "https://www.mozilla.org"; + + info("Setting up environment"); + + await setUpEnv(); + + info("Test 0 - InitOrigin shouldn't leave an empty directoy"); + + let cache = await caches.open("test"); + let response = await cache.match(url); + ok(!!response, "Upgrade from 25 to 26 do succeed"); + ok(verifyResult(), "InitOrigin should clean all empty directories"); + + info("Test 1 - DeleteOrphanedBodyFiles shouldn't leave an empty directoy"); + + await cache.put(url, response.clone()); + // eslint-disable-next-line no-unused-vars + let r = await cache.match(url); + await cache.delete(url); + await resetStorage(); + + cache = await caches.open("test"); + + // Extra operation to ensure the deletion is completed + await cache.match(url); + + ok(verifyResult(), "Empty directory should be removed"); + + await caches.delete("test"); +}); diff --git a/dom/cache/test/xpcshell/test_migration.js b/dom/cache/test/xpcshell/test_migration.js new file mode 100644 index 0000000000..5a6637eecc --- /dev/null +++ b/dom/cache/test/xpcshell/test_migration.js @@ -0,0 +1,65 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + * + * All images in schema_15_profile.zip are from https://github.com/mdn/sw-test/ + * and are CC licensed by https://www.flickr.com/photos/legofenris/. + */ + +add_task(async function testSteps() { + create_test_profile("schema_15_profile.zip"); + + const cache = await caches.open("xpcshell-test"); + ok(cache, "cache exists"); + + const requestList = await cache.keys(); + + ok(requestList.length, "should have at least one request in cache"); + for (const request of requestList) { + ok(request, "each request in list should be non-null"); + ok( + request.redirect === "follow", + 'request.redirect should default to "follow"' + ); + ok( + request.cache === "default", + 'request.cache should have been updated to "default"' + request.cache + ); + ok( + request.mode === "navigate", + 'request.mode should have been updated to "navigate"' + ); + ok( + request.referrerPolicy === "no-referrer-when-downgrade", + 'request.referrerPolicy should have been updated to "no-referrer-when-downgrade"' + ); + } + + const responseList = await Promise.all( + requestList.map(function (request) { + return cache.match(request); + }) + ); + + ok(responseList.length, "should have at least one response in cache"); + for (const response of responseList) { + ok(response, "each response should be non-null"); + // reponse.url is a empty string in current test file. It should test for + // not being a empty string once thet test file is updated. + ok( + typeof response.url === "string", + "each response.url should be a string" + ); + // reponse.redirected may be changed once test file is updated. It should + // be false since current reponse.url is a empty string. + ok( + response.redirected === false, + "each response.redirected should be false" + ); + Assert.equal( + response.headers.get("Content-Type"), + "text/plain;charset=UTF-8", + "the response should have the correct header" + ); + } +}); diff --git a/dom/cache/test/xpcshell/test_originInit.js b/dom/cache/test/xpcshell/test_originInit.js new file mode 100644 index 0000000000..395cba8a0f --- /dev/null +++ b/dom/cache/test/xpcshell/test_originInit.js @@ -0,0 +1,249 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +add_task(async function testSteps() { + // ToDo: Replace storage and default with a getter function once we expose the + // filenames of them to a IDL file. + const basePath = `${storageDirName}/${defaultPersistenceDirName}/`; + const principal = getPrincipal("https://example.com"); + const originDirName = "https+++example.com"; + + // ToDo: Replace caches.sqlite with a getter function once we expose the + // filename of it to a IDL file. + const cachesDatabase = getRelativeFile( + `${basePath}/${originDirName}/${cacheClientDirName()}/caches.sqlite` + ); + // ToDo: Replace .padding with a getter function once we expose the filename + // of it to a IDL file. + const paddingFile = getRelativeFile( + `${basePath}/${originDirName}/${cacheClientDirName()}/.padding` + ); + // ToDo: Replace .padding-tmp with a getter function once we expose the + // filename of it to a IDL file. + const paddingTempFile = getRelativeFile( + `${basePath}/${originDirName}/${cacheClientDirName()}/.padding-tmp` + ); + // ToDo: Replace morgue with a getter function once we expose the + // filename of it to a IDL file. + const morgueDir = getRelativeFile( + `${basePath}/${originDirName}/${cacheClientDirName()}/morgue` + ); + + const persistentCacheDir = getRelativeFile( + `${storageDirName}/${persistentPersistenceDirName}/${originDirName}/` + + `${cacheClientDirName()}` + ); + + async function createNormalCacheOrigin() { + async function sandboxScript() { + const cache = await caches.open("myCache"); + const request = new Request("https://example.com/index.html"); + const response = new Response("hello world"); + await cache.put(request, response); + } + + const sandbox = new Cu.Sandbox(principal, { + wantGlobalProperties: ["caches", "fetch"], + }); + + const promise = new Promise(function (resolve, reject) { + sandbox.resolve = resolve; + sandbox.reject = reject; + }); + + Cu.evalInSandbox( + sandboxScript.toSource() + " sandboxScript().then(resolve, reject);", + sandbox + ); + await promise; + + let request = reset(); + await requestFinished(request); + } + + async function createPersistentTestOrigin() { + let database = getSimpleDatabase(principal, "persistent"); + + let request = database.open("data"); + await requestFinished(request); + + request = reset(); + await requestFinished(request); + } + + function removeFile(file) { + file.remove(false); + } + + function removeDir(dir) { + dir.remove(true); + } + + function createEmptyFile(file) { + file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + } + + function createEmptyDirectory(dir) { + dir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o0755); + } + + async function initTestOrigin() { + let request = initStorage(); + await requestFinished(request); + + request = initTemporaryStorage(); + await requestFinished(request); + + request = initTemporaryOrigin(principal); + await requestFinished(request); + } + + async function initPersistentTestOrigin() { + let request = initStorage(); + await requestFinished(request); + + request = initPersistentOrigin(principal); + await requestFinished(request); + } + + function checkFiles( + expectCachesDatabase, + expectPaddingFile, + expectTempPaddingFile, + expectMorgueDir + ) { + let exists = cachesDatabase.exists(); + if (expectCachesDatabase) { + ok(exists, "caches.sqlite does exist"); + } else { + ok(!exists, "caches.sqlite doesn't exist"); + } + + exists = paddingFile.exists(); + if (expectPaddingFile) { + ok(exists, ".padding does exist"); + } else { + ok(!exists, ".padding doesn't exist"); + } + + exists = paddingTempFile.exists(); + if (expectTempPaddingFile) { + ok(exists, ".padding-tmp does exist"); + } else { + ok(!exists, ".padding-tmp doesn't exist"); + } + + exists = morgueDir.exists(); + if (expectMorgueDir) { + ok(exists, "morgue does exist"); + } else { + ok(!exists, "moruge doesn't exist"); + } + } + + async function clearTestOrigin() { + let request = clearOrigin(principal, defaultPersistence); + await requestFinished(request); + } + + async function clearPersistentTestOrigin() { + let request = clearOrigin(principal, persistentPersistence); + await requestFinished(request); + } + + async function testOriginInit( + createCachesDatabase, + createPaddingFile, + createTempPaddingFile, + createMorgueDir + ) { + info( + `Testing initialization of cache directory when caches.sqlite ` + + `${createCachesDatabase ? "exists" : "doesn't exist"}, .padding ` + + `${createPaddingFile ? "exists" : "doesn't exist"}, .padding-tmp ` + + `${createTempPaddingFile ? "exists" : "doesn't exist"}, morgue ` + + `${createMorgueDir ? "exists" : "doesn't exist"}` + ); + + await createNormalCacheOrigin(); + + checkFiles(true, true, false, true); + + if (!createCachesDatabase) { + removeFile(cachesDatabase); + } + + if (!createPaddingFile) { + removeFile(paddingFile); + } + + if (createTempPaddingFile) { + createEmptyFile(paddingTempFile); + } + + if (!createMorgueDir) { + removeDir(morgueDir); + } + + await initTestOrigin(); + + checkFiles( + createCachesDatabase, + // After the origin is initialized, ".padding" should only exist when + // "caches.sqlite" exists. + createCachesDatabase, + // After the origin is initialized, ".padding-tmp" should have always been + // removed. + false, + createCachesDatabase && createMorgueDir + ); + + await clearTestOrigin(); + } + + // Test all possible combinations. + for (let createCachesDatabase of [false, true]) { + for (let createPaddingFile of [false, true]) { + for (let createTempPaddingFile of [false, true]) { + for (let createMorgueDir of [false, true]) { + await testOriginInit( + createCachesDatabase, + createPaddingFile, + createTempPaddingFile, + createMorgueDir + ); + } + } + } + } + + // Verify that InitializeOrigin doesn't fail when a + // storage/permanent/${origin}/cache exists. + async function testPermanentCacheDir() { + info( + "Testing initialization of cache directory placed in permanent origin " + + "directory" + ); + + await createPersistentTestOrigin(); + + createEmptyDirectory(persistentCacheDir); + + try { + await initPersistentTestOrigin(); + + ok(true, "Should not have thrown"); + } catch (ex) { + ok(false, "Should not have thrown"); + } + + let exists = persistentCacheDir.exists(); + ok(exists, "cache directory in permanent origin directory does exist"); + + await clearPersistentTestOrigin(); + } + + await testPermanentCacheDir(); +}); diff --git a/dom/cache/test/xpcshell/test_padding_error_handle.js b/dom/cache/test/xpcshell/test_padding_error_handle.js new file mode 100644 index 0000000000..1bb1e1804f --- /dev/null +++ b/dom/cache/test/xpcshell/test_padding_error_handle.js @@ -0,0 +1,72 @@ +/** + * This test is mainly to verify cache actions work as usual even there exists + * an unexpected padding file. + */ + +function getTempPaddingFilePath() { + let cacheDir = getCacheDir(); + let temporaryPaddingFile = cacheDir.clone(); + temporaryPaddingFile.append(".padding-tmp"); + return temporaryPaddingFile; +} + +function createTempPaddingFile() { + let temporaryPaddingFile = getTempPaddingFilePath(); + temporaryPaddingFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0644", 8)); + + ok( + temporaryPaddingFile.exists(), + "Temporary padding file does be created by test" + ); +} + +add_task(async function testSteps() { + create_test_profile("schema_25_profile.zip"); + let cache = await caches.open("test"); + + // Step 1: Verify cache.match won't fail when there is a temporary padding + // file + createTempPaddingFile(); + + let response = await cache.match("https://www.mozilla.org"); + ok(!!response, "Upgrade from 25 to 26 do succeed"); + + // Note: Only cache write actions(e.g. cache.put/add/addAll/delete) will + // remove unexpected temporary padding file when writting an opaque response + // into the file-system. Cache read actions(e.g. cache.keys/match) won't. + let temporaryPaddingFile = getTempPaddingFilePath(); + ok( + temporaryPaddingFile.exists(), + "Temporary padding file doesn't be removed by cache.match" + ); + + // Step 2: Verify cache.put won't fail when there is a temporary padding + // file + await cache.put("https://foo.com", response); + ok( + !temporaryPaddingFile.exists(), + "Temporary padding file does be removed by cache.put" + ); + + // Step 3: Verify cache.keys won't fail when there is a temporary padding + // file + createTempPaddingFile(); + + let cacheEntries = await cache.keys("https://foo.com"); + ok(cacheEntries.length === 1, "Cache.put does succeed"); + + ok( + temporaryPaddingFile.exists(), + "Temporary padding file doesn't be removed by cache.keys" + ); + + // Step 4: Verify cache.delete won't fail when there is a temporary padding + // file + await cache.delete("https://foo.com"); + ok( + !temporaryPaddingFile.exists(), + "Temporary padding file does be removed by cache.delete" + ); + + await caches.delete("test"); +}); diff --git a/dom/cache/test/xpcshell/test_schema_26_upgrade.js b/dom/cache/test/xpcshell/test_schema_26_upgrade.js new file mode 100644 index 0000000000..3bdb789e17 --- /dev/null +++ b/dom/cache/test/xpcshell/test_schema_26_upgrade.js @@ -0,0 +1,18 @@ +/** + * The schema_25_profile.zip are made from local Nightly by following step + * 1. Go to any website + * 2. Open web console and type + * caches.open("test") + * .then(c => fetch("https://www.mozilla.org", {mode:"no-cors"}) + * .then(r => c.put("https://www.mozilla.org", r))); + * 3. Go to profile directory and rename the website folder to "chrome" + */ + +add_task(async function testSteps() { + create_test_profile("schema_25_profile.zip"); + + let cache = await caches.open("test"); + let response = await cache.match("https://www.mozilla.org"); + ok(!!response, "Upgrade from 25 to 26 do succeed"); + ok(response.type === "opaque", "The response type does be opaque"); +}); diff --git a/dom/cache/test/xpcshell/xpcshell.ini b/dom/cache/test/xpcshell/xpcshell.ini new file mode 100644 index 0000000000..b1640ce42d --- /dev/null +++ b/dom/cache/test/xpcshell/xpcshell.ini @@ -0,0 +1,21 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +[DEFAULT] +head = head.js +support-files = + bug1425146_profile.zip + schema_15_profile.zip + schema_25_profile.zip + +# dummy test entry to generate profile zip files +[make_profile.js] + skip-if = true + +[test_bug1425146.js] +[test_empty_directories.js] +[test_migration.js] +[test_originInit.js] +[test_padding_error_handle.js] +[test_schema_26_upgrade.js] |