From 247ba78d5d5edbda39e27bb7b8151ea7cc5616a2 Mon Sep 17 00:00:00 2001 From: SylRob Date: Tue, 2 Jul 2019 16:34:14 +0900 Subject: [PATCH 1/3] first draft --- bin/serve.js | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/bin/serve.js b/bin/serve.js index 45e583f3..e7fb6ba5 100755 --- a/bin/serve.js +++ b/bin/serve.js @@ -81,6 +81,8 @@ const getHelp = () => chalk` -c, --config Specify custom path to \`serve.json\` + --charset override header charset for every http request of html, htm, css file + -n, --no-clipboard Do not copy the local address to the clipboard -S, --symlinks Resolve symlinks instead of showing 404 errors @@ -167,6 +169,30 @@ const getNetworkAddress = () => { } }; +const scanContentTypeCharset = (fullPath, ext) => { + const defaultCharset = 'utf-8'; + + let content = ''; + let charset = []; + let matcher = null; + try { + content = fs.readFileSync(fullPath, 'utf8'); + } catch (e) { + console.error(`could not find the file ${fullPath}`); + } + + if (ext === 'html') { + matcher = /]*?charset\s*=[\s"']*([^\s"'/>]*)/; + } else if (ext === 'css') { + matcher = /@charset\s*[\s"']*([^\s"'/>]*)/; + } + + charset = content.match(matcher); + charset = charset && charset.length > 1 ? charset[1].toLowerCase() : defaultCharset; + + return `text/${ext}; charset=${charset}`; +}; + const startEndpoint = (endpoint, config, args, previous) => { const {isTTY} = process.stdout; const clipboard = args['--no-clipboard'] !== true; @@ -177,6 +203,37 @@ const startEndpoint = (endpoint, config, args, previous) => { await compressionHandler(request, response); } + + const fullPath = path.resolve() + request.url + (request.url === '/' ? 'index.html' : ''); + const extention = fullPath.split(/\#|\?/)[0].split('.').pop() + .trim() + .toLowerCase(); + + // if the file type is html or css we can try to detect the charset specified in the content + if (extention === 'css' || extention === 'html' || extention === 'htm') { + if (!config.headers) { + config.headers = []; + } + + if (config.charset) { + config.headers.push({ + source: (request.url === '/' ? 'index.html' : ''), + headers: [{ + key: 'Content-Type', + value: `text/${extention}; charset=${config.charset}` + }] + }); + } else { + config.headers.push({ + source: (request.url === '/' ? 'index.html' : ''), + headers: [{ + key: 'Content-Type', + value: scanContentTypeCharset(fullPath, extention) + }] + }); + } + } + return handler(request, response, config); }); @@ -341,6 +398,7 @@ const loadConfig = async (cwd, entry, args) => { '--single': Boolean, '--debug': Boolean, '--config': String, + '--charset': String, '--no-clipboard': Boolean, '--no-compression': Boolean, '--symlinks': Boolean, @@ -405,6 +463,10 @@ const loadConfig = async (cwd, entry, args) => { config.symlinks = true; } + if (args['--charset']) { + config.charset = args['--charset']; + } + for (const endpoint of args['--listen']) { startEndpoint(endpoint, config, args); } From c11034d16ddb60fc8a6b1d19852aecfd0bd93ab6 Mon Sep 17 00:00:00 2001 From: SylRob Date: Fri, 5 Jul 2019 09:33:11 +0900 Subject: [PATCH 2/3] fix file path / extention bug for none index.html files --- bin/serve.js | 93 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 61 insertions(+), 32 deletions(-) diff --git a/bin/serve.js b/bin/serve.js index e7fb6ba5..8c1a4285 100755 --- a/bin/serve.js +++ b/bin/serve.js @@ -169,70 +169,99 @@ const getNetworkAddress = () => { } }; -const scanContentTypeCharset = (fullPath, ext) => { +const scanContentTypeCharset = (content, ext) => { const defaultCharset = 'utf-8'; - - let content = ''; let charset = []; let matcher = null; - try { - content = fs.readFileSync(fullPath, 'utf8'); - } catch (e) { - console.error(`could not find the file ${fullPath}`); - } - if (ext === 'html') { + if (ext === 'html' || ext === 'htm') { matcher = /]*?charset\s*=[\s"']*([^\s"'/>]*)/; } else if (ext === 'css') { matcher = /@charset\s*[\s"']*([^\s"'/>]*)/; } - charset = content.match(matcher); charset = charset && charset.length > 1 ? charset[1].toLowerCase() : defaultCharset; - return `text/${ext}; charset=${charset}`; + return charset; +}; + +const extractCharset = (extention, fileContent, forcedCharset) => { + if (forcedCharset) { + return forcedCharset; + } + + return scanContentTypeCharset(fileContent, extention); +}; + +const realPath = (relativePath) => { + const possiblePaths = [ + path.join(relativePath, 'index.html'), + relativePath.endsWith('/') ? relativePath.replace(/\/$/g, '.html') : `${relativePath}.html` + ].filter((item) => path.basename(item) !== '.html'); + + for (let index = 0; index < possiblePaths.length; index++) { + const related = possiblePaths[index]; + const absolutePath = path.join(process.cwd(), related); + let exist = false; + try { + exist = fs.existsSync(absolutePath); + } catch (err) { + continue; + } + + if (exist) { + return absolutePath; + } + } + + return null; }; const startEndpoint = (endpoint, config, args, previous) => { const {isTTY} = process.stdout; const clipboard = args['--no-clipboard'] !== true; const compress = args['--no-compression'] !== true; + const allowExt = [ + 'css', + 'html', + 'htm', + 'shtml' + ]; const server = http.createServer(async (request, response) => { if (compress) { await compressionHandler(request, response); } + let fullPath = path.resolve() + request.url; + let relativePath = request.url; + if (path.extname(fullPath) === '') { + fullPath = realPath(request.url) ? realPath(request.url) : fullPath; + relativePath = path.relative(process.cwd(), fullPath); + } - const fullPath = path.resolve() + request.url + (request.url === '/' ? 'index.html' : ''); - const extention = fullPath.split(/\#|\?/)[0].split('.').pop() - .trim() - .toLowerCase(); + const extention = fullPath.split(/\#|\?/)[0].split('.').pop(); - // if the file type is html or css we can try to detect the charset specified in the content - if (extention === 'css' || extention === 'html' || extention === 'htm') { - if (!config.headers) { - config.headers = []; - } + let charset = 'utf-8'; + if (allowExt.indexOf(extention) >= 0) { + try { + const fileContent = fs.readFileSync(fullPath, 'utf-8'); + charset = extractCharset(extention, fileContent, config.charset); + if (!config.headers) { + config.headers = []; + } - if (config.charset) { config.headers.push({ - source: (request.url === '/' ? 'index.html' : ''), + source: relativePath, headers: [{ key: 'Content-Type', - value: `text/${extention}; charset=${config.charset}` - }] - }); - } else { - config.headers.push({ - source: (request.url === '/' ? 'index.html' : ''), - headers: [{ - key: 'Content-Type', - value: scanContentTypeCharset(fullPath, extention) + value: `text/${extention}; charset=${config.charset ? config.charset : charset}` }] }); + } catch (err) { + console.log(''); } - } + } // if (allowExt.indexOf(extention) >= 0) return handler(request, response, config); }); From d5a019ffedbc5e1dbb3e8c350739607551e3399e Mon Sep 17 00:00:00 2001 From: SylRob Date: Tue, 16 Jul 2019 13:33:57 +0900 Subject: [PATCH 3/3] delegate charset detection to the "chardet" library --- bin/serve.js | 25 +++++-------------------- package.json | 1 + 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/bin/serve.js b/bin/serve.js index 8c1a4285..b52c99cf 100755 --- a/bin/serve.js +++ b/bin/serve.js @@ -18,6 +18,7 @@ const handler = require('serve-handler'); const schema = require('@zeit/schemas/deployment/config-static'); const boxen = require('boxen'); const compression = require('compression'); +const chardet = require('chardet'); // Utilities const pkg = require('../package'); @@ -169,28 +170,12 @@ const getNetworkAddress = () => { } }; -const scanContentTypeCharset = (content, ext) => { - const defaultCharset = 'utf-8'; - let charset = []; - let matcher = null; - - if (ext === 'html' || ext === 'htm') { - matcher = /]*?charset\s*=[\s"']*([^\s"'/>]*)/; - } else if (ext === 'css') { - matcher = /@charset\s*[\s"']*([^\s"'/>]*)/; - } - charset = content.match(matcher); - charset = charset && charset.length > 1 ? charset[1].toLowerCase() : defaultCharset; - - return charset; -}; - -const extractCharset = (extention, fileContent, forcedCharset) => { +const extractCharset = (filePath, forcedCharset) => { if (forcedCharset) { return forcedCharset; } - return scanContentTypeCharset(fileContent, extention); + return chardet.detectFileSync(filePath); }; const realPath = (relativePath) => { @@ -245,8 +230,8 @@ const startEndpoint = (endpoint, config, args, previous) => { let charset = 'utf-8'; if (allowExt.indexOf(extention) >= 0) { try { - const fileContent = fs.readFileSync(fullPath, 'utf-8'); - charset = extractCharset(extention, fileContent, config.charset); + const tempChar = extractCharset(fullPath, config.charset); + charset = config.charset ? config.charset : (tempChar ? tempChar : 'utf-8'); if (!config.headers) { config.headers = []; } diff --git a/package.json b/package.json index 9f1d50ae..3c18269e 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "chalk": "2.4.1", "clipboardy": "1.2.3", "compression": "1.7.3", + "chardet": "0.8.0", "serve-handler": "6.0.2", "update-check": "1.5.2" }