node.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.tryGetPreviewData = tryGetPreviewData;
  6. exports.parseBody = parseBody;
  7. exports.apiResolver = apiResolver;
  8. var _ = require(".");
  9. var _bytes = _interopRequireDefault(require("next/dist/compiled/bytes"));
  10. var _jsonwebtoken = _interopRequireDefault(require("next/dist/compiled/jsonwebtoken"));
  11. var _cryptoUtils = require("../crypto-utils");
  12. var _etag = require("../lib/etag");
  13. var _sendPayload = require("../send-payload");
  14. var _stream = require("stream");
  15. var _contentType = require("next/dist/compiled/content-type");
  16. var _isError = _interopRequireDefault(require("../../lib/is-error"));
  17. var _utils = require("../../shared/lib/utils");
  18. var _interopDefault = require("../../lib/interop-default");
  19. var _index = require("./index");
  20. var _mockRequest = require("../lib/mock-request");
  21. function _interopRequireDefault(obj) {
  22. return obj && obj.__esModule ? obj : {
  23. default: obj
  24. };
  25. }
  26. function tryGetPreviewData(req, res, options) {
  27. // if an On-Demand revalidation is being done preview mode
  28. // is disabled
  29. if (options && (0, _).checkIsManualRevalidate(req, options).isManualRevalidate) {
  30. return false;
  31. }
  32. // Read cached preview data if present
  33. if (_index.SYMBOL_PREVIEW_DATA in req) {
  34. return req[_index.SYMBOL_PREVIEW_DATA];
  35. }
  36. const getCookies = (0, _index).getCookieParser(req.headers);
  37. let cookies;
  38. try {
  39. cookies = getCookies();
  40. } catch {
  41. // TODO: warn
  42. return false;
  43. }
  44. const hasBypass = _index.COOKIE_NAME_PRERENDER_BYPASS in cookies;
  45. const hasData = _index.COOKIE_NAME_PRERENDER_DATA in cookies;
  46. // Case: neither cookie is set.
  47. if (!(hasBypass || hasData)) {
  48. return false;
  49. }
  50. // Case: one cookie is set, but not the other.
  51. if (hasBypass !== hasData) {
  52. (0, _index).clearPreviewData(res);
  53. return false;
  54. }
  55. // Case: preview session is for an old build.
  56. if (cookies[_index.COOKIE_NAME_PRERENDER_BYPASS] !== options.previewModeId) {
  57. (0, _index).clearPreviewData(res);
  58. return false;
  59. }
  60. const tokenPreviewData = cookies[_index.COOKIE_NAME_PRERENDER_DATA];
  61. let encryptedPreviewData;
  62. try {
  63. encryptedPreviewData = _jsonwebtoken.default.verify(tokenPreviewData, options.previewModeSigningKey);
  64. } catch {
  65. // TODO: warn
  66. (0, _index).clearPreviewData(res);
  67. return false;
  68. }
  69. const decryptedPreviewData = (0, _cryptoUtils).decryptWithSecret(Buffer.from(options.previewModeEncryptionKey), encryptedPreviewData.data);
  70. try {
  71. // TODO: strict runtime type checking
  72. const data = JSON.parse(decryptedPreviewData);
  73. // Cache lookup
  74. Object.defineProperty(req, _index.SYMBOL_PREVIEW_DATA, {
  75. value: data,
  76. enumerable: false
  77. });
  78. return data;
  79. } catch {
  80. return false;
  81. }
  82. }
  83. /**
  84. * Parse `JSON` and handles invalid `JSON` strings
  85. * @param str `JSON` string
  86. */ function parseJson(str) {
  87. if (str.length === 0) {
  88. // special-case empty json body, as it's a common client-side mistake
  89. return {};
  90. }
  91. try {
  92. return JSON.parse(str);
  93. } catch (e) {
  94. throw new _index.ApiError(400, "Invalid JSON");
  95. }
  96. }
  97. async function parseBody(req, limit) {
  98. let contentType;
  99. try {
  100. contentType = (0, _contentType).parse(req.headers["content-type"] || "text/plain");
  101. } catch {
  102. contentType = (0, _contentType).parse("text/plain");
  103. }
  104. const { type , parameters } = contentType;
  105. const encoding = parameters.charset || "utf-8";
  106. let buffer;
  107. try {
  108. const getRawBody = require("next/dist/compiled/raw-body");
  109. buffer = await getRawBody(req, {
  110. encoding,
  111. limit
  112. });
  113. } catch (e) {
  114. if ((0, _isError).default(e) && e.type === "entity.too.large") {
  115. throw new _index.ApiError(413, `Body exceeded ${limit} limit`);
  116. } else {
  117. throw new _index.ApiError(400, "Invalid body");
  118. }
  119. }
  120. const body = buffer.toString();
  121. if (type === "application/json" || type === "application/ld+json") {
  122. return parseJson(body);
  123. } else if (type === "application/x-www-form-urlencoded") {
  124. const qs = require("querystring");
  125. return qs.decode(body);
  126. } else {
  127. return body;
  128. }
  129. }
  130. function getMaxContentLength(responseLimit) {
  131. if (responseLimit && typeof responseLimit !== "boolean") {
  132. return _bytes.default.parse(responseLimit);
  133. }
  134. return _index.RESPONSE_LIMIT_DEFAULT;
  135. }
  136. /**
  137. * Send `any` body to response
  138. * @param req request object
  139. * @param res response object
  140. * @param body of response
  141. */ function sendData(req, res, body) {
  142. if (body === null || body === undefined) {
  143. res.end();
  144. return;
  145. }
  146. // strip irrelevant headers/body
  147. if (res.statusCode === 204 || res.statusCode === 304) {
  148. res.removeHeader("Content-Type");
  149. res.removeHeader("Content-Length");
  150. res.removeHeader("Transfer-Encoding");
  151. if (process.env.NODE_ENV === "development" && body) {
  152. console.warn(`A body was attempted to be set with a 204 statusCode for ${req.url}, this is invalid and the body was ignored.\n` + `See more info here https://nextjs.org/docs/messages/invalid-api-status-body`);
  153. }
  154. res.end();
  155. return;
  156. }
  157. const contentType = res.getHeader("Content-Type");
  158. if (body instanceof _stream.Stream) {
  159. if (!contentType) {
  160. res.setHeader("Content-Type", "application/octet-stream");
  161. }
  162. body.pipe(res);
  163. return;
  164. }
  165. const isJSONLike = [
  166. "object",
  167. "number",
  168. "boolean"
  169. ].includes(typeof body);
  170. const stringifiedBody = isJSONLike ? JSON.stringify(body) : body;
  171. const etag = (0, _etag).generateETag(stringifiedBody);
  172. if ((0, _sendPayload).sendEtagResponse(req, res, etag)) {
  173. return;
  174. }
  175. if (Buffer.isBuffer(body)) {
  176. if (!contentType) {
  177. res.setHeader("Content-Type", "application/octet-stream");
  178. }
  179. res.setHeader("Content-Length", body.length);
  180. res.end(body);
  181. return;
  182. }
  183. if (isJSONLike) {
  184. res.setHeader("Content-Type", "application/json; charset=utf-8");
  185. }
  186. res.setHeader("Content-Length", Buffer.byteLength(stringifiedBody));
  187. res.end(stringifiedBody);
  188. }
  189. /**
  190. * Send `JSON` object
  191. * @param res response object
  192. * @param jsonBody of data
  193. */ function sendJson(res, jsonBody) {
  194. // Set header to application/json
  195. res.setHeader("Content-Type", "application/json; charset=utf-8");
  196. // Use send to handle request
  197. res.send(JSON.stringify(jsonBody));
  198. }
  199. function isNotValidData(str) {
  200. return typeof str !== "string" || str.length < 16;
  201. }
  202. function setPreviewData(res, data, options) {
  203. if (isNotValidData(options.previewModeId)) {
  204. throw new Error("invariant: invalid previewModeId");
  205. }
  206. if (isNotValidData(options.previewModeEncryptionKey)) {
  207. throw new Error("invariant: invalid previewModeEncryptionKey");
  208. }
  209. if (isNotValidData(options.previewModeSigningKey)) {
  210. throw new Error("invariant: invalid previewModeSigningKey");
  211. }
  212. const payload = _jsonwebtoken.default.sign({
  213. data: (0, _cryptoUtils).encryptWithSecret(Buffer.from(options.previewModeEncryptionKey), JSON.stringify(data))
  214. }, options.previewModeSigningKey, {
  215. algorithm: "HS256",
  216. ...options.maxAge !== undefined ? {
  217. expiresIn: options.maxAge
  218. } : undefined
  219. });
  220. // limit preview mode cookie to 2KB since we shouldn't store too much
  221. // data here and browsers drop cookies over 4KB
  222. if (payload.length > 2048) {
  223. throw new Error(`Preview data is limited to 2KB currently, reduce how much data you are storing as preview data to continue`);
  224. }
  225. const { serialize } = require("next/dist/compiled/cookie");
  226. const previous = res.getHeader("Set-Cookie");
  227. res.setHeader(`Set-Cookie`, [
  228. ...typeof previous === "string" ? [
  229. previous
  230. ] : Array.isArray(previous) ? previous : [],
  231. serialize(_index.COOKIE_NAME_PRERENDER_BYPASS, options.previewModeId, {
  232. httpOnly: true,
  233. sameSite: process.env.NODE_ENV !== "development" ? "none" : "lax",
  234. secure: process.env.NODE_ENV !== "development",
  235. path: "/",
  236. ...options.maxAge !== undefined ? {
  237. maxAge: options.maxAge
  238. } : undefined,
  239. ...options.path !== undefined ? {
  240. path: options.path
  241. } : undefined
  242. }),
  243. serialize(_index.COOKIE_NAME_PRERENDER_DATA, payload, {
  244. httpOnly: true,
  245. sameSite: process.env.NODE_ENV !== "development" ? "none" : "lax",
  246. secure: process.env.NODE_ENV !== "development",
  247. path: "/",
  248. ...options.maxAge !== undefined ? {
  249. maxAge: options.maxAge
  250. } : undefined,
  251. ...options.path !== undefined ? {
  252. path: options.path
  253. } : undefined
  254. }),
  255. ]);
  256. return res;
  257. }
  258. async function revalidate(urlPath, opts, req, context) {
  259. if (typeof urlPath !== "string" || !urlPath.startsWith("/")) {
  260. throw new Error(`Invalid urlPath provided to revalidate(), must be a path e.g. /blog/post-1, received ${urlPath}`);
  261. }
  262. const revalidateHeaders = {
  263. [_index.PRERENDER_REVALIDATE_HEADER]: context.previewModeId,
  264. ...opts.unstable_onlyGenerated ? {
  265. [_.PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER]: "1"
  266. } : {}
  267. };
  268. try {
  269. if (context.trustHostHeader) {
  270. const res = await fetch(`https://${req.headers.host}${urlPath}`, {
  271. method: "HEAD",
  272. headers: {
  273. ...revalidateHeaders,
  274. cookie: req.headers.cookie || ""
  275. }
  276. });
  277. // we use the cache header to determine successful revalidate as
  278. // a non-200 status code can be returned from a successful revalidate
  279. // e.g. notFound: true returns 404 status code but is successful
  280. const cacheHeader = res.headers.get("x-vercel-cache") || res.headers.get("x-nextjs-cache");
  281. if ((cacheHeader == null ? void 0 : cacheHeader.toUpperCase()) !== "REVALIDATED" && !(res.status === 404 && opts.unstable_onlyGenerated)) {
  282. throw new Error(`Invalid response ${res.status}`);
  283. }
  284. } else if (context.revalidate) {
  285. const { req: mockReq , res: mockRes , streamPromise , } = (0, _mockRequest).mockRequest(urlPath, revalidateHeaders, "GET");
  286. await context.revalidate(mockReq, mockRes);
  287. await streamPromise;
  288. if (mockRes.getHeader("x-nextjs-cache") !== "REVALIDATED" && !(mockRes.statusCode === 404 && opts.unstable_onlyGenerated)) {
  289. throw new Error(`Invalid response ${mockRes.statusCode}`);
  290. }
  291. } else {
  292. throw new Error(`Invariant: required internal revalidate method not passed to api-utils`);
  293. }
  294. } catch (err) {
  295. throw new Error(`Failed to revalidate ${urlPath}: ${(0, _isError).default(err) ? err.message : err}`);
  296. }
  297. }
  298. async function apiResolver(req, res, query, resolverModule, apiContext, propagateError, dev, page) {
  299. const apiReq = req;
  300. const apiRes = res;
  301. try {
  302. var ref, ref1, ref2;
  303. if (!resolverModule) {
  304. res.statusCode = 404;
  305. res.end("Not Found");
  306. return;
  307. }
  308. const config = resolverModule.config || {};
  309. const bodyParser = ((ref = config.api) == null ? void 0 : ref.bodyParser) !== false;
  310. var ref3;
  311. const responseLimit = (ref3 = (ref1 = config.api) == null ? void 0 : ref1.responseLimit) != null ? ref3 : true;
  312. const externalResolver = ((ref2 = config.api) == null ? void 0 : ref2.externalResolver) || false;
  313. // Parsing of cookies
  314. (0, _index).setLazyProp({
  315. req: apiReq
  316. }, "cookies", (0, _index).getCookieParser(req.headers));
  317. // Parsing query string
  318. apiReq.query = query;
  319. // Parsing preview data
  320. (0, _index).setLazyProp({
  321. req: apiReq
  322. }, "previewData", ()=>tryGetPreviewData(req, res, apiContext));
  323. // Checking if preview mode is enabled
  324. (0, _index).setLazyProp({
  325. req: apiReq
  326. }, "preview", ()=>apiReq.previewData !== false ? true : undefined);
  327. // Parsing of body
  328. if (bodyParser && !apiReq.body) {
  329. apiReq.body = await parseBody(apiReq, config.api && config.api.bodyParser && config.api.bodyParser.sizeLimit ? config.api.bodyParser.sizeLimit : "1mb");
  330. }
  331. let contentLength = 0;
  332. const maxContentLength = getMaxContentLength(responseLimit);
  333. const writeData = apiRes.write;
  334. const endResponse = apiRes.end;
  335. apiRes.write = (...args)=>{
  336. contentLength += Buffer.byteLength(args[0] || "");
  337. return writeData.apply(apiRes, args);
  338. };
  339. apiRes.end = (...args)=>{
  340. if (args.length && typeof args[0] !== "function") {
  341. contentLength += Buffer.byteLength(args[0] || "");
  342. }
  343. if (responseLimit && contentLength >= maxContentLength) {
  344. console.warn(`API response for ${req.url} exceeds ${_bytes.default.format(maxContentLength)}. API Routes are meant to respond quickly. https://nextjs.org/docs/messages/api-routes-response-size-limit`);
  345. }
  346. endResponse.apply(apiRes, args);
  347. };
  348. apiRes.status = (statusCode)=>(0, _index).sendStatusCode(apiRes, statusCode);
  349. apiRes.send = (data)=>sendData(apiReq, apiRes, data);
  350. apiRes.json = (data)=>sendJson(apiRes, data);
  351. apiRes.redirect = (statusOrUrl, url)=>(0, _index).redirect(apiRes, statusOrUrl, url);
  352. apiRes.setPreviewData = (data, options = {})=>setPreviewData(apiRes, data, Object.assign({}, apiContext, options));
  353. apiRes.clearPreviewData = (options = {})=>(0, _index).clearPreviewData(apiRes, options);
  354. apiRes.revalidate = (urlPath, opts)=>revalidate(urlPath, opts || {}, req, apiContext);
  355. // TODO: remove in next minor (current v12.2)
  356. apiRes.unstable_revalidate = ()=>{
  357. throw new Error(`"unstable_revalidate" has been renamed to "revalidate" see more info here: https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration#on-demand-revalidation`);
  358. };
  359. const resolver = (0, _interopDefault).interopDefault(resolverModule);
  360. let wasPiped = false;
  361. if (process.env.NODE_ENV !== "production") {
  362. // listen for pipe event and don't show resolve warning
  363. res.once("pipe", ()=>wasPiped = true);
  364. }
  365. // Call API route method
  366. await resolver(req, res);
  367. if (process.env.NODE_ENV !== "production" && !externalResolver && !(0, _utils).isResSent(res) && !wasPiped) {
  368. console.warn(`API resolved without sending a response for ${req.url}, this may result in stalled requests.`);
  369. }
  370. } catch (err) {
  371. if (err instanceof _index.ApiError) {
  372. (0, _index).sendError(apiRes, err.statusCode, err.message);
  373. } else {
  374. if (dev) {
  375. if ((0, _isError).default(err)) {
  376. err.page = page;
  377. }
  378. throw err;
  379. }
  380. console.error(err);
  381. if (propagateError) {
  382. throw err;
  383. }
  384. (0, _index).sendError(apiRes, 500, "Internal Server Error");
  385. }
  386. }
  387. }
  388. //# sourceMappingURL=node.js.map