network_utils_spec.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. /* Copyright 2017 Mozilla Foundation
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. import {
  16. createResponseStatusError,
  17. extractFilenameFromHeader,
  18. validateRangeRequestCapabilities,
  19. validateResponseStatus,
  20. } from "../../src/display/network_utils.js";
  21. import {
  22. MissingPDFException,
  23. UnexpectedResponseException,
  24. } from "../../src/shared/util.js";
  25. describe("network_utils", function () {
  26. describe("validateRangeRequestCapabilities", function () {
  27. it("rejects invalid rangeChunkSize", function () {
  28. expect(function () {
  29. validateRangeRequestCapabilities({ rangeChunkSize: "abc" });
  30. }).toThrow(
  31. new Error("rangeChunkSize must be an integer larger than zero.")
  32. );
  33. expect(function () {
  34. validateRangeRequestCapabilities({ rangeChunkSize: 0 });
  35. }).toThrow(
  36. new Error("rangeChunkSize must be an integer larger than zero.")
  37. );
  38. });
  39. it("rejects disabled or non-HTTP range requests", function () {
  40. expect(
  41. validateRangeRequestCapabilities({
  42. disableRange: true,
  43. isHttp: true,
  44. getResponseHeader: headerName => {
  45. if (headerName === "Content-Length") {
  46. return 8;
  47. }
  48. throw new Error(`Unexpected headerName: ${headerName}`);
  49. },
  50. rangeChunkSize: 64,
  51. })
  52. ).toEqual({
  53. allowRangeRequests: false,
  54. suggestedLength: 8,
  55. });
  56. expect(
  57. validateRangeRequestCapabilities({
  58. disableRange: false,
  59. isHttp: false,
  60. getResponseHeader: headerName => {
  61. if (headerName === "Content-Length") {
  62. return 8;
  63. }
  64. throw new Error(`Unexpected headerName: ${headerName}`);
  65. },
  66. rangeChunkSize: 64,
  67. })
  68. ).toEqual({
  69. allowRangeRequests: false,
  70. suggestedLength: 8,
  71. });
  72. });
  73. it("rejects invalid Accept-Ranges header values", function () {
  74. expect(
  75. validateRangeRequestCapabilities({
  76. disableRange: false,
  77. isHttp: true,
  78. getResponseHeader: headerName => {
  79. if (headerName === "Accept-Ranges") {
  80. return "none";
  81. } else if (headerName === "Content-Length") {
  82. return 8;
  83. }
  84. throw new Error(`Unexpected headerName: ${headerName}`);
  85. },
  86. rangeChunkSize: 64,
  87. })
  88. ).toEqual({
  89. allowRangeRequests: false,
  90. suggestedLength: 8,
  91. });
  92. });
  93. it("rejects invalid Content-Encoding header values", function () {
  94. expect(
  95. validateRangeRequestCapabilities({
  96. disableRange: false,
  97. isHttp: true,
  98. getResponseHeader: headerName => {
  99. if (headerName === "Accept-Ranges") {
  100. return "bytes";
  101. } else if (headerName === "Content-Encoding") {
  102. return "gzip";
  103. } else if (headerName === "Content-Length") {
  104. return 8;
  105. }
  106. throw new Error(`Unexpected headerName: ${headerName}`);
  107. },
  108. rangeChunkSize: 64,
  109. })
  110. ).toEqual({
  111. allowRangeRequests: false,
  112. suggestedLength: 8,
  113. });
  114. });
  115. it("rejects invalid Content-Length header values", function () {
  116. expect(
  117. validateRangeRequestCapabilities({
  118. disableRange: false,
  119. isHttp: true,
  120. getResponseHeader: headerName => {
  121. if (headerName === "Accept-Ranges") {
  122. return "bytes";
  123. } else if (headerName === "Content-Encoding") {
  124. return null;
  125. } else if (headerName === "Content-Length") {
  126. return "eight";
  127. }
  128. throw new Error(`Unexpected headerName: ${headerName}`);
  129. },
  130. rangeChunkSize: 64,
  131. })
  132. ).toEqual({
  133. allowRangeRequests: false,
  134. suggestedLength: undefined,
  135. });
  136. });
  137. it("rejects file sizes that are too small for range requests", function () {
  138. expect(
  139. validateRangeRequestCapabilities({
  140. disableRange: false,
  141. isHttp: true,
  142. getResponseHeader: headerName => {
  143. if (headerName === "Accept-Ranges") {
  144. return "bytes";
  145. } else if (headerName === "Content-Encoding") {
  146. return null;
  147. } else if (headerName === "Content-Length") {
  148. return 8;
  149. }
  150. throw new Error(`Unexpected headerName: ${headerName}`);
  151. },
  152. rangeChunkSize: 64,
  153. })
  154. ).toEqual({
  155. allowRangeRequests: false,
  156. suggestedLength: 8,
  157. });
  158. });
  159. it("accepts file sizes large enough for range requests", function () {
  160. expect(
  161. validateRangeRequestCapabilities({
  162. disableRange: false,
  163. isHttp: true,
  164. getResponseHeader: headerName => {
  165. if (headerName === "Accept-Ranges") {
  166. return "bytes";
  167. } else if (headerName === "Content-Encoding") {
  168. return null;
  169. } else if (headerName === "Content-Length") {
  170. return 8192;
  171. }
  172. throw new Error(`Unexpected headerName: ${headerName}`);
  173. },
  174. rangeChunkSize: 64,
  175. })
  176. ).toEqual({
  177. allowRangeRequests: true,
  178. suggestedLength: 8192,
  179. });
  180. });
  181. });
  182. describe("extractFilenameFromHeader", function () {
  183. it("returns null when content disposition header is blank", function () {
  184. expect(
  185. extractFilenameFromHeader(headerName => {
  186. if (headerName === "Content-Disposition") {
  187. return null;
  188. }
  189. throw new Error(`Unexpected headerName: ${headerName}`);
  190. })
  191. ).toBeNull();
  192. expect(
  193. extractFilenameFromHeader(headerName => {
  194. if (headerName === "Content-Disposition") {
  195. return undefined;
  196. }
  197. throw new Error(`Unexpected headerName: ${headerName}`);
  198. })
  199. ).toBeNull();
  200. expect(
  201. extractFilenameFromHeader(headerName => {
  202. if (headerName === "Content-Disposition") {
  203. return "";
  204. }
  205. throw new Error(`Unexpected headerName: ${headerName}`);
  206. })
  207. ).toBeNull();
  208. });
  209. it("gets the filename from the response header", function () {
  210. expect(
  211. extractFilenameFromHeader(headerName => {
  212. if (headerName === "Content-Disposition") {
  213. return "inline";
  214. }
  215. throw new Error(`Unexpected headerName: ${headerName}`);
  216. })
  217. ).toBeNull();
  218. expect(
  219. extractFilenameFromHeader(headerName => {
  220. if (headerName === "Content-Disposition") {
  221. return "attachment";
  222. }
  223. throw new Error(`Unexpected headerName: ${headerName}`);
  224. })
  225. ).toBeNull();
  226. expect(
  227. extractFilenameFromHeader(headerName => {
  228. if (headerName === "Content-Disposition") {
  229. return 'attachment; filename="filename.pdf"';
  230. }
  231. throw new Error(`Unexpected headerName: ${headerName}`);
  232. })
  233. ).toEqual("filename.pdf");
  234. expect(
  235. extractFilenameFromHeader(headerName => {
  236. if (headerName === "Content-Disposition") {
  237. return 'attachment; filename="filename.pdf and spaces.pdf"';
  238. }
  239. throw new Error(`Unexpected headerName: ${headerName}`);
  240. })
  241. ).toEqual("filename.pdf and spaces.pdf");
  242. expect(
  243. extractFilenameFromHeader(headerName => {
  244. if (headerName === "Content-Disposition") {
  245. return 'attachment; filename="tl;dr.pdf"';
  246. }
  247. throw new Error(`Unexpected headerName: ${headerName}`);
  248. })
  249. ).toEqual("tl;dr.pdf");
  250. expect(
  251. extractFilenameFromHeader(headerName => {
  252. if (headerName === "Content-Disposition") {
  253. return "attachment; filename=filename.pdf";
  254. }
  255. throw new Error(`Unexpected headerName: ${headerName}`);
  256. })
  257. ).toEqual("filename.pdf");
  258. expect(
  259. extractFilenameFromHeader(headerName => {
  260. if (headerName === "Content-Disposition") {
  261. return "attachment; filename=filename.pdf someotherparam";
  262. }
  263. throw new Error(`Unexpected headerName: ${headerName}`);
  264. })
  265. ).toEqual("filename.pdf");
  266. expect(
  267. extractFilenameFromHeader(headerName => {
  268. if (headerName === "Content-Disposition") {
  269. return 'attachment; filename="%e4%b8%ad%e6%96%87.pdf"';
  270. }
  271. throw new Error(`Unexpected headerName: ${headerName}`);
  272. })
  273. ).toEqual("中文.pdf");
  274. expect(
  275. extractFilenameFromHeader(headerName => {
  276. if (headerName === "Content-Disposition") {
  277. return 'attachment; filename="100%.pdf"';
  278. }
  279. throw new Error(`Unexpected headerName: ${headerName}`);
  280. })
  281. ).toEqual("100%.pdf");
  282. });
  283. it("gets the filename from the response header (RFC 6266)", function () {
  284. expect(
  285. extractFilenameFromHeader(headerName => {
  286. if (headerName === "Content-Disposition") {
  287. return "attachment; filename*=filename.pdf";
  288. }
  289. throw new Error(`Unexpected headerName: ${headerName}`);
  290. })
  291. ).toEqual("filename.pdf");
  292. expect(
  293. extractFilenameFromHeader(headerName => {
  294. if (headerName === "Content-Disposition") {
  295. return "attachment; filename*=''filename.pdf";
  296. }
  297. throw new Error(`Unexpected headerName: ${headerName}`);
  298. })
  299. ).toEqual("filename.pdf");
  300. expect(
  301. extractFilenameFromHeader(headerName => {
  302. if (headerName === "Content-Disposition") {
  303. return "attachment; filename*=utf-8''filename.pdf";
  304. }
  305. throw new Error(`Unexpected headerName: ${headerName}`);
  306. })
  307. ).toEqual("filename.pdf");
  308. expect(
  309. extractFilenameFromHeader(headerName => {
  310. if (headerName === "Content-Disposition") {
  311. return "attachment; filename=no.pdf; filename*=utf-8''filename.pdf";
  312. }
  313. throw new Error(`Unexpected headerName: ${headerName}`);
  314. })
  315. ).toEqual("filename.pdf");
  316. expect(
  317. extractFilenameFromHeader(headerName => {
  318. if (headerName === "Content-Disposition") {
  319. return "attachment; filename*=utf-8''filename.pdf; filename=no.pdf";
  320. }
  321. throw new Error(`Unexpected headerName: ${headerName}`);
  322. })
  323. ).toEqual("filename.pdf");
  324. });
  325. it("gets the filename from the response header (RFC 2231)", function () {
  326. // Tests continuations (RFC 2231 section 3, via RFC 5987 section 3.1).
  327. expect(
  328. extractFilenameFromHeader(headerName => {
  329. if (headerName === "Content-Disposition") {
  330. return "attachment; filename*0=filename; filename*1=.pdf";
  331. }
  332. throw new Error(`Unexpected headerName: ${headerName}`);
  333. })
  334. ).toEqual("filename.pdf");
  335. });
  336. it("only extracts filename with pdf extension", function () {
  337. expect(
  338. extractFilenameFromHeader(headerName => {
  339. if (headerName === "Content-Disposition") {
  340. return 'attachment; filename="filename.png"';
  341. }
  342. throw new Error(`Unexpected headerName: ${headerName}`);
  343. })
  344. ).toBeNull();
  345. });
  346. it("extension validation is case insensitive", function () {
  347. expect(
  348. extractFilenameFromHeader(headerName => {
  349. if (headerName === "Content-Disposition") {
  350. return 'form-data; name="fieldName"; filename="file.PdF"';
  351. }
  352. throw new Error(`Unexpected headerName: ${headerName}`);
  353. })
  354. ).toEqual("file.PdF");
  355. });
  356. });
  357. describe("createResponseStatusError", function () {
  358. it("handles missing PDF file responses", function () {
  359. expect(createResponseStatusError(404, "https://foo.com/bar.pdf")).toEqual(
  360. new MissingPDFException('Missing PDF "https://foo.com/bar.pdf".')
  361. );
  362. expect(createResponseStatusError(0, "file://foo.pdf")).toEqual(
  363. new MissingPDFException('Missing PDF "file://foo.pdf".')
  364. );
  365. });
  366. it("handles unexpected responses", function () {
  367. expect(createResponseStatusError(302, "https://foo.com/bar.pdf")).toEqual(
  368. new UnexpectedResponseException(
  369. "Unexpected server response (302) while retrieving PDF " +
  370. '"https://foo.com/bar.pdf".'
  371. )
  372. );
  373. expect(createResponseStatusError(0, "https://foo.com/bar.pdf")).toEqual(
  374. new UnexpectedResponseException(
  375. "Unexpected server response (0) while retrieving PDF " +
  376. '"https://foo.com/bar.pdf".'
  377. )
  378. );
  379. });
  380. });
  381. describe("validateResponseStatus", function () {
  382. it("accepts valid response statuses", function () {
  383. expect(validateResponseStatus(200)).toEqual(true);
  384. expect(validateResponseStatus(206)).toEqual(true);
  385. });
  386. it("rejects invalid response statuses", function () {
  387. expect(validateResponseStatus(302)).toEqual(false);
  388. expect(validateResponseStatus(404)).toEqual(false);
  389. expect(validateResponseStatus(null)).toEqual(false);
  390. expect(validateResponseStatus(undefined)).toEqual(false);
  391. });
  392. });
  393. });