diff --git a/share/tools/web_config/js/controllers.js b/share/tools/web_config/js/controllers.js index 7db84a35a..cc4452b55 100644 --- a/share/tools/web_config/js/controllers.js +++ b/share/tools/web_config/js/controllers.js @@ -83,7 +83,7 @@ controllers.controller("colorsController", function($scope, $http) { $scope.getCurrentTheme = function() { - $http.get("/colors/").success(function(data, status, headers, config) { + $http.get("colors/").success(function(data, status, headers, config) { var currentScheme = { "name": "Current", "colors":[], "preferred_background": "" }; for (var i in data) { currentScheme[data[i].name] = data[i].color; @@ -103,7 +103,7 @@ controllers.controller("colorsController", function($scope, $http) { var remaining = settingNames.length; for (name in settingNames) { var postData = "what=" + settingNames[name] + "&color=" + $scope.selectedColorScheme[settingNames[name]] + "&background_color=&bold=&underline="; - $http.post("/set_color/", postData, { headers: {'Content-Type': 'application/x-www-form-urlencoded'} }).success(function(data, status, headers, config) { + $http.post("set_color/", postData, { headers: {'Content-Type': 'application/x-www-form-urlencoded'} }).success(function(data, status, headers, config) { if (status == 200) { remaining -= 1; if (remaining == 0) { @@ -124,7 +124,7 @@ controllers.controller("promptController", function($scope, $http) { $scope.savePromptButtonTitle = "Set Prompt"; $scope.fetchSamplePrompts= function() { - $http.get("/sample_prompts/").success(function(data, status, headers, config) { + $http.get("sample_prompts/").success(function(data, status, headers, config) { $scope.samplePrompts = data; $scope.samplePromptsArrayArray = get_colors_as_nested_array($scope.samplePrompts, 1); @@ -140,7 +140,7 @@ controllers.controller("promptController", function($scope, $http) { } $scope.setNewPrompt = function(selectedPrompt) { - $http.post("/set_prompt/","what=" + encodeURIComponent(selectedPrompt.function), { headers: {'Content-Type': 'application/x-www-form-urlencoded'} }).success(function(data, status, headers, config){ + $http.post("set_prompt/","what=" + encodeURIComponent(selectedPrompt.function), { headers: {'Content-Type': 'application/x-www-form-urlencoded'} }).success(function(data, status, headers, config){ // Update attributes of current prompt and select it $scope.samplePrompts[0].demo = selectedPrompt.demo; @@ -171,7 +171,7 @@ controllers.controller("functionsController", function($scope, $http) { } $scope.fetchFunctions= function() { - $http.get("/functions/").success(function(data, status, headers, config) { + $http.get("functions/").success(function(data, status, headers, config) { $scope.functions = data; $scope.selectFunction($scope.functions[0]); })}; @@ -195,7 +195,7 @@ controllers.controller("functionsController", function($scope, $http) { } $scope.fetchFunctionDefinition = function(name) { - $http.post("/get_function/","what=" + name, { headers: {'Content-Type': 'application/x-www-form-urlencoded'} }).success(function(data, status, headers, config) { + $http.post("get_function/","what=" + name, { headers: {'Content-Type': 'application/x-www-form-urlencoded'} }).success(function(data, status, headers, config) { $scope.functionDefinition = $scope.cleanupFishFunction(data[0]); })}; @@ -206,7 +206,7 @@ controllers.controller("variablesController", function($scope, $http) { $scope.query = null; $scope.fetchVariables= function() { - $http.get("/variables/").success(function(data, status, headers, config) { + $http.get("variables/").success(function(data, status, headers, config) { $scope.variables = data; })}; @@ -247,7 +247,7 @@ controllers.controller("historyController", function($scope, $http, $timeout) { } // Get history from server $scope.fetchHistory = function() { - $http.get("/history/").success(function(data, status, headers, config) { + $http.get("history/").success(function(data, status, headers, config) { $scope.historySize = data.length; $scope.remainingItems = data; @@ -257,7 +257,7 @@ controllers.controller("historyController", function($scope, $http, $timeout) { $scope.deleteHistoryItem = function(item) { index = $scope.historyItems.indexOf(item); - $http.post("/delete_history_item/","what=" + encodeURIComponent(item), { headers: {'Content-Type': 'application/x-www-form-urlencoded'} }).success(function(data, status, headers, config) { + $http.post("delete_history_item/","what=" + encodeURIComponent(item), { headers: {'Content-Type': 'application/x-www-form-urlencoded'} }).success(function(data, status, headers, config) { $scope.historyItems.splice(index, 1); })}; @@ -278,7 +278,7 @@ controllers.controller("historyController", function($scope, $http, $timeout) { controllers.controller("bindingsController", function($scope, $http) { $scope.bindings = []; $scope.fetchBindings = function() { - $http.get("/bindings/").success(function(data, status, headers, config) { + $http.get("bindings/").success(function(data, status, headers, config) { $scope.bindings = data; })}; diff --git a/share/tools/web_config/webconfig.py b/share/tools/web_config/webconfig.py index c3daef5fe..55ab372ef 100755 --- a/share/tools/web_config/webconfig.py +++ b/share/tools/web_config/webconfig.py @@ -26,7 +26,7 @@ if term: os.environ['TERM'] = term import subprocess -import re, socket, cgi, select, time, glob +import re, socket, cgi, select, time, glob, random, string try: import json except ImportError: @@ -693,9 +693,16 @@ class FishConfigHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): else: font_size = '18pt' return font_size - def do_GET(self): p = self.path + + authpath = '/' + authkey + if p.startswith(authpath): + p = p[len(authpath):] + else: + return self.send_error(403) + self.path = p + if p == '/colors/': output = self.do_get_colors() elif p == '/functions/': @@ -727,6 +734,14 @@ class FishConfigHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): def do_POST(self): p = self.path + + authpath = '/' + authkey + if p.startswith(authpath): + p = p[len(authpath):] + else: + return self.send_error(403) + self.path = p + if IS_PY2: ctype, pdict = cgi.parse_header(self.headers.getheader('content-type')) else: # Python 3 @@ -788,6 +803,18 @@ class FishConfigHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): """ Disable request logging """ pass +redirect_template_html = """ + + +
+ + + + + + +""" + # find fish fish_bin_dir = os.environ.get('__fish_bin_dir') fish_bin_path = None @@ -823,6 +850,9 @@ initial_wd = os.getcwd() where = os.path.dirname(sys.argv[0]) os.chdir(where) +# Generate a 16-byte random key as a hexadecimal string +authkey = hex(random.getrandbits(16*4))[2:] + # Try to find a suitable port PORT = 8000 while PORT <= 9000: @@ -852,9 +882,36 @@ if len(sys.argv) > 1: initial_tab = '#' + tab break -url = 'http://localhost:%d/%s' % (PORT, initial_tab) -print("Web config started at '%s'. Hit enter to stop." % url) -webbrowser.open(url) +url = 'http://localhost:%d/%s/%s' % (PORT, authkey, initial_tab) + +# Create temporary file to hold redirect to real server +# This prevents exposing the URL containing the authentication key on the command line +# (see CVE-2014-2914 or https://github.com/fish-shell/fish-shell/issues/1438) +if 'XDG_CACHE_HOME' in os.environ: + dirname = os.path.expanduser(os.path.expandvars('$XDG_CACHE_HOME/fish/')) +else: + dirname = os.path.expanduser('~/.cache/fish/') + +os.umask(0o0077) +try: + os.makedirs(dirname, 0o0700) +except OSError as e: + if e.errno == 17: + pass + else: + raise e + +randtoken = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6)) +filename = dirname + 'web_config-%s.html' % randtoken + +f = open(filename, 'w') +f.write(redirect_template_html % (url, url)) +f.close() + +# Open temporary file as URL +fileurl = 'file://' + filename +print("Web config started at '%s'. Hit enter to stop." % fileurl) +webbrowser.open(fileurl) # Select on stdin and httpd stdin_no = sys.stdin.fileno() @@ -871,3 +928,5 @@ try: except KeyboardInterrupt: print("\nShutting down.") +# Clean up temporary file +os.remove(filename)