// Copyright 2013 Selenium committers // Copyright 2013 Software Freedom Conservancy // // Licensed 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'; var fs = require('fs'), http = require('http'), path = require('path'), url = require('url'); var Server = require('./httpserver').Server, resources = require('./resources'), promise = require('../..').promise, isDevMode = require('../../_base').isDevMode(), string = require('../../_base').require('goog.string'); var WEB_ROOT = isDevMode ? '/common/src/web' : '/common'; var JS_ROOT = '/javascript'; var baseDirectory = resources.locate('.'), server = new Server(onRequest); var Pages = (function() { var pages = {}; function addPage(page, path) { pages.__defineGetter__(page, function() { return exports.whereIs(path); }); } addPage('ajaxyPage', 'ajaxy_page.html'); addPage('alertsPage', 'alerts.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('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('redirectPage', 'redirect'); addPage('resultPage', 'resultPage.html'); addPage('richTextPage', 'rich_text.html'); addPage('selectableItemsPage', 'selectableItems.html'); addPage('selectPage', 'selectPage.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('xhtmlTestPage', 'xhtmlTest.html'); return pages; })(); var Path = { BASIC_AUTH: WEB_ROOT + '/basicAuth', MANIFEST: WEB_ROOT + '/manifest', REDIRECT: WEB_ROOT + '/redirect', PAGE: WEB_ROOT + '/page', SLEEP: WEB_ROOT + '/sleep', UPLOAD: WEB_ROOT + '/upload' }; /** * HTTP request handler. * @param {!http.ServerRequest} request The request object. * @param {!http.ServerResponse} response The response object. */ function onRequest(request, response) { if (request.method !== 'GET' && request.method !== 'HEAD') { response.writeHead(405, {'Allowed': 'GET,HEAD'}); return response.end(); } var pathname = path.resolve(url.parse(request.url).pathname); if (pathname === '/') { return sendIndex(request, response); } if (pathname === '/favicon.ico') { response.writeHead(204); return response.end(); } switch (pathname) { case Path.BASIC_AUTH: return sendBasicAuth(response); case Path.MANIFEST: return sendManifest(response); case Path.PAGE: return sendInifinitePage(request, response); case Path.REDIRECT: return redirectToResultPage(response); case Path.SLEEP: return sendDelayedResponse(request, response); case Path.UPLOAD: return sendUpload(response); } if (string.startsWith(pathname, Path.PAGE + '/')) { return sendInifinitePage(request, response); } if ((string.startsWith(pathname, WEB_ROOT) || string.startsWith(pathname, JS_ROOT)) && string.endsWith(pathname, '.appcache')) { return sendManifest(response); } if (string.startsWith(pathname, WEB_ROOT)) { if (!isDevMode) { pathname = pathname.substring(WEB_ROOT.length); } } else if (string.startsWith(pathname, JS_ROOT)) { if (!isDevMode) { sendSimpleError(response, 404, request.url); return; } pathname = pathname.substring(JS_ROOT); } try { var fullPath = resources.locate(pathname); } catch (ex) { fullPath = ''; } if (fullPath.lastIndexOf(baseDirectory, 0) == -1) { sendSimpleError(response, 404, request.url); return; } fs.stat(fullPath, function(err, stats) { if (err) { sendIOError(request, response, err); } else if (stats.isDirectory()) { sendDirectoryListing(request, response, fullPath); } else if (stats.isFile()) { sendFile(request, response, fullPath); } else { sendSimpleError(response, 404, request.url); } }); } function redirectToResultPage(response) { response.writeHead(303, { Location: Pages.resultPage }); return response.end(); } function sendInifinitePage(request, response) { setTimeout(function() { 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); }, 500); } function sendDelayedResponse(request, response) { var duration = 0; var query = url.parse(request.url).query || ''; var match = query.match(/\btime=(\d+)/); if (match) { duration = parseInt(match[1]); } 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); } /** * Sends an error in response to an I/O operation. * @param {!http.ServerRequest} request The request object. * @param {!http.ServerResponse} response The response object. * @param {!Error} err The I/O error. */ function sendIOError(request, response, err) { var code = 500; if (err.code === 'ENOENT') { code = 404; } else if (err.code === 'EACCES') { code = 403; } sendSimpleError(response, code, request.url); } /** * Sends a simple error message to the client and instructs it to close the * connection. * @param {!http.ServerResponse} response The response to populate. * @param {number} code The numeric HTTP code to send. * @param {string} message The error message. */ function sendSimpleError(response, code, message) { response.writeHead(code, { 'Content-Type': 'text/html; charset=utf-8', 'Connection': 'close' }); response.end( '

' + code + ' ' + http.STATUS_CODES[code] + '


' + message); } var MimeType = { 'css': 'text/css', 'gif': 'image/gif', 'html': 'text/html', 'js': 'application/javascript', 'png': 'image/png', 'svg': 'image/svg+xml', 'txt': 'text/plain', 'xhtml': 'application/xhtml+xml', 'xsl': 'application/xml', 'xml': 'application/xml' }; /** * Responds to a request for an individual file. * @param {!http.ServerRequest} request The request object. * @param {!http.ServerResponse} response The response object. * @param {string} filePath Path to the file to return. */ function sendFile(request, response, filePath) { fs.readFile(filePath, function(err, buffer) { if (err) { sendIOError(request, response, err); return; } var index = filePath.lastIndexOf('.'); var type = MimeType[index < 0 ? '' : filePath.substring(index + 1)]; var headers = {'Content-Length': buffer.length}; if (type) headers['Content-Type'] = type; response.writeHead(200, headers); response.end(buffer); }); } /** * 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) { 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); } /** * Responds to a request for a directory listing. * @param {!http.ServerRequest} request The request object. * @param {!http.ServerResponse} response The response object. * @param {string} dirPath Path to the directory to generate a listing for. */ function sendDirectoryListing(request, response, dirPath) { var pathname = url.parse(request.url).pathname; var host = request.headers.host; if (!host) { host = server.host(); } var requestUrl = ['http://' + host + pathname].join(''); if (requestUrl[requestUrl.length - 1] !== '/') { response.writeHead(303, {'Location': requestUrl + '/'}); return response.end(); } fs.readdir(dirPath, function(err, files) { if (err) { sendIOError(request, response, err); return; } var data = ['

    ', pathname, '