// Licensed to the Software Freedom Conservancy (SFC) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The SFC licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. 'use strict' const path = require('node:path') const url = require('node:url') const express = require('express') const multer = require('multer') const serveIndex = require('serve-index') const { isDevMode } = require('./build') const resources = require('./resources') const { Server } = require('./httpserver') const WEB_ROOT = '/common' const DATA_ROOT = '/data' const JS_ROOT = '/javascript' const baseDirectory = resources.locate('common/src/web') const dataDirectory = path.join(__dirname, 'data') const jsDirectory = resources.locate('javascript') const Pages = (function () { let pages = {} function addPage(page, path) { pages.__defineGetter__(page, function () { return exports.whereIs(path) }) } addPage('ajaxyPage', 'ajaxy_page.html') addPage('alertsPage', 'alerts.html') addPage('basicAuth', 'basicAuth') addPage('blankPage', 'blank.html') addPage('bodyTypingPage', 'bodyTypingTest.html') addPage('booleanAttributes', 'booleanAttributes.html') addPage('childPage', 'child/childPage.html') addPage('chinesePage', 'cn-test.html') addPage('clickJacker', 'click_jacker.html') addPage('clickEventPage', 'clickEventPage.html') addPage('clicksPage', 'clicks.html') addPage('colorPage', 'colorPage.html') addPage('deletingFrame', 'deletingFrame.htm') addPage('draggableLists', 'draggableLists.html') addPage('dragAndDropPage', 'dragAndDropTest.html') addPage('droppableItems', 'droppableItems.html') addPage('documentWrite', 'document_write_in_onload.html') addPage('dynamicallyModifiedPage', 'dynamicallyModifiedPage.html') addPage('dynamicPage', 'dynamic.html') addPage('echoPage', 'echo') addPage('errorsPage', 'errors.html') addPage('xhtmlFormPage', 'xhtmlFormPage.xhtml') addPage('formPage', 'formPage.html') addPage('formSelectionPage', 'formSelectionPage.html') addPage('framesetPage', 'frameset.html') addPage('grandchildPage', 'child/grandchild/grandchildPage.html') addPage('html5Page', 'html5Page.html') addPage('html5OfflinePage', 'html5/offline.html') addPage('iframePage', 'iframes.html') addPage('javascriptEnhancedForm', 'javascriptEnhancedForm.html') addPage('javascriptPage', 'javascriptPage.html') addPage('linkedImage', 'linked_image.html') addPage('longContentPage', 'longContentPage.html') addPage('macbethPage', 'macbeth.html') addPage('mapVisibilityPage', 'map_visibility.html') addPage('metaRedirectPage', 'meta-redirect.html') addPage('missedJsReferencePage', 'missedJsReference.html') addPage('mouseTrackerPage', 'mousePositionTracker.html') addPage('nestedPage', 'nestedElements.html') addPage('readOnlyPage', 'readOnlyPage.html') addPage('rectanglesPage', 'rectangles.html') addPage('relativeLocators', 'relative_locators.html') addPage('redirectPage', 'redirect') addPage('resultPage', 'resultPage.html') addPage('richTextPage', 'rich_text.html') addPage('printPage', 'printPage.html') addPage('scrollingPage', 'scrollingPage.html') addPage('selectableItemsPage', 'selectableItems.html') addPage('selectPage', 'selectPage.html') addPage('selectSpacePage', 'select_space.html') addPage('simpleTestPage', 'simpleTest.html') addPage('simpleXmlDocument', 'simple.xml') addPage('sleepingPage', 'sleep') addPage('slowIframes', 'slow_loading_iframes.html') addPage('slowLoadingAlertPage', 'slowLoadingAlert.html') addPage('svgPage', 'svgPiechart.xhtml') addPage('tables', 'tables.html') addPage('underscorePage', 'underscore.html') addPage('unicodeLtrPage', 'utf8/unicode_ltr.html') addPage('uploadPage', 'upload.html') addPage('veryLargeCanvas', 'veryLargeCanvas.html') addPage('webComponents', 'webComponents.html') addPage('xhtmlTestPage', 'xhtmlTest.html') addPage('uploadInvisibleTestPage', 'upload_invisible.html') addPage('userpromptPage', 'userprompt.html') addPage('virtualAuthenticator', 'virtual-authenticator.html') addPage('logEntryAdded', 'bidi/logEntryAdded.html') addPage('scriptTestAccessProperty', 'bidi/scriptTestAccessProperty.html') addPage('scriptTestRemoveProperty', 'bidi/scriptTestRemoveProperty.html') addPage('emptyPage', 'bidi/emptyPage.html') addPage('emptyText', 'bidi/emptyText.txt') addPage('redirectedHttpEquiv', 'bidi/redirected_http_equiv.html') addPage('releaseAction', 'bidi/release_action.html') addPage('fedcm', 'fedcm/fedcm_async_js.html') return pages })() const Path = { BASIC_AUTH: WEB_ROOT + '/basicAuth', ECHO: WEB_ROOT + '/echo', GENERATED: WEB_ROOT + '/generated', MANIFEST: WEB_ROOT + '/manifest', REDIRECT: WEB_ROOT + '/redirect', PAGE: WEB_ROOT + '/page', SLEEP: WEB_ROOT + '/sleep', UPLOAD: WEB_ROOT + '/upload', } var app = express() app .get('/', sendIndex) .get('/favicon.ico', function (_req, res) { res.writeHead(204) res.end() }) .use(JS_ROOT, serveIndex(jsDirectory), express.static(jsDirectory)) .post(Path.UPLOAD, handleUpload) .use(WEB_ROOT, serveIndex(baseDirectory), express.static(baseDirectory)) .use(DATA_ROOT, serveIndex(dataDirectory), express.static(dataDirectory)) .get(Path.ECHO, sendEcho) .get(Path.PAGE, sendInifinitePage) .get(Path.PAGE + '/*', sendInifinitePage) .get(Path.REDIRECT, redirectToResultPage) .get(Path.SLEEP, sendDelayedResponse) .get(Path.BASIC_AUTH, sendBasicAuth) if (isDevMode()) { var closureDir = resources.locate('third_party/closure/goog') app.use('/third_party/closure/goog', serveIndex(closureDir), express.static(closureDir)) } var server = new Server(app) function redirectToResultPage(_, response) { response.writeHead(303, { Location: Pages.resultPage, }) return response.end() } function sendInifinitePage(request, response) { // eslint-disable-next-line n/no-deprecated-api var pathname = url.parse(request.url).pathname var lastIndex = pathname.lastIndexOf('/') var pageNumber = lastIndex == -1 ? 'Unknown' : pathname.substring(lastIndex + 1) var body = [ '', 'Page', pageNumber, '', 'Page number ', pageNumber, '', '

top', ].join('') response.writeHead(200, { 'Content-Length': Buffer.byteLength(body, 'utf8'), 'Content-Type': 'text/html; charset=utf-8', }) response.end(body) } function sendBasicAuth(request, response) { const denyAccess = function () { response.writeHead(401, { 'WWW-Authenticate': 'Basic realm="test"' }) response.end('Access denied') } const basicAuthRegExp = /^\s*basic\s+([a-z0-9\-._~+/]+)=*\s*$/i const auth = request.headers.authorization const match = basicAuthRegExp.exec(auth || '') if (!match) { denyAccess() return } const userNameAndPass = Buffer.from(match[1], 'base64').toString() const parts = userNameAndPass.split(':', 2) if (parts[0] !== 'genie' || parts[1] !== 'bottle') { denyAccess() return } response.writeHead(200, { 'content-type': 'text/plain' }) response.end('Access granted!') } function sendDelayedResponse(request, response) { var duration = 0 // eslint-disable-next-line n/no-deprecated-api var query = url.parse(request.url).search.substr(1) || '' var match = query.match(/\btime=(\d+)/) if (match) { duration = parseInt(match[1], 10) } setTimeout(function () { var body = ['', 'Done', 'Slept for ', duration, 's'].join('') response.writeHead(200, { 'Content-Length': Buffer.byteLength(body, 'utf8'), 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-cache', Pragma: 'no-cache', Expires: 0, }) response.end(body) }, duration * 1000) } function handleUpload(request, response) { let upload = multer({ storage: multer.memoryStorage() }).any() upload(request, response, function (err) { if (err) { response.writeHead(500) response.end(err + '') } else { if (!request.files) { return response.status(400).send('No files were uploaded') } let files = [] let keys = Object.keys(request.files) keys.forEach((file) => { files.push(request.files[file].originalname) }) response .status(200) .contentType('html') .send(files.join('\n') + '\n') } }) } function sendEcho(request, response) { if (request.query['html']) { const html = request.query['html'] if (html) { response.writeHead(200, { 'Content-Length': Buffer.byteLength(html, 'utf8'), 'Content-Type': 'text/html; charset=utf-8', }) response.end(html) return } } var body = [ '', 'Echo', '

', request.method, ' ', request.url, ' ', 'HTTP/', request.httpVersion, '
', ] for (var name in request.headers) { body.push('
', name, ': ', request.headers[name], '
') } body = body.join('') response.writeHead(200, { 'Content-Length': Buffer.byteLength(body, 'utf8'), 'Content-Type': 'text/html; charset=utf-8', }) response.end(body) } /** * Responds to a request for the file server's main index. * @param {!http.ServerRequest} request The request object. * @param {!http.ServerResponse} response The response object. */ function sendIndex(request, response) { // eslint-disable-next-line n/no-deprecated-api var pathname = url.parse(request.url).pathname var host = request.headers.host if (!host) { host = server.host() } var requestUrl = ['http://' + host + pathname].join('') function createListEntry(path) { var url = requestUrl + path return ['
  • ', path, ''].join('') } var data = ['

    /


    ') data = data.join('') response.writeHead(200, { 'Content-Type': 'text/html; charset=UTF-8', 'Content-Length': Buffer.byteLength(data, 'utf8'), }) response.end(data) } /** * Detects the hostname. * @return {string} The detected hostname or 'localhost' if not found. */ function getHostName() { const hostnameFromEnv = process.env.HOSTNAME return hostnameFromEnv ? hostnameFromEnv : 'localhost' } // PUBLIC application /** * Starts the server on the specified port. * @param {number=} opt_port The port to use, or 0 for any free port. * @return {!Promise} A promise that will resolve * with the server host when it has fully started. */ exports.start = server.start.bind(server) /** * Stops the server. * @return {!Promise} A promise that will resolve when the * server has closed all connections. */ exports.stop = server.stop.bind(server) /** * Formats a URL for this server. * @param {string=} opt_pathname The desired pathname on the server. * @return {string} The formatted URL. * @throws {Error} If the server is not running. */ exports.url = server.url.bind(server) /** * Builds the URL for a file in the //common/src/web directory of the * Selenium client. * @param {string} filePath A path relative to //common/src/web to compute a * URL for. * @return {string} The formatted URL. * @throws {Error} If the server is not running. */ exports.whereIs = function (filePath) { filePath = filePath.replace(/\\/g, '/') if (!filePath.startsWith('/')) { filePath = `${WEB_ROOT}/${filePath}` } return server.url(filePath) } exports.getHostName = getHostName exports.Pages = Pages if (require.main === module) { server.start(2310).then(function () { console.log('Server running at ' + server.url()) }) }