Path: blob/trunk/javascript/selenium-webdriver/lib/test/fileserver.js
2884 views
// Licensed to the Software Freedom Conservancy (SFC) under one1// or more contributor license agreements. See the NOTICE file2// distributed with this work for additional information3// regarding copyright ownership. The SFC licenses this file4// to you under the Apache License, Version 2.0 (the5// "License"); you may not use this file except in compliance6// with the License. You may obtain a copy of the License at7//8// http://www.apache.org/licenses/LICENSE-2.09//10// Unless required by applicable law or agreed to in writing,11// software distributed under the License is distributed on an12// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY13// KIND, either express or implied. See the License for the14// specific language governing permissions and limitations15// under the License.1617'use strict'1819const path = require('node:path')20const url = require('node:url')2122const express = require('express')23const multer = require('multer')24const serveIndex = require('serve-index')2526const { isDevMode } = require('./build')27const resources = require('./resources')28const { Server } = require('./httpserver')2930const WEB_ROOT = '/common'31const DATA_ROOT = '/data'32const JS_ROOT = '/javascript'3334const baseDirectory = resources.locate('common/src/web')35const dataDirectory = path.join(__dirname, 'data')36const jsDirectory = resources.locate('javascript')3738const Pages = (function () {39let pages = {}4041function addPage(page, path) {42pages.__defineGetter__(page, function () {43return exports.whereIs(path)44})45}4647addPage('ajaxyPage', 'ajaxy_page.html')48addPage('alertsPage', 'alerts.html')49addPage('basicAuth', 'basicAuth')50addPage('blankPage', 'blank.html')51addPage('bodyTypingPage', 'bodyTypingTest.html')52addPage('booleanAttributes', 'booleanAttributes.html')53addPage('childPage', 'child/childPage.html')54addPage('chinesePage', 'cn-test.html')55addPage('clickJacker', 'click_jacker.html')56addPage('clickEventPage', 'clickEventPage.html')57addPage('clicksPage', 'clicks.html')58addPage('colorPage', 'colorPage.html')59addPage('deletingFrame', 'deletingFrame.htm')60addPage('draggableLists', 'draggableLists.html')61addPage('dragAndDropPage', 'dragAndDropTest.html')62addPage('droppableItems', 'droppableItems.html')63addPage('documentWrite', 'document_write_in_onload.html')64addPage('dynamicallyModifiedPage', 'dynamicallyModifiedPage.html')65addPage('dynamicPage', 'dynamic.html')66addPage('echoPage', 'echo')67addPage('errorsPage', 'errors.html')68addPage('xhtmlFormPage', 'xhtmlFormPage.xhtml')69addPage('formPage', 'formPage.html')70addPage('formSelectionPage', 'formSelectionPage.html')71addPage('framesetPage', 'frameset.html')72addPage('grandchildPage', 'child/grandchild/grandchildPage.html')73addPage('html5Page', 'html5Page.html')74addPage('html5OfflinePage', 'html5/offline.html')75addPage('iframePage', 'iframes.html')76addPage('javascriptEnhancedForm', 'javascriptEnhancedForm.html')77addPage('javascriptPage', 'javascriptPage.html')78addPage('linkedImage', 'linked_image.html')79addPage('longContentPage', 'longContentPage.html')80addPage('macbethPage', 'macbeth.html')81addPage('mapVisibilityPage', 'map_visibility.html')82addPage('metaRedirectPage', 'meta-redirect.html')83addPage('missedJsReferencePage', 'missedJsReference.html')84addPage('mouseTrackerPage', 'mousePositionTracker.html')85addPage('nestedPage', 'nestedElements.html')86addPage('readOnlyPage', 'readOnlyPage.html')87addPage('rectanglesPage', 'rectangles.html')88addPage('relativeLocators', 'relative_locators.html')89addPage('redirectPage', 'redirect')90addPage('resultPage', 'resultPage.html')91addPage('richTextPage', 'rich_text.html')92addPage('printPage', 'printPage.html')93addPage('scrollingPage', 'scrollingPage.html')94addPage('selectableItemsPage', 'selectableItems.html')95addPage('selectPage', 'selectPage.html')96addPage('selectSpacePage', 'select_space.html')97addPage('simpleTestPage', 'simpleTest.html')98addPage('simpleXmlDocument', 'simple.xml')99addPage('sleepingPage', 'sleep')100addPage('slowIframes', 'slow_loading_iframes.html')101addPage('slowLoadingAlertPage', 'slowLoadingAlert.html')102addPage('svgPage', 'svgPiechart.xhtml')103addPage('tables', 'tables.html')104addPage('underscorePage', 'underscore.html')105addPage('unicodeLtrPage', 'utf8/unicode_ltr.html')106addPage('uploadPage', 'upload.html')107addPage('veryLargeCanvas', 'veryLargeCanvas.html')108addPage('webComponents', 'webComponents.html')109addPage('xhtmlTestPage', 'xhtmlTest.html')110addPage('uploadInvisibleTestPage', 'upload_invisible.html')111addPage('userpromptPage', 'userprompt.html')112addPage('virtualAuthenticator', 'virtual-authenticator.html')113addPage('logEntryAdded', 'bidi/logEntryAdded.html')114addPage('scriptTestAccessProperty', 'bidi/scriptTestAccessProperty.html')115addPage('scriptTestRemoveProperty', 'bidi/scriptTestRemoveProperty.html')116addPage('emptyPage', 'bidi/emptyPage.html')117addPage('emptyText', 'bidi/emptyText.txt')118addPage('redirectedHttpEquiv', 'bidi/redirected_http_equiv.html')119addPage('releaseAction', 'bidi/release_action.html')120addPage('fedcm', 'fedcm/fedcm_async_js.html')121return pages122})()123124const Path = {125BASIC_AUTH: WEB_ROOT + '/basicAuth',126ECHO: WEB_ROOT + '/echo',127GENERATED: WEB_ROOT + '/generated',128MANIFEST: WEB_ROOT + '/manifest',129REDIRECT: WEB_ROOT + '/redirect',130PAGE: WEB_ROOT + '/page',131SLEEP: WEB_ROOT + '/sleep',132UPLOAD: WEB_ROOT + '/upload',133}134135var app = express()136137app138.get('/', sendIndex)139.get('/favicon.ico', function (_req, res) {140res.writeHead(204)141res.end()142})143.use(JS_ROOT, serveIndex(jsDirectory), express.static(jsDirectory))144.post(Path.UPLOAD, handleUpload)145.use(WEB_ROOT, serveIndex(baseDirectory), express.static(baseDirectory))146.use(DATA_ROOT, serveIndex(dataDirectory), express.static(dataDirectory))147.get(Path.ECHO, sendEcho)148.get(Path.PAGE, sendInifinitePage)149.get(Path.PAGE + '/*', sendInifinitePage)150.get(Path.REDIRECT, redirectToResultPage)151.get(Path.SLEEP, sendDelayedResponse)152.get(Path.BASIC_AUTH, sendBasicAuth)153154if (isDevMode()) {155var closureDir = resources.locate('third_party/closure/goog')156app.use('/third_party/closure/goog', serveIndex(closureDir), express.static(closureDir))157}158var server = new Server(app)159160function redirectToResultPage(_, response) {161response.writeHead(303, {162Location: Pages.resultPage,163})164return response.end()165}166167function sendInifinitePage(request, response) {168// eslint-disable-next-line n/no-deprecated-api169var pathname = url.parse(request.url).pathname170var lastIndex = pathname.lastIndexOf('/')171var pageNumber = lastIndex == -1 ? 'Unknown' : pathname.substring(lastIndex + 1)172var body = [173'<!DOCTYPE html>',174'<title>Page',175pageNumber,176'</title>',177'Page number <span id="pageNumber">',178pageNumber,179'</span>',180'<p><a href="../xhtmlTest.html" target="_top">top</a>',181].join('')182response.writeHead(200, {183'Content-Length': Buffer.byteLength(body, 'utf8'),184'Content-Type': 'text/html; charset=utf-8',185})186response.end(body)187}188189function sendBasicAuth(request, response) {190const denyAccess = function () {191response.writeHead(401, { 'WWW-Authenticate': 'Basic realm="test"' })192response.end('Access denied')193}194195const basicAuthRegExp = /^\s*basic\s+([a-z0-9\-._~+/]+)=*\s*$/i196const auth = request.headers.authorization197const match = basicAuthRegExp.exec(auth || '')198if (!match) {199denyAccess()200return201}202203const userNameAndPass = Buffer.from(match[1], 'base64').toString()204const parts = userNameAndPass.split(':', 2)205if (parts[0] !== 'genie' || parts[1] !== 'bottle') {206denyAccess()207return208}209210response.writeHead(200, { 'content-type': 'text/plain' })211response.end('Access granted!')212}213214function sendDelayedResponse(request, response) {215var duration = 0216// eslint-disable-next-line n/no-deprecated-api217var query = url.parse(request.url).search.substr(1) || ''218var match = query.match(/\btime=(\d+)/)219if (match) {220duration = parseInt(match[1], 10)221}222223setTimeout(function () {224var body = ['<!DOCTYPE html>', '<title>Done</title>', '<body>Slept for ', duration, 's</body>'].join('')225response.writeHead(200, {226'Content-Length': Buffer.byteLength(body, 'utf8'),227'Content-Type': 'text/html; charset=utf-8',228'Cache-Control': 'no-cache',229Pragma: 'no-cache',230Expires: 0,231})232response.end(body)233}, duration * 1000)234}235236function handleUpload(request, response) {237let upload = multer({ storage: multer.memoryStorage() }).any()238upload(request, response, function (err) {239if (err) {240response.writeHead(500)241response.end(err + '')242} else {243if (!request.files) {244return response.status(400).send('No files were uploaded')245}246247let files = []248let keys = Object.keys(request.files)249250keys.forEach((file) => {251files.push(request.files[file].originalname)252})253254response255.status(200)256.contentType('html')257.send(files.join('\n') + '\n<script>window.top.window.onUploadDone();</script>')258}259})260}261262function sendEcho(request, response) {263if (request.query['html']) {264const html = request.query['html']265if (html) {266response.writeHead(200, {267'Content-Length': Buffer.byteLength(html, 'utf8'),268'Content-Type': 'text/html; charset=utf-8',269})270response.end(html)271return272}273}274275var body = [276'<!DOCTYPE html>',277'<title>Echo</title>',278'<div class="request">',279request.method,280' ',281request.url,282' ',283'HTTP/',284request.httpVersion,285'</div>',286]287for (var name in request.headers) {288body.push('<div class="header ', name, '">', name, ': ', request.headers[name], '</div>')289}290body = body.join('')291response.writeHead(200, {292'Content-Length': Buffer.byteLength(body, 'utf8'),293'Content-Type': 'text/html; charset=utf-8',294})295response.end(body)296}297298/**299* Responds to a request for the file server's main index.300* @param {!http.ServerRequest} request The request object.301* @param {!http.ServerResponse} response The response object.302*/303function sendIndex(request, response) {304// eslint-disable-next-line n/no-deprecated-api305var pathname = url.parse(request.url).pathname306307var host = request.headers.host308if (!host) {309host = server.host()310}311312var requestUrl = ['http://' + host + pathname].join('')313314function createListEntry(path) {315var url = requestUrl + path316return ['<li><a href="', url, '">', path, '</a>'].join('')317}318319var data = ['<!DOCTYPE html><h1>/</h1><hr/><ul>', createListEntry('common'), createListEntry('data')]320if (isDevMode()) {321data.push(createListEntry('javascript'))322}323data.push('</ul>')324data = data.join('')325326response.writeHead(200, {327'Content-Type': 'text/html; charset=UTF-8',328'Content-Length': Buffer.byteLength(data, 'utf8'),329})330response.end(data)331}332333/**334* Detects the hostname.335* @return {string} The detected hostname or 'localhost' if not found.336*/337function getHostName() {338const hostnameFromEnv = process.env.HOSTNAME339return hostnameFromEnv ? hostnameFromEnv : 'localhost'340}341342// PUBLIC application343344/**345* Starts the server on the specified port.346* @param {number=} opt_port The port to use, or 0 for any free port.347* @return {!Promise<Host>} A promise that will resolve348* with the server host when it has fully started.349*/350exports.start = server.start.bind(server)351352/**353* Stops the server.354* @return {!Promise} A promise that will resolve when the355* server has closed all connections.356*/357exports.stop = server.stop.bind(server)358359/**360* Formats a URL for this server.361* @param {string=} opt_pathname The desired pathname on the server.362* @return {string} The formatted URL.363* @throws {Error} If the server is not running.364*/365exports.url = server.url.bind(server)366367/**368* Builds the URL for a file in the //common/src/web directory of the369* Selenium client.370* @param {string} filePath A path relative to //common/src/web to compute a371* URL for.372* @return {string} The formatted URL.373* @throws {Error} If the server is not running.374*/375exports.whereIs = function (filePath) {376filePath = filePath.replace(/\\/g, '/')377if (!filePath.startsWith('/')) {378filePath = `${WEB_ROOT}/${filePath}`379}380return server.url(filePath)381}382383exports.getHostName = getHostName384385exports.Pages = Pages386387if (require.main === module) {388server.start(2310).then(function () {389console.log('Server running at ' + server.url())390})391}392393394