is-simple-request.spec.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. import type { Request } from 'express';
  2. import { mock } from 'vitest-mock-extended';
  3. import isSimpleRequest from './is-simple-request';
  4. describe('isSimpleRequest', () => {
  5. // method
  6. describe('When request method is checked', () => {
  7. // allow
  8. describe('When allowed method is given', () => {
  9. const allowedMethods = ['GET', 'HEAD', 'POST'];
  10. it.each(allowedMethods)('returns true for %s method', (method) => {
  11. const reqMock = mock<Request>();
  12. reqMock.method = method;
  13. reqMock.headers = { 'content-type': 'text/plain' };
  14. expect(isSimpleRequest(reqMock)).toBe(true);
  15. });
  16. });
  17. // disallow
  18. describe('When disallowed method is given', () => {
  19. const disallowedMethods = ['PUT', 'DELETE', 'PATCH', 'OPTIONS', 'TRACE'];
  20. it.each(disallowedMethods)('returns false for %s method', (method) => {
  21. const reqMock = mock<Request>();
  22. reqMock.method = method;
  23. reqMock.headers = {};
  24. expect(isSimpleRequest(reqMock)).toBe(false);
  25. });
  26. });
  27. });
  28. // headers
  29. describe('When request headers are checked', () => {
  30. // allow(Other than content-type)
  31. describe('When only safe headers are given', () => {
  32. const safeHeaders = [
  33. 'accept',
  34. 'accept-language',
  35. 'content-language',
  36. 'range',
  37. 'referer',
  38. 'dpr',
  39. 'downlink',
  40. 'save-data',
  41. 'viewport-width',
  42. 'width',
  43. ];
  44. it.each(safeHeaders)('returns true for safe header: %s', (headerName) => {
  45. const reqMock = mock<Request>();
  46. reqMock.method = 'POST';
  47. reqMock.headers = {
  48. [headerName]: 'test-value',
  49. };
  50. expect(isSimpleRequest(reqMock)).toBe(true);
  51. });
  52. // content-type
  53. it('returns true for valid content-type values', () => {
  54. const validContentTypes = [
  55. 'application/x-www-form-urlencoded',
  56. 'multipart/form-data',
  57. 'text/plain',
  58. ];
  59. validContentTypes.forEach((contentType) => {
  60. const reqMock = mock<Request>();
  61. reqMock.method = 'POST';
  62. reqMock.headers = { 'content-type': contentType };
  63. expect(isSimpleRequest(reqMock)).toBe(true);
  64. });
  65. });
  66. // combination
  67. it('returns true for combination of safe headers', () => {
  68. const reqMock = mock<Request>();
  69. reqMock.method = 'POST';
  70. reqMock.headers = {
  71. Accept: 'application/json',
  72. 'content-Type': 'text/plain',
  73. 'Accept-Language': 'en-US',
  74. };
  75. expect(isSimpleRequest(reqMock)).toBe(true);
  76. });
  77. });
  78. // disallow
  79. describe('When unsafe headers are given', () => {
  80. const unsafeHeaders = [
  81. 'X-Custom-Header',
  82. 'Authorization',
  83. 'X-Requested-With',
  84. 'X-CSRF-Token',
  85. ];
  86. it.each(unsafeHeaders)('returns false for unsafe header: %s', (headerName) => {
  87. const reqMock = mock<Request>({
  88. method: 'POST',
  89. headers: { [headerName]: 'test-value' },
  90. });
  91. expect(isSimpleRequest(reqMock)).toBe(false);
  92. });
  93. // combination
  94. it('returns false when safe and unsafe headers are mixed', () => {
  95. const reqMock = mock<Request>();
  96. reqMock.method = 'POST';
  97. reqMock.headers = {
  98. Accept: 'application/json', // Safe
  99. 'X-Custom-Header': 'custom-value', // Unsafe
  100. };
  101. expect(isSimpleRequest(reqMock)).toBe(false);
  102. });
  103. });
  104. });
  105. // content-type
  106. describe('When content-type is checked', () => {
  107. // allow
  108. describe('When a safe content-type is given', () => {
  109. const safeContentTypes = [
  110. 'application/x-www-form-urlencoded',
  111. 'multipart/form-data',
  112. 'text/plain',
  113. // parameters
  114. 'application/x-www-form-urlencoded; charset=UTF-8',
  115. 'multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW',
  116. 'text/plain; charset=iso-8859-1',
  117. ];
  118. it.each(safeContentTypes)('returns true for %s', (contentType) => {
  119. const reqMock = mock<Request>();
  120. reqMock.method = 'POST';
  121. reqMock.headers = {
  122. 'content-type': contentType,
  123. };
  124. expect(isSimpleRequest(reqMock)).toBe(true);
  125. });
  126. });
  127. // absent
  128. describe('When content-type is absent', () => {
  129. it('returns true when no content-type header is set', () => {
  130. const reqMock = mock<Request>();
  131. reqMock.method = 'POST';
  132. reqMock.headers = {};
  133. expect(isSimpleRequest(reqMock)).toBe(true);
  134. });
  135. });
  136. // disallow
  137. describe('When disallowed content-type is given', () => {
  138. const disallowedContentTypes = [
  139. 'application/json',
  140. 'application/xml',
  141. 'text/html',
  142. 'application/octet-stream',
  143. ];
  144. it.each(disallowedContentTypes)('returns false for %s', (contentType) => {
  145. const reqMock = mock<Request>();
  146. reqMock.method = 'POST';
  147. reqMock.headers = { 'content-type': contentType };
  148. expect(isSimpleRequest(reqMock)).toBe(false);
  149. });
  150. });
  151. });
  152. // integration
  153. describe('When multiple conditions are checked', () => {
  154. describe('When all conditions are met', () => {
  155. it('returns true', () => {
  156. const reqMock = mock<Request>();
  157. reqMock.method = 'POST';
  158. reqMock.headers = { 'content-type': 'application/x-www-form-urlencoded' };
  159. expect(isSimpleRequest(reqMock)).toBe(true);
  160. });
  161. });
  162. describe('When method is disallowed but headers are safe', () => {
  163. it('returns false', () => {
  164. const reqMock = mock<Request>();
  165. reqMock.method = 'PUT';
  166. reqMock.headers = { 'content-type': 'text/plain' };
  167. expect(isSimpleRequest(reqMock)).toBe(false);
  168. });
  169. });
  170. describe('When method is allowed but headers are non-safe', () => {
  171. it('returns false', () => {
  172. const reqMock = mock<Request>();
  173. reqMock.method = 'POST';
  174. reqMock.headers = { 'X-Custom-Header': 'custom-value' };
  175. expect(isSimpleRequest(reqMock)).toBe(false);
  176. });
  177. });
  178. });
  179. });