diff --git a/packages/next/compiled/content-disposition/LICENSE b/packages/next/compiled/content-disposition/LICENSE new file mode 100644 index 0000000000000..84441fbb57092 --- /dev/null +++ b/packages/next/compiled/content-disposition/LICENSE @@ -0,0 +1,22 @@ +(The MIT License) + +Copyright (c) 2014-2017 Douglas Christopher Wilson + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/next/compiled/content-disposition/index.js b/packages/next/compiled/content-disposition/index.js new file mode 100644 index 0000000000000..cb908fd8853e3 --- /dev/null +++ b/packages/next/compiled/content-disposition/index.js @@ -0,0 +1 @@ +module.exports=(()=>{var r={565:(r,e,t)=>{"use strict";r.exports=contentDisposition;r.exports.parse=parse;var a=t(622).basename;var n=t(505).Buffer;var o=/[\x00-\x20"'()*,/:;<=>?@[\\\]{}\x7f]/g;var i=/%[0-9A-Fa-f]{2}/;var f=/%([0-9A-Fa-f]{2})/g;var s=/[^\x20-\x7e\xa0-\xff]/g;var p=/\\([\u0000-\u007f])/g;var u=/([\\"])/g;var c=/;[\x09\x20]*([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*=[\x09\x20]*("(?:[\x20!\x23-\x5b\x5d-\x7e\x80-\xff]|\\[\x20-\x7e])*"|[!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*/g;var v=/^[\x20-\x7e\x80-\xff]+$/;var x=/^[!#$%&'*+.0-9A-Z^_`a-z|~-]+$/;var l=/^([A-Za-z0-9!#$%&+\-^_`{}~]+)'(?:[A-Za-z]{2,3}(?:-[A-Za-z]{3}){0,3}|[A-Za-z]{4,8}|)'((?:%[0-9A-Fa-f]{2}|[A-Za-z0-9!#$&+.^_`|~-])+)$/;var w=/^([!#$%&'*+.0-9A-Z^_`a-z|~-]+)[\x09\x20]*(?:$|;)/;function contentDisposition(r,e){var t=e||{};var a=t.type||"attachment";var n=createparams(r,t.fallback);return format(new ContentDisposition(a,n))}function createparams(r,e){if(r===undefined){return}var t={};if(typeof r!=="string"){throw new TypeError("filename must be a string")}if(e===undefined){e=true}if(typeof e!=="string"&&typeof e!=="boolean"){throw new TypeError("fallback must be a string or boolean")}if(typeof e==="string"&&s.test(e)){throw new TypeError("fallback must be ISO-8859-1 string")}var n=a(r);var o=v.test(n);var f=typeof e!=="string"?e&&getlatin1(n):a(e);var p=typeof f==="string"&&f!==n;if(p||!o||i.test(n)){t["filename*"]=n}if(o||p){t.filename=p?f:n}return t}function format(r){var e=r.parameters;var t=r.type;if(!t||typeof t!=="string"||!x.test(t)){throw new TypeError("invalid type")}var a=String(t).toLowerCase();if(e&&typeof e==="object"){var n;var o=Object.keys(e).sort();for(var i=0;i{var a=t(293);var n=a.Buffer;function copyProps(r,e){for(var t in r){e[t]=r[t]}}if(n.from&&n.alloc&&n.allocUnsafe&&n.allocUnsafeSlow){r.exports=a}else{copyProps(a,e);e.Buffer=SafeBuffer}function SafeBuffer(r,e,t){return n(r,e,t)}copyProps(n,SafeBuffer);SafeBuffer.from=function(r,e,t){if(typeof r==="number"){throw new TypeError("Argument must not be a number")}return n(r,e,t)};SafeBuffer.alloc=function(r,e,t){if(typeof r!=="number"){throw new TypeError("Argument must be a number")}var a=n(r);if(e!==undefined){if(typeof t==="string"){a.fill(e,t)}else{a.fill(e)}}else{a.fill(0)}return a};SafeBuffer.allocUnsafe=function(r){if(typeof r!=="number"){throw new TypeError("Argument must be a number")}return n(r)};SafeBuffer.allocUnsafeSlow=function(r){if(typeof r!=="number"){throw new TypeError("Argument must be a number")}return a.SlowBuffer(r)}},293:r=>{"use strict";r.exports=require("buffer")},622:r=>{"use strict";r.exports=require("path")}};var e={};function __nccwpck_require__(t){if(e[t]){return e[t].exports}var a=e[t]={exports:{}};var n=true;try{r[t](a,a.exports,__nccwpck_require__);n=false}finally{if(n)delete e[t]}return a.exports}__nccwpck_require__.ab=__dirname+"/";return __nccwpck_require__(565)})(); \ No newline at end of file diff --git a/packages/next/compiled/content-disposition/package.json b/packages/next/compiled/content-disposition/package.json new file mode 100644 index 0000000000000..f5c3c0e8108f2 --- /dev/null +++ b/packages/next/compiled/content-disposition/package.json @@ -0,0 +1 @@ +{"name":"content-disposition","main":"index.js","author":"Douglas Christopher Wilson ","license":"MIT"} diff --git a/packages/next/package.json b/packages/next/package.json index 9af7e314c5e0d..046e27cbe13d0 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -167,6 +167,7 @@ "@types/babel__traverse": "7.11.0", "@types/ci-info": "2.0.0", "@types/compression": "0.0.36", + "@types/content-disposition": "0.5.4", "@types/content-type": "1.1.3", "@types/cookie": "0.3.3", "@types/cross-spawn": "6.0.0", @@ -203,6 +204,7 @@ "comment-json": "3.0.3", "compression": "1.7.4", "conf": "5.0.0", + "content-disposition": "0.5.3", "content-type": "1.0.4", "cookie": "0.4.1", "cross-spawn": "6.0.5", diff --git a/packages/next/server/image-optimizer.ts b/packages/next/server/image-optimizer.ts index 8689b5e026d20..782a8be1aa179 100644 --- a/packages/next/server/image-optimizer.ts +++ b/packages/next/server/image-optimizer.ts @@ -6,6 +6,7 @@ import imageSizeOf from 'image-size' import { IncomingMessage, ServerResponse } from 'http' // @ts-ignore no types for is-animated import isAnimated from 'next/dist/compiled/is-animated' +import contentDisposition from 'next/dist/compiled/content-disposition' import { join } from 'path' import Stream from 'stream' import nodeUrl, { UrlWithParsedQuery } from 'url' @@ -541,7 +542,10 @@ function setResponseHeaders( const fileName = getFileNameWithExtension(url, contentType) if (fileName) { - res.setHeader('Content-Disposition', `inline; filename="${fileName}"`) + res.setHeader( + 'Content-Disposition', + contentDisposition(fileName, { type: 'inline' }) + ) } res.setHeader('Content-Security-Policy', `script-src 'none'; sandbox;`) diff --git a/packages/next/taskfile.js b/packages/next/taskfile.js index 5e6205bdbac38..ad9f44898eb5c 100644 --- a/packages/next/taskfile.js +++ b/packages/next/taskfile.js @@ -199,6 +199,16 @@ export async function ncc_conf(task, opts) { .target('compiled/conf') } // eslint-disable-next-line camelcase +externals['content-disposition'] = 'next/dist/compiled/content-disposition' +export async function ncc_content_disposition(task, opts) { + await task + .source( + opts.src || relative(__dirname, require.resolve('content-disposition')) + ) + .ncc({ packageName: 'content-disposition', externals }) + .target('compiled/content-disposition') +} +// eslint-disable-next-line camelcase externals['content-type'] = 'next/dist/compiled/content-type' export async function ncc_content_type(task, opts) { await task @@ -929,6 +939,7 @@ export async function ncc(task, opts) { 'ncc_comment_json', 'ncc_compression', 'ncc_conf', + 'ncc_content_disposition', 'ncc_content_type', 'ncc_cookie', 'ncc_cross_spawn', diff --git a/packages/next/types/misc.d.ts b/packages/next/types/misc.d.ts index 4fa3c9303cc0a..d63e17eb18bae 100644 --- a/packages/next/types/misc.d.ts +++ b/packages/next/types/misc.d.ts @@ -87,6 +87,10 @@ declare module 'next/dist/compiled/conf' { import m from 'conf' export = m } +declare module 'next/dist/compiled/content-disposition' { + import m from 'content-disposition' + export = m +} declare module 'next/dist/compiled/content-type' { import m from 'content-type' export = m diff --git a/test/integration/image-component/unicode/pages/index.js b/test/integration/image-component/unicode/pages/index.js index bdc4ea840f65b..da3178409a3ec 100644 --- a/test/integration/image-component/unicode/pages/index.js +++ b/test/integration/image-component/unicode/pages/index.js @@ -1,16 +1,16 @@ import React from 'react' import Image from 'next/image' -import img from '../public/äöü.png' +import img from '../public/äöüščří.png' const Page = () => { return (

Unicode Image URL

- + diff --git "a/test/integration/image-component/unicode/public/\303\244\303\266\303\274.png" "b/test/integration/image-component/unicode/public/\303\244\303\266\303\274\305\241\304\215\305\231\303\255.png" similarity index 100% rename from "test/integration/image-component/unicode/public/\303\244\303\266\303\274.png" rename to "test/integration/image-component/unicode/public/\303\244\303\266\303\274\305\241\304\215\305\231\303\255.png" diff --git a/test/integration/image-component/unicode/test/index.test.js b/test/integration/image-component/unicode/test/index.test.js index ee8c84e67b04f..9c686c50ab448 100644 --- a/test/integration/image-component/unicode/test/index.test.js +++ b/test/integration/image-component/unicode/test/index.test.js @@ -20,7 +20,9 @@ let browser function runTests() { it('should load static unicode image', async () => { const src = await browser.elementById('static').getAttribute('src') - expect(src).toMatch(/_next%2Fstatic%2Fmedia%2F%C3%A4%C3%B6%C3%BC(.+)png/) + expect(src).toMatch( + /_next%2Fstatic%2Fmedia%2F%C3%A4%C3%B6%C3%BC%C5%A1%C4%8D%C5%99%C3%AD(.+)png/ + ) const fullSrc = new URL(src, `http://localhost:${appPort}`) const res = await fetch(fullSrc) expect(res.status).toBe(200) @@ -28,7 +30,9 @@ function runTests() { it('should load internal unicode image', async () => { const src = await browser.elementById('internal').getAttribute('src') - expect(src).toMatch('/_next/image?url=%2F%C3%A4%C3%B6%C3%BC.png') + expect(src).toMatch( + '/_next/image?url=%2F%C3%A4%C3%B6%C3%BC%C5%A1%C4%8D%C5%99%C3%AD.png' + ) const fullSrc = new URL(src, `http://localhost:${appPort}`) const res = await fetch(fullSrc) expect(res.status).toBe(200) @@ -37,7 +41,7 @@ function runTests() { it('should load external unicode image', async () => { const src = await browser.elementById('external').getAttribute('src') expect(src).toMatch( - '/_next/image?url=https%3A%2F%2Fimage-optimization-test.vercel.app%2F%C3%A4%C3%B6%C3%BC.png' + '/_next/image?url=https%3A%2F%2Fimage-optimization-test.vercel.app%2F%C3%A4%C3%B6%C3%BC%C5%A1%C4%8D%C5%99%C3%AD.png' ) const fullSrc = new URL(src, `http://localhost:${appPort}`) const res = await fetch(fullSrc) diff --git "a/test/integration/image-optimizer/app/public/\303\244\303\266\303\274.png" "b/test/integration/image-optimizer/app/public/\303\244\303\266\303\274\305\241\304\215\305\231\303\255.png" similarity index 100% rename from "test/integration/image-optimizer/app/public/\303\244\303\266\303\274.png" rename to "test/integration/image-optimizer/app/public/\303\244\303\266\303\274\305\241\304\215\305\231\303\255.png" diff --git a/test/integration/image-optimizer/test/index.test.js b/test/integration/image-optimizer/test/index.test.js index 6460c604aedc7..519a27715f6d4 100644 --- a/test/integration/image-optimizer/test/index.test.js +++ b/test/integration/image-optimizer/test/index.test.js @@ -63,7 +63,7 @@ function runTests({ }) it('should handle non-ascii characters in image url', async () => { - const query = { w, q: 90, url: '/äöü.png' } + const query = { w, q: 90, url: '/äöüščří.png' } const res = await fetchViaHTTP(appPort, '/_next/image', query, {}) expect(res.status).toBe(200) }) diff --git a/yarn.lock b/yarn.lock index 144269dfc5988..b35acaa2e1bac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4498,6 +4498,11 @@ dependencies: "@types/node" "*" +"@types/content-disposition@0.5.4": + version "0.5.4" + resolved "https://registry.yarnpkg.com/@types/content-disposition/-/content-disposition-0.5.4.tgz#de48cf01c79c9f1560bcfd8ae43217ab028657f8" + integrity sha512-0mPF08jn9zYI0n0Q/Pnz7C4kThdSt+6LD4amsrYDDpgBfrVWa3TcCOxKX1zkGgYniGagRv8heN2cbh+CAn+uuQ== + "@types/content-type@1.1.3": version "1.1.3" resolved "https://registry.yarnpkg.com/@types/content-type/-/content-type-1.1.3.tgz#3688bd77fc12f935548eef102a4e34c512b03a07" @@ -7483,6 +7488,7 @@ contains-path@^0.1.0: content-disposition@0.5.3: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" + integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== dependencies: safe-buffer "5.1.2"