Hi Dave,
Please find attached rebased patch. -- *Harshal Dhumal* *Software Engineer* EnterpriseDB India: http://www.enterprisedb.com The Enterprise PostgreSQL Company On Sun, Jan 8, 2017 at 4:07 PM, Dave Page <dp...@pgadmin.org> wrote: > Hi > > Can you rebase this please? It no longer applies :-( > > > On Wednesday, December 28, 2016, Harshal Dhumal < > harshal.dhu...@enterprisedb.com> wrote: > >> Hi Dave, >> Here is updated (V4) patch. >> >> Changes: 1] Now can enter both type of slashes ( / and \ ) and all will >> get replaced with the platform standard. >> 2] Added unicode support. >> >> -- >> *Harshal Dhumal* >> *Software Engineer* >> >> EnterpriseDB India: http://www.enterprisedb.com >> The Enterprise PostgreSQL Company >> >> On Mon, Dec 19, 2016 at 5:16 PM, Dave Page <dp...@pgadmin.org> wrote: >> >>> Hi >>> >>> On Fri, Dec 16, 2016 at 6:46 PM, Harshal Dhumal >>> <harshal.dhu...@enterprisedb.com> wrote: >>> > Hi Dave, >>> > >>> > Please find updated patch below for direct file navigation. >>> > I have covered all of above mentioned case. >>> >>> Still not there I'm afraid: >>> >>> - On Mac, if I type \Users\dpage, it changes it to /\Users\dpage and >>> then tells me it doesn't exist. Per the RM, either forward or back >>> slashes should be acceptable ("The path should accept either / or \ as >>> separators. Upon successful navigation to the path (after pressing >>> Return), the slashes should be replaced with the platform standard if >>> needed.") >>> >>> - Unicode handling seems to be completely broken - see the attached >>> screenshot. >>> >>> I haven't tested on Windows yet, and only in Desktop mode on Mac. >>> Please test on Windows and Mac or Linux with both Python 2 and 3, in >>> both Server and Desktop modes to ensure that the behaviour meets the >>> requirements of the ticket with Unicode and non-Unicode paths and >>> files before resubmitting. >>> >>> Thanks. >>> >>> -- >>> Dave Page >>> Blog: http://pgsnake.blogspot.com >>> Twitter: @pgsnake >>> >>> EnterpriseDB UK: http://www.enterprisedb.com >>> The Enterprise PostgreSQL Company >>> >> >> > > -- > Dave Page > Blog: http://pgsnake.blogspot.com > Twitter: @pgsnake > > EnterpriseDB UK: http://www.enterprisedb.com > The Enterprise PostgreSQL Company > >
diff --git a/web/pgadmin/misc/file_manager/__init__.py b/web/pgadmin/misc/file_manager/__init__.py index 7040f11..28ec225 100644 --- a/web/pgadmin/misc/file_manager/__init__.py +++ b/web/pgadmin/misc/file_manager/__init__.py @@ -16,6 +16,7 @@ import string import sys import time from sys import platform as _platform +import config import simplejson as json from flask import render_template, Response, session, request as req, url_for @@ -241,6 +242,7 @@ def delete_trans_id(trans_id): data={'status': True} ) + @blueprint.route("/save_last_dir/<int:trans_id>", methods=["POST"]) @login_required def save_last_directory_visited(trans_id): @@ -259,12 +261,12 @@ class Filemanager(object): { 'Error': gettext('No permission to operate on \ specified path.'), - 'Code': -1 + 'Code': 0 } ) self.dir = get_storage_directory() - if (self.dir is not None and isinstance(self.dir, list)): + if self.dir is not None and isinstance(self.dir, list): self.dir = "" @staticmethod @@ -312,25 +314,34 @@ class Filemanager(object): folders_only = False title = "Storage Manager" - # get last visited directory, if not present then traverse in reverse order - # to find closest parent directory + # get last visited directory, if not present then traverse in reverse + # order to find closest parent directory last_dir = blueprint.last_directory_visited.get() if storage_dir is None: if last_dir is None: last_dir = "/" else: if last_dir is not None: - if len(last_dir) > 1 and last_dir.endswith('/'): + if len(last_dir) > 1 and \ + (last_dir.endswith('/') or last_dir.endswith('\\')): last_dir = last_dir[:-1] while last_dir: if os.path.exists(storage_dir + last_dir): - break; - index = last_dir.rfind('/') + break + if _platform == 'win32': + index = last_dir.rfind('\\') + else: + index = last_dir.rfind('/') last_dir = last_dir[0:index] if not last_dir: last_dir = "/" - if not last_dir.endswith('/'): - last_dir += "/" + + if _platform == 'win32': + if not last_dir.endswith('\\'): + last_dir += "\\" + else: + if not last_dir.endswith('/'): + last_dir += "/" else: last_dir = "/" @@ -426,11 +437,11 @@ class Filemanager(object): bitmask >>= 1 if (drive_name != '' and drive_name is not None and drive_name in drives): - return "{0}{1}".format(drive_name, ':/') + return u"{0}{1}".format(drive_name, ':') else: return drives # return drives if no argument is passed except Exception: - return ['C:/'] + return ['C:'] else: return '/' @@ -443,12 +454,24 @@ class Filemanager(object): path = unquote(path) if hasattr(str, 'decode'): path = unquote(path).encode('utf-8').decode('utf-8') + + try: + Filemanager.check_access_permission(dir, path) + except Exception as e: + err_msg = "Error: {0}".format(str(e)) + files = { + 'Code': 0, + 'Error': err_msg + } + return files + files = {} - if (_platform == "win32" and path == '/') and dir is None: + if (_platform == "win32" and (path == '/' or path == '\\'))\ + and dir is None: drives = Filemanager._get_drives() for drive in drives: protected = 0 - path = file_name = "{0}:/".format(drive) + path = file_name = u"{0}:".format(drive) try: drive_size = getDriveSize(path) drive_size_in_units = sizeof_fmt(drive_size) @@ -487,7 +510,7 @@ class Filemanager(object): system_path = os.path.join(os.path.join(orig_path, f)) # continue if file/folder is hidden - if (is_folder_hidden(system_path) or f.startswith('.')): + if is_folder_hidden(system_path) or f.startswith('.'): continue if hasattr(str, 'decode'): @@ -508,7 +531,7 @@ class Filemanager(object): if files_only == 'true': continue file_extension = str('dir') - user_path = "{0}/".format(user_path) + user_path = u"{0}/".format(user_path) else: # filter files based on file_type if file_type is not None and file_type != "*": @@ -537,10 +560,62 @@ class Filemanager(object): err_msg = "Error: {0}".format(e) files = { 'Code': 0, - 'err_msg': err_msg + 'Error': err_msg } return files + @staticmethod + def check_access_permission(dir, path): + if dir is None: + dir = "" + orig_path = Filemanager.get_abs_path(dir, path) + + # This translates path with relative path notations like ./ and ../ to + # absolute path. + orig_path = os.path.abspath(orig_path) + + if _platform == 'win32': + if dir[-1] == '\\': + dir = dir[:-1] + else: + if dir[-1] == '/': + dir = dir[:-1] + + # Do not allow user to access outside his storage dir in server mode. + if config.SERVER_MODE is True and not orig_path.startswith(dir): + raise Exception( + gettext(u"Access denied ({})".format(orig_path))) + return True + + @staticmethod + def get_abs_path(dir, path): + + if path.startswith('\\\\'): + return u"{}".format(path) + + if path == '/' or path == '\\': + if _platform == 'win32': + if dir.endswith('\\'): + return u"{}".format(dir) + else: + return u"{}{}".format(dir, '\\') + else: + if dir.endswith('/'): + return u"{}".format(dir) + else: + return u"{}{}".format(dir, '/') + + if dir.endswith('/') or dir.endswith('\\'): + if path.startswith('/') or path.startswith('\\'): + return u"{}{}".format(dir[:-1], path) + else: + return u"{}{}".format(dir, path) + else: + if path.startswith('/') or path.startswith('\\'): + return u"{}{}".format(dir, path) + else: + return u"{}/{}".format(dir, path) + def validate_request(self, capability): """ It validates the capability with the capabilities @@ -560,13 +635,35 @@ class Filemanager(object): if self.dir is None: self.dir = "" orig_path = u"{0}{1}".format(self.dir, path) + + try: + Filemanager.check_access_permission(self.dir, path) + except Exception as e: + thefile = { + 'Filename': split_path(path)[-1], + 'FileType': '', + 'Path': path, + 'Error': gettext("Error: {0}".format(str(e))), + 'Code': 0, + 'Info': '', + 'Properties': { + 'Date Created': '', + 'Date Modified': '', + 'Width': '', + 'Height': '', + 'Size': '' + } + } + return thefile + user_dir = path thefile = { 'Filename': split_path(orig_path)[-1], - 'File Type': '', + 'FileType': '', 'Path': user_dir, 'Error': '', - 'Code': 0, + 'Code': 1, + 'Info': '', 'Properties': { 'Date Created': '', 'Date Modified': '', @@ -577,13 +674,16 @@ class Filemanager(object): } if not path_exists(orig_path): - thefile['Error'] = gettext('File does not exist.') - return (encode_json(thefile), None, 'application/json') - - if split_path(user_dir)[-1] == '/': - thefile['File Type'] = 'Directory' + thefile['Error'] = gettext("'{}' file does not exist.".format( + split_path(orig_path)[-1])) + thefile['Code'] = -1 + return thefile + + if split_path(user_dir)[-1] == '/'\ + or os.path.isfile(orig_path) is False: + thefile['FileType'] = 'Directory' else: - thefile['File Type'] = splitext(user_dir) + thefile['FileType'] = splitext(user_dir) created = time.ctime(os.path.getctime(orig_path)) modified = time.ctime(os.path.getmtime(orig_path)) @@ -601,7 +701,7 @@ class Filemanager(object): trans_data = Filemanager.get_trasaction_selection(self.trans_id) dir = self.dir if self.dir is not None else '' if not dir.endswith('/'): - dir += '/'; + dir += '/' filelist = self.list_filesystem(dir, path, trans_data, file_type) return filelist @@ -613,10 +713,21 @@ class Filemanager(object): if not self.validate_request('rename'): return { 'Error': gettext('Not allowed'), - 'Code': 1 + 'Code': 0 } dir = self.dir if self.dir is not None else '' + + try: + Filemanager.check_access_permission(dir, old) + Filemanager.check_access_permission(dir, new) + except Exception as e: + res = { + 'Error': gettext("Error: {0}".format(str(e))), + 'Code': 0 + } + return res + # check if it's dir if old[-1] == '/': old = old[:-1] @@ -631,7 +742,6 @@ class Filemanager(object): if not path[-1] == '/': path += '/' - # newname = encode_urlpath(new) newname = new if hasattr(str, 'decode'): newname = new.encode('utf-8').decode('utf-8') @@ -645,8 +755,8 @@ class Filemanager(object): code = 1 try: os.rename(oldpath_sys, newpath_sys) - code = 0 except Exception as e: + code = 0 error_msg = "{0} {1}".format( gettext('There was an error renaming the file:'), str(e)) @@ -669,23 +779,31 @@ class Filemanager(object): if not self.validate_request('delete'): return { 'Error': gettext('Not allowed'), - 'Code': 1 + 'Code': 0 } dir = self.dir if self.dir is not None else '' path = path.encode('utf-8').decode('utf-8') if hasattr(str, 'decode') else path orig_path = u"{0}{1}".format(dir, path) + try: + Filemanager.check_access_permission(dir, path) + except Exception as e: + res = { + 'Error': gettext("Error: {0}".format(str(e))), + 'Code': 0 + } + return res + err_msg = '' code = 1 try: if os.path.isdir(orig_path): os.rmdir(orig_path) - code = 0 else: os.remove(orig_path) - code = 0 except Exception as e: + code = 0 err_msg = "Error: {0}".format(e.strerror) result = { @@ -703,7 +821,7 @@ class Filemanager(object): if not self.validate_request('upload'): return { 'Error': gettext('Not allowed'), - 'Code': 1 + 'Code': 0 } dir = self.dir if self.dir is not None else '' @@ -718,14 +836,23 @@ class Filemanager(object): path = req.form.get('currentpath').encode('utf-8').decode('utf-8') file_name = file_obj.filename.encode('utf-8').decode('utf-8') orig_path = u"{0}{1}".format(dir, path) - newName = u'{0}{1}'.format(orig_path, file_name) + newName = u"{0}{1}".format(orig_path, file_name) with open(newName, 'wb') as f: f.write(file_obj.read()) - code = 0 except Exception as e: + code = 0 err_msg = "Error: {0}".format(e.strerror) + try: + Filemanager.check_access_permission(dir, path) + except Exception as e: + res = { + 'Error': gettext("Error: {0}".format(str(e))), + 'Code': 0 + } + return res + result = { 'Path': path, 'Name': newName, @@ -745,17 +872,21 @@ class Filemanager(object): name = unquote(name) path = unquote(path) if hasattr(str, 'decode'): - name = unquote(name).encode('utf-8') - path = unquote(path).encode('utf-8') + name = name.encode('utf-8').decode('utf-8') + path = path.encode('utf-8').decode('utf-8') try: - orig_path = "{0}{1}".format(dir, path) - newName = '{0}{1}'.format(orig_path, name) - if os.path.exists(newName): + orig_path = u"{0}{1}".format(dir, path) + Filemanager.check_access_permission(dir, u"{}{}".format(path, name)) + + newName = u"{0}{1}".format(orig_path, name) + if not os.path.exists(newName): code = 0 - else: - code = 1 except Exception as e: - err_msg = "Error: {0}".format(e.strerror) + code = 0 + if hasattr(e, 'strerror'): + err_msg = "Error: {0}".format(e.strerror) + else: + err_msg = "Error: {0}".format(str(e)) result = { 'Path': path, @@ -792,7 +923,7 @@ class Filemanager(object): if not self.validate_request('create'): return { 'Error': gettext('Not allowed'), - 'Code': 1 + 'Code': 0 } dir = self.dir if self.dir is not None else '' @@ -800,6 +931,16 @@ class Filemanager(object): if hasattr(str, 'decode'): newName = name.encode('utf-8') + try: + Filemanager.check_access_permission(dir, u"{}{}".format( + path, newName)) + except Exception as e: + res = { + 'Error': gettext("Error: {0}".format(str(e))), + 'Code': 0 + } + return res + if dir != "": if hasattr(str, 'decode'): newPath = dir + '/' + path + newName.decode('utf-8') + '/' @@ -816,15 +957,15 @@ class Filemanager(object): if not path_exists(newPath): try: os.mkdir(newPath) - code = 0 except Exception as e: + code = 0 err_msg = "Error: {0}".format(e.strerror) else: newPath, newName = self.getNewName(dir, path, newName) try: os.mkdir(newPath) - code = 0 except Exception as e: + code = 0 err_msg = "Error: {0}".format(e.strerror) result = { @@ -843,21 +984,42 @@ class Filemanager(object): if not self.validate_request('download'): return { 'Error': gettext('Not allowed'), - 'Code': 1 + 'Code': 0 } dir = self.dir if self.dir is not None else '' + if hasattr(str, 'decode'): path = path.encode('utf-8') orig_path = u"{0}{1}".format(dir, path.decode('utf-8')) else: - orig_path = "{0}{1}".format(dir, path) + orig_path = u"{0}{1}".format(dir, path) + + try: + Filemanager.check_access_permission(dir, u"{}{}".format( + path, path)) + except Exception as e: + resp = Response(gettext("Error: {0}".format(str(e)))) + resp.headers['Content-Disposition'] = 'attachment; filename=' + name + return resp + name = path.split('/')[-1] content = open(orig_path, 'rb') resp = Response(content) resp.headers['Content-Disposition'] = 'attachment; filename=' + name return resp + def permission(self, path=None, req=None): + dir = self.dir if self.dir is not None else '' + res = {'Code': 1} + try: + Filemanager.check_access_permission(dir, path) + except Exception as e: + err_msg = "Error: {0}".format(str(e)) + res['Code'] = 0 + res['Error'] = err_msg + return res + @blueprint.route("/filemanager/<int:trans_id>/", methods=["GET", "POST"]) @login_required diff --git a/web/pgadmin/misc/file_manager/static/css/file_manager.css b/web/pgadmin/misc/file_manager/static/css/file_manager.css index ea502ac..56d9c38 100755 --- a/web/pgadmin/misc/file_manager/static/css/file_manager.css +++ b/web/pgadmin/misc/file_manager/static/css/file_manager.css @@ -15,19 +15,17 @@ top: 35px; } -#uploader h1 { +#uploader .input-path { font-size: 14px; margin: 0; - margin-left: 5px; padding: 0; display: block; float: left; text-align: left; - line-height:1.9em; - max-width: 367px; + line-height:1.6em; + width: calc(100% - 72px); text-overflow: ellipsis; overflow: hidden; - color: #999; } #uploader h1 b { diff --git a/web/pgadmin/misc/file_manager/templates/file_manager/index.html b/web/pgadmin/misc/file_manager/templates/file_manager/index.html index 197386f..992fe5d 100755 --- a/web/pgadmin/misc/file_manager/templates/file_manager/index.html +++ b/web/pgadmin/misc/file_manager/templates/file_manager/index.html @@ -5,13 +5,14 @@ </head> <body> <div class="file_manager"> - <form id="uploader" method="post" class='col-xs-12'> + <div id="uploader" class='col-xs-12'> <div class="btn-group filemanager-path-group col-sm-7 col-xs-12" role="group"> <button name="home" type="button" value="Home" title="Home" class="fa fa-home btn home"><span></span> </button> <button name="level-up" type="button" title="Back" value="LevelUp" class="btn fa fa-level-up level-up" disabled><span></span></button> - <h1 title=''></h1> + <input class='input-path' title='' type="text"/> + </div> <div class="btn-group filemanager-btn-group" role="group"> <div class="uploadresponse"></div> @@ -29,7 +30,7 @@ <button class="ON fa fa-th btn grid" type="button" title="View as grid"><span></span></button> <button type="button" class="btn fa fa-list list" title="View as Table"><span></span></button> </div> - </form> + </div> <div class="fileinfo"> <span class="activity"> <img src="{{ url_for('browser.static', filename='css/aciTree/image/load-root.gif') }}"> diff --git a/web/pgadmin/misc/file_manager/templates/file_manager/js/file_manager.js b/web/pgadmin/misc/file_manager/templates/file_manager/js/file_manager.js index 412e6c8..8558d3c 100644 --- a/web/pgadmin/misc/file_manager/templates/file_manager/js/file_manager.js +++ b/web/pgadmin/misc/file_manager/templates/file_manager/js/file_manager.js @@ -47,9 +47,9 @@ define([ }); }; - var set_last_traversed_dir = function(path, _url) { + var set_last_traversed_dir = function(path, trans_id) { return $.ajax({ - url: _url, + url: "{{ url_for('file_manager.index') }}save_last_dir/" + trans_id, type: 'POST', data: JSON.stringify(path), contentType: 'application/json' @@ -90,6 +90,7 @@ define([ return { main: function(params) { // Set title and button name + var self = this; if (_.isUndefined(params['dialog_title'])) { params['dialog_title'] = 'Storage manager'; } @@ -105,6 +106,11 @@ define([ renderStoragePanel(params); this.elements.dialog.style.minWidth = '630px'; this.show(); + setTimeout(function() { + $($container.find('.file_manager')).on('enter-key', function() { + $($(self.elements.footer).find('.file_manager_ok')).trigger('click') + }); + }, 200); }, settings: { label: undefined @@ -142,25 +148,21 @@ define([ }, callback: function(closeEvent) { if (closeEvent.button.text == "{{ _('Select') }}") { - if($('.fileinfo').data('view') == 'grid') { - sel_file = $('.fileinfo').find('#contents li.selected p span').attr('title'); - } else { - sel_file = $('.fileinfo tbody tr.selected td p span').attr('title'); - } - var newFile = $('.currentpath').val() + sel_file; + var newFile = $('.storage_dialog #uploader .input-path').val(), + file_data = {'path': $('.currentpath').val()}; pgAdmin.Browser.Events.trigger('pgadmin-storage:finish_btn:storage_dialog', newFile); - var _Url = "{{ url_for('file_manager.index') }}" + "save_last_dir/" + trans_id; - var file_data = { - 'path': $('.currentpath').val() - }; - set_last_traversed_dir(file_data, _Url); + set_last_traversed_dir(file_data, trans_id); + var innerbody = $(this.elements.body).find('.storage_content') + $(innerbody).find('*').off(); + innerbody.remove(); + removeTransId(trans_id); } else if (closeEvent.button.text == "{{ _('Cancel') }}") { - if (removeTransId(trans_id)) { - this.destroy(); - return; - } + var innerbody = $(this.elements.body).find('.storage_content') + $(innerbody).find('*').off(); + innerbody.remove(); + removeTransId(trans_id); } }, build: function() { @@ -210,6 +212,7 @@ define([ return { main: function(params) { // Set title and button name + var self = this; if (_.isUndefined(params['dialog_title'])) { params['dialog_title'] = 'Select file'; } @@ -225,6 +228,11 @@ define([ renderStoragePanel(params); this.elements.dialog.style.minWidth = '630px'; this.show(); + setTimeout(function() { + $($container.find('.file_manager')).on('enter-key', function() { + $($(self.elements.footer).find('.file_manager_ok')).trigger('click') + }); + }, 200); }, settings: { label: undefined @@ -264,26 +272,22 @@ define([ }, callback: function(closeEvent) { if (closeEvent.button.text == "{{ _('Select') }}") { - if($('.fileinfo').data('view') == 'grid') { - sel_file = $('.fileinfo').find('#contents li.selected p span').attr('title'); - } else { - sel_file = $('.fileinfo tbody tr.selected td p span').attr('title'); - } - var newFile = $('.currentpath').val() + sel_file; + var newFile = $('.storage_dialog #uploader .input-path').val(), + file_data = {'path': $('.currentpath').val()}; pgAdmin.Browser.Events.trigger('pgadmin-storage:finish_btn:select_file', newFile); + var innerbody = $(this.elements.body).find('.storage_content') + $(innerbody).find('*').off(); + innerbody.remove(); removeTransId(trans_id); // Ajax call to store the last directory visited once user press select button - var _Url = "{{ url_for('file_manager.index') }}" + "save_last_dir/" + trans_id; - var file_data = { - 'path': $('.currentpath').val() - }; - set_last_traversed_dir(file_data, _Url); + + set_last_traversed_dir(file_data, trans_id); } else if (closeEvent.button.text == "{{ _('Cancel') }}") { - if (removeTransId(trans_id)) { - this.destroy(); - return; - } + var innerbody = $(this.elements.body).find('.storage_content') + $(innerbody).find('*').off(); + innerbody.remove(); + removeTransId(trans_id); } }, build: function() { @@ -332,6 +336,7 @@ define([ // Dialog property return { main: function(params) { + var self = this; // Set title and button name if (_.isUndefined(params['dialog_title'])) { params['dialog_title'] = 'Select folder'; @@ -348,6 +353,11 @@ define([ renderStoragePanel(params); this.elements.dialog.style.minWidth = '630px'; this.show(); + setTimeout(function() { + $($container.find('.file_manager')).on('enter-key', function() { + $($(self.elements.footer).find('.file_manager_ok')).trigger('click') + }); + }, 200); }, settings: { label: undefined @@ -387,26 +397,20 @@ define([ }, callback: function(closeEvent) { if (closeEvent.button.text == "{{ _('Select') }}") { - if($('.fileinfo').data('view') == 'grid') { - sel_file = $('.fileinfo').find('#contents li.selected p span').attr('title'); - } else { - sel_file = $('.fileinfo tbody tr.selected td p span').attr('title'); - } - var newFile = $('.currentpath').val() + sel_file; - + var newFile = $('.storage_dialog #uploader .input-path').val(), + file_data = {'path': $('.currentpath').val()}; pgAdmin.Browser.Events.trigger('pgadmin-storage:finish_btn:select_folder', newFile); + var innerbody = $(this.elements.body).find('.storage_content') + $(innerbody).find('*').off(); + innerbody.remove(); removeTransId(trans_id); // Ajax call to store the last directory visited once user press select button - var _Url = "{{ url_for('file_manager.index') }}" + "save_last_dir/" + trans_id; - var file_data = { - 'path': $('.currentpath').val() - }; - set_last_traversed_dir(file_data, _Url); + set_last_traversed_dir(file_data, trans_id); } else if (closeEvent.button.text == "{{ _('Cancel') }}") { - if (removeTransId(trans_id)) { - this.destroy(); - return; - } + var innerbody = $(this.elements.body).find('.storage_content') + $(innerbody).find('*').off(); + innerbody.remove(); + removeTransId(trans_id); } }, build: function() { @@ -454,7 +458,8 @@ define([ // Dialog property return { main: function(params) { - var trans_id; + var self = this, + trans_id; // Set title and button name if (_.isUndefined(params['dialog_title'])) { params['dialog_title'] = 'Create file'; @@ -471,6 +476,11 @@ define([ renderStoragePanel(params); this.elements.dialog.style.minWidth = '630px'; this.show(); + setTimeout(function() { + $($container.find('.file_manager')).on('enter-key', function() { + $($(self.elements.footer).find('.file_manager_ok')).trigger('click') + }); + }, 200); }, settings: { label: undefined @@ -512,8 +522,7 @@ define([ $('.replace_file, .fm_dimmer').show(); $('.replace_file .btn_yes').click(function(self) { $('.replace_file, .fm_dimmer').hide(); - var selected_item = $('.allowed_file_types .create_input input[type="text"]').val(), - newFile = $('.currentpath').val() + selected_item; + var newFile = $('.storage_dialog #uploader .input-path').val() pgAdmin.Browser.Events.trigger('pgadmin-storage:finish_btn:create_file', newFile); $('.file_manager_create_cancel').trigger('click'); @@ -523,11 +532,13 @@ define([ }); }, is_file_exist: function() { - var selected_item = $('.allowed_file_types .create_input input[type="text"]').val(), + var full_path = $('.storage_dialog #uploader .input-path').val(), + path = full_path.substr(0, full_path.lastIndexOf('/') + 1), + selected_item = full_path.substr(full_path.lastIndexOf('/') + 1), is_exist = false; var file_data = { - 'path': $('.currentpath').val(), + 'path': path, 'name': selected_item, 'mode': 'is_file_exist' }; @@ -541,7 +552,7 @@ define([ async: false, success: function(resp) { data = resp.data.result; - if(data['Code'] === 0) { + if(data['Code'] === 1) { is_exist = true; } else { is_exist = false; @@ -550,30 +561,63 @@ define([ }); return is_exist; }, + check_permission: function(path) { + var permission = false, + post_data = { + 'path': path, + 'mode': 'permission' + }; + + $.ajax({ + type: 'POST', + data: JSON.stringify(post_data), + url: fileConnector + trans_id+'/', + dataType: 'json', + contentType: "application/json; charset=utf-8", + async: false, + success: function(resp) { + var data = resp.data.result; + if (data.Code === 1) { + permission = true; + } else { + $('.file_manager_ok').addClass('disabled'); + alertify.error(data.Error); + } + }, + error: function() { + $('.file_manager_ok').addClass('disabled'); + alertify.error('{{ _('Error occurred while checking access permission.') }}'); + } + }); + return permission; + }, callback: function(closeEvent) { if (closeEvent.button.text == "{{ _('Create') }}") { - var selected_item = $('.allowed_file_types .create_input input[type="text"]').val(); - var newFile = $('.currentpath').val() + selected_item; + var newFile = $('.storage_dialog #uploader .input-path').val(), + file_data = {'path': $('.currentpath').val()}; - if(!_.isUndefined(selected_item) && selected_item !== '' && this.is_file_exist()) { - this.replace_file(); + if (!this.check_permission(newFile)) { closeEvent.cancel = true; + return; } - else { + + if(!_.isUndefined(newFile) && newFile !== '' && this.is_file_exist()) { + this.replace_file(); + closeEvent.cancel = true; + } else { pgAdmin.Browser.Events.trigger('pgadmin-storage:finish_btn:create_file', newFile); + var innerbody = $(this.elements.body).find('.storage_content') + $(innerbody).find('*').off(); + innerbody.remove(); removeTransId(trans_id); } - var _Url = "{{ url_for('file_manager.index') }}" + "save_last_dir/" + trans_id; - var file_data = { - 'path': $('.currentpath').val() - }; - set_last_traversed_dir(file_data, _Url); + set_last_traversed_dir(file_data, trans_id); } else if (closeEvent.button.text == "{{ _('Cancel') }}") { - if (removeTransId(trans_id)) { - this.destroy(); - return; - } + var innerbody = $(this.elements.body).find('.storage_content') + $(innerbody).find('*').off(); + innerbody.remove(); + removeTransId(trans_id); } }, build: function() { diff --git a/web/pgadmin/misc/file_manager/templates/file_manager/js/utility.js b/web/pgadmin/misc/file_manager/templates/file_manager/js/utility.js index b8a2444..cb4faf4 100755 --- a/web/pgadmin/misc/file_manager/templates/file_manager/js/utility.js +++ b/web/pgadmin/misc/file_manager/templates/file_manager/js/utility.js @@ -159,37 +159,43 @@ var setUploader = function(path) { $('.storage_dialog #uploader').find('a').remove(); $('.storage_dialog #uploader').find('b').remove(); - path = decodeURI(path); - - var display_string = path, - file_path = ''; + if(config.options.platform_type === "win32") { + path = path.replace(/\//g, '\\') + } else { + path = path.replace(/\\/g, '/') + } - // split path - var split_path = display_string.split('/'); - split_path = split_path.filter(function(e) {return e;}); + path = decodeURI(path); - // set empty path if it is windows - if (config.options.platform_type === "win32" && config.options.show_volumes) { - file_path = ""; - } else if (split_path.length === 0) { - file_path = '/'; + if (config.options.platform_type === "win32") { + if (config.options.show_volumes && path == '\\') { + $('.storage_dialog #uploader .input-path').val(''); + } else { + $('.storage_dialog #uploader .input-path').val(path); + } + } else if (!config.options.platform_type === "win32" && + (path == '' || !path.startsWith('/'))) { + path = '/' + path; + $('.storage_dialog #uploader .input-path').val(path); } else { - file_path = '/'; + $('.storage_dialog #uploader .input-path').val(path); } - Object.keys(split_path).forEach(function (i) { - file_path += split_path[i] + '/'; - }); - $('.storage_dialog #uploader h1').html(file_path); + if( path.lastIndexOf('\\') == -1 && path.lastIndexOf('/') == -1) { + $('.currentpath').val(path); + } else if(path.lastIndexOf('/') > path.lastIndexOf('\\')) { + $('.currentpath').val(path.substr(0, path.lastIndexOf('/') + 1)); + } else { + $('.currentpath').val(path.substr(0, path.lastIndexOf('\\') + 1)); + } - $('.currentpath').val(path); enab_dis_level_up(); if ($('.storage_dialog #uploader h1 span').length === 0) { $('<span>'+lg.current_folder+'</span>').appendTo($('.storage_dialog #uploader h1')); } - $('.storage_dialog #uploader h1').attr('title', display_string); - $('.storage_dialog #uploader h1').attr('data-path', display_string); + $('.storage_dialog #uploader .input-path').attr('title', path); + $('.storage_dialog #uploader .input-path').attr('data-path', path); // create new folder $('.create').unbind().click(function() { @@ -288,7 +294,7 @@ var setUploader = function(path) { var d = new Date(); // to prevent IE cache issues $.getJSON(fileConnector + '?mode=addfolder&path=' + $('.currentpath').val() + '&name=' + foldername, function(resp) { var result = resp.data.result; - if (result.Code === 0) { + if (result.Code === 1) { alertify.success(lg.successful_added_folder); getFolderInfo(result.Parent); } else { @@ -382,7 +388,6 @@ var enable_disable_btn = function() { $('.file_manager').find('button.download').prop('disabled', true); $('.file_manager').find('button.rename').prop('disabled', true); if ($grid_file.length > 0) { - $('.create_input input[type="text"]').val(''); $('.file_manager_ok').addClass('disabled'); } } else { @@ -392,14 +397,13 @@ var enable_disable_btn = function() { $('.file_manager').find('button.download').prop('disabled', true); $('.file_manager').find('button.rename').prop('disabled', true); if ($list_file.length > 0) { - $('.create_input input[type="text"]').val(''); $('.file_manager_ok').addClass('disabled'); } } $('.delete_item').hide(); // clear address bar - $('.file_manager #uploader h1').show(); + $('.file_manager #uploader .input-path').show(); $('.file_manager #uploader .show_selected_file').remove(); }; @@ -436,7 +440,7 @@ var renameItem = function(data) { async: false, success: function(resp) { var result = resp.data.result; - if (result.Code === 0) { + if (result.Code === 1) { var newPath = result['New Path'], newName = result['New Name'], title = $("#preview h1").attr("title"); @@ -500,7 +504,7 @@ var deleteItem = function(data) { async: false, success: function(resp) { var result = resp.data.result; - if (result.Code === 0) { + if (result.Code === 1) { isDeleted = true; if (isDeleted) { alertify.success(lg.successful_delete); @@ -548,11 +552,11 @@ var getDetailView = function(path) { */ var getFileInfo = function(file) { // Update location for status, upload, & new folder functions. - var currentpath = file.substr(0, file.lastIndexOf('/') + 1); - setUploader(currentpath); + setUploader(file); // Retrieve the data & populate the template. - var d = new Date(); // to prevent IE cache issues + var d = new Date(), // to prevent IE cache issues + is_file_valid = false; var post_data = { 'path': file, 'mode': 'getinfo' @@ -567,8 +571,8 @@ var getFileInfo = function(file) { async: false, success: function(resp) { var data = resp.data.result; - - if (data.Code === 0) { + if (data.Code === 1) { + $('.file_manager_ok').removeClass('disabled'); var properties = ''; if ( data.Properties.Size || parseInt(data.Properties.Size)==0 @@ -579,18 +583,69 @@ var getFileInfo = function(file) { } data.Capabilities = capabilities; bindToolbar(data); + if (data.FileType == 'Directory') { + // Enable/Disable level up button + enab_dis_level_up(); + $('.file_manager_ok').addClass('disabled'); + + $('.file_manager button.delete, .file_manager button.rename').attr('disabled', 'disabled'); + $('.file_manager button.download').attr('disabled', 'disabled'); + + if (file.charAt(file.length - 1) != '/' && file.charAt(file.length - 1) != '\\') { + file += '/'; + } + getFolderInfo(file); + } else { + is_file_valid = true; + } } else { + $('.file_manager_ok').addClass('disabled'); alertify.error(data.Error); } } }); + return is_file_valid; }; +var checkPermission = function(path) { + var permission = false, + post_data = { + 'path': path, + 'mode': 'permission' + }; + + $.ajax({ + type: 'POST', + data: JSON.stringify(post_data), + url: fileConnector, + dataType: 'json', + contentType: "application/json; charset=utf-8", + async: false, + success: function(resp) { + var data = resp.data.result; + if (data.Code === 1) { + permission = true; + } else { + $('.file_manager_ok').addClass('disabled'); + alertify.error(data.Error); + } + }, + error: function() { + $('.file_manager_ok').addClass('disabled'); + alertify.error('{{ _('Error occurred while checking access permission.') }}'); + } + }); + return permission; +}; + + + /* * Retrieves data for all items within the given folder and * creates a list view. */ var getFolderInfo = function(path, file_type) { + $('.storage_dialog #uploader .input-path').prop('disabled', true); if (!file_type) { file_type = ''; } @@ -640,16 +695,16 @@ var getFolderInfo = function(path, file_type) { contentType: "application/json; charset=utf-8", async: false, success: function(resp) { + $('.storage_dialog #uploader .input-path').prop('disabled', false); var result = '', data = resp.data.result; // hide activity indicator $('.fileinfo').find('span.activity').hide(); if (data.Code === 0) { - alertify.error(data.err_msg); + alertify.error(data.Error); return; } - // generate HTML for files/folder and render into container if (!_.isEmpty(data)) { if ($('.fileinfo').data('view') == 'grid') { @@ -951,7 +1006,6 @@ var getFolderInfo = function(path, file_type) { var old_name = decodeURI($(this).siblings('span').attr('title')), newvalue = old_name.substring(0, old_name.indexOf('.')), last = getFileExtension(old_name); - if (old_name.indexOf('.') == 0) { last = ''; } @@ -997,24 +1051,19 @@ var getFolderInfo = function(path, file_type) { // Get into folder on dblclick $('.fileinfo').find('#contents li').dblclick(function(e) { e.stopPropagation(); - // Enable/Disable level up button enab_dis_level_up(); var path = decodeURI($(this).find('span').attr('data-alt')); - if (path.lastIndexOf("/") == path.length - 1) { + if (path.lastIndexOf("/") == path.length - 1 || path.lastIndexOf("\\") == path.length - 1) { $('.file_manager_ok').addClass('disabled'); - var $create_input = $('.create_input input[type="text"]'); - $('.file_manager button.delete, .file_manager button.rename').attr('disabled', 'disabled'); $('.file_manager button.download').attr('disabled', 'disabled'); getFolderInfo(path); - if ($create_input.length != 0 && $create_input.val() != '') { - $('.file_manager_ok').removeClass('disabled'); - } + } else { getFileInfo(path); } @@ -1028,7 +1077,7 @@ var getFolderInfo = function(path, file_type) { '.clip span.fm_lock_icon' ).attr('data-protected'); - if (path.lastIndexOf('/') == path.length - 1) { + if (path.lastIndexOf('/') == path.length - 1 || path.lastIndexOf('\\') == path.length - 1) { if ( has_capability(data_cap, 'select_folder') && is_protected == undefined @@ -1044,7 +1093,7 @@ var getFolderInfo = function(path, file_type) { 'disabled', 'disabled' ); // set selected folder name in breadcrums - $('.file_manager #uploader h1').hide(); + $('.file_manager #uploader .input-path').hide(); $('.file_manager #uploader .show_selected_file').remove(); $('<span class="show_selected_file">'+path+'</span>').appendTo( '.file_manager #uploader .filemanager-path-group' @@ -1065,13 +1114,6 @@ var getFolderInfo = function(path, file_type) { $('.file_manager #uploader .show_selected_file').remove(); } - if ( - config.options.dialog_type == 'create_file' && - is_protected == undefined - ) { - $('.create_input input[type="text"]').val(decodeURI(file_name)); - $('.file_manager_ok, .file_manager_create').removeClass('disabled'); - } getFileInfo(path); } }); @@ -1086,7 +1128,7 @@ var getFolderInfo = function(path, file_type) { 'i.tbl_lock_icon' ).attr('data-protected'); - if (path.lastIndexOf('/') == path.length - 1) { + if (path.lastIndexOf('/') == path.length - 1 || path.lastIndexOf('\\') == path.length - 1) { if (has_capability(data_cap, 'select_folder') && is_protected == undefined) { $(this).parent().find('tr.selected').removeClass('selected'); $('td:first-child', this).parent().addClass('selected'); @@ -1095,7 +1137,7 @@ var getFolderInfo = function(path, file_type) { $('.file_manager button.delete, .file_manager button.rename').removeAttr('disabled'); // set selected folder name in breadcrums - $('.file_manager #uploader h1').hide(); + $('.file_manager #uploader .input-path').hide(); $('.file_manager #uploader .show_selected_file').remove(); $('<span class="show_selected_file">'+path+'</span>').appendTo( '.file_manager #uploader .filemanager-path-group' @@ -1105,33 +1147,24 @@ var getFolderInfo = function(path, file_type) { if (has_capability(data_cap, 'select_file') && is_protected == undefined) { $(this).parent().find('tr.selected').removeClass('selected'); $('td:first-child', this).parent().addClass('selected'); - $('.file_manager_ok').removeClass('disabled'); $('.file_manager button.delete, .file_manager button.download, .file_manager button.rename').removeAttr( 'disabled' ); // set selected folder name in breadcrums $('.file_manager #uploader .show_selected_file').remove(); } - if ( - config.options.dialog_type == 'create_file' && - is_protected == undefined - ) { - $('.create_input input[type="text"]').val(file_name); - $('.file_manager_ok, .file_manager_create').removeClass('disabled'); - } + getFileInfo(path); } }); $('.fileinfo table#contents tbody tr').on('dblclick', function(e) { e.stopPropagation(); - // Enable/Disable level up button enab_dis_level_up(); - var path = $('td:first-child', this).attr('title'); - if (path.lastIndexOf('/') == path.length - 1) { + if (path.lastIndexOf('/') == path.length - 1 || path.lastIndexOf('\\') == path.length - 1) { $('.file_manager_ok').removeClass('disabled'); $('.file_manager button.download').attr('disabled', 'disabled'); $('.file_manager button.delete, .file_manager button.rename').attr('disabled', 'disabled'); @@ -1142,13 +1175,17 @@ var getFolderInfo = function(path, file_type) { }); } + input_object.set_cap(data_cap); + }, + error: function() { + $('.storage_dialog #uploader .input-path').prop('disabled', false); } }); }; // Enable/Disable level up button var enab_dis_level_up = function() { - $('.file_manager #uploader h1').show(); + $('.file_manager #uploader .input-path').show(); $('.show_selected_file').remove(); setTimeout(function() { @@ -1203,6 +1240,7 @@ var fileRoot = config.options.fileRoot, * Get localized messages from file * through culture var or from URL */ + var lg = [], enjs = '{{ url_for("file_manager.index") }}' + "en.js", lgf = loadData(enjs); @@ -1260,39 +1298,26 @@ if ( }); } -if (config.options.dialog_type == 'create_file') { - var create_file_html = '<div class="create_input">'+ - '<span>Filename:</span>'+ - '<input type="text" name="new_filename" class="fm_create_input form-control" />'+ - '</div>'; - - $('.create_mode_dlg').find('.allowed_file_types').prepend(create_file_html); - $('.create_input input[type="text"]').on('keypress, keydown', function() { - var input_text_len = $(this).val().length; - if (input_text_len > 0 ) { - $('.file_manager_ok').removeClass('disabled'); - } else { - $('.file_manager_ok').addClass('disabled'); - } - }); -} - /*--------------------------------------------------------- Item Actions - Object events ---------------------------------------------------------*/ // switch to folder view $('.file_manager .fileinfo').on('click', function(e) { + $('.file_manager #uploader .input-path').val($('.currentpath').val()) enable_disable_btn(); }); // refresh current directory $('.file_manager .refresh').on('click', function(e) { enable_disable_btn(); - var curr_path = $('.currentpath').val(), - path = curr_path.substring( - 0, curr_path.lastIndexOf("/") - ) + "/"; + var curr_path = $('.currentpath').val(); + $('.file_manager #uploader .input-path').val(curr_path); + if(curr_path.endsWith("/")) { + var path = curr_path.substring(0, curr_path.lastIndexOf("/")) + "/"; + } else { + var path = curr_path.substring(0, curr_path.lastIndexOf("\\")) + "\\"; + } getFolderInfo(path); }); @@ -1345,12 +1370,20 @@ $('.file_manager .home').click(function() { // Go one directory back $(".file_manager .level-up").click(function() { var b = $('.currentpath').val(); - // Enable/Disable level up button enab_dis_level_up(); + if (b.endsWith('\\') || b.endsWith('/')) { + b = b.substring(0, b.length - 1) + } + if (b != '/') { - parent = b.substring(0, b.slice(0, -1).lastIndexOf("/")) + "/"; + if(b.lastIndexOf('/') > b.lastIndexOf('\\')) { + var parent = b.substring(0, b.slice(0, -1).lastIndexOf("/")) + "/"; + } else { + var parent = b.substring(0, b.slice(0, -1).lastIndexOf("\\")) + "\\"; + } + var d = $(".fileinfo").data("view"); $(".fileinfo").data("view", d); getFolderInfo(parent); @@ -1376,12 +1409,110 @@ $('.file_manager .list').click(function() { // Provide initial values for upload form, status, etc. setUploader(fileRoot); -$('#uploader').attr('action', fileConnector); - var data = { 'Capabilities': capabilities }; +function InputObject() { + this.init= function(cap) { + var self = this, + check_obj = function(path, check) { + + var path = decodeURI(path); + + if (path.lastIndexOf('/') == path.length - 1 || path.lastIndexOf('\\') == path.length - 1) { + if ( + has_capability(self.data_cap, 'select_folder') + ) { + $('.file_manager_ok').removeClass('disabled'); + $('.file_manager button.delete, .file_manager button.rename').removeAttr( + 'disabled', 'disabled' + ); + $('.file_manager button.download').attr( + 'disabled', 'disabled' + ); + // set selected folder name in breadcrums + $('.file_manager #uploader .input-path').hide(); + $('.file_manager #uploader .show_selected_file').remove(); + $('<span class="show_selected_file">'+path+'</span>').appendTo( + '.file_manager #uploader .filemanager-path-group' + ); + } else { + $('.file_manager_ok').addClass('disabled'); + if(check) { + // Enable/Disable level up button + enab_dis_level_up(); + + $('.file_manager button.delete, .file_manager button.rename').attr('disabled', 'disabled'); + $('.file_manager button.download').attr('disabled', 'disabled'); + getFolderInfo(path); + } + } + } else { + if ( + has_capability(self.data_cap, 'select_file') + ) { + $('.file_manager_ok').removeClass('disabled'); + $('.file_manager button.delete, .file_manager button.download, .file_manager button.rename').removeAttr( + 'disabled' + ); + // set selected folder name in breadcrums + $('.file_manager #uploader .show_selected_file').remove(); + } + + if(check) { + if (config.options.dialog_type == 'create_file') { + var status = checkPermission(path) + if (status) { + $('.file_manager').trigger('enter-key'); + } + } else if(config.options.dialog_type == 'select_file') { + var file_status = getFileInfo(path); + if (file_status) { + $('.file_manager').trigger('enter-key'); + } + } + } + } + }; + + self.data_cap = cap; + + $('.storage_dialog #uploader .input-path').keyup(function(e) { + if(e.keyCode == 13) { + e.stopPropagation(); + var path = $(this).val(); + if(path == '') { + path = '/'; + } + + if(config.options.platform_type === "win32") { + path = path.replace(/\//g, '\\') + } else { + path = path.replace(/\\/g, '/') + if (!path.startsWith('/')) { + path = '/' + path; + } + } + + $(this).val(path); + setTimeout(function() { + check_obj(path, true); + }); + + return; + } + check_obj($(this).val(), false); + }); + } + this.set_cap = function(cap) { + this.data_cap = cap; + } +} + +var input_object = new InputObject() +input_object.init(data); + // Upload file if (has_capability(data, 'upload')) { Dropzone.autoDiscover = false; @@ -1463,7 +1594,7 @@ if (has_capability(data, 'upload')) { var data = response.data.result, $this = $(file.previewTemplate); - if (data.Code == 0) { + if (data.Code == 1) { setTimeout(function() { $this.find(".dz-upload").addClass("success"); }, 1000); diff --git a/web/pgadmin/tools/sqleditor/__init__.py b/web/pgadmin/tools/sqleditor/__init__.py index d0af418..f1efeb8 100644 --- a/web/pgadmin/tools/sqleditor/__init__.py +++ b/web/pgadmin/tools/sqleditor/__init__.py @@ -8,8 +8,6 @@ ########################################################################## """A blueprint module implementing the sqleditor frame.""" -MODULE_NAME = 'sqleditor' - import simplejson as json import os import pickle @@ -26,7 +24,9 @@ from pgadmin.utils.ajax import make_json_response, bad_request, \ from pgadmin.utils.driver import get_driver from pgadmin.utils.sqlautocomplete.autocomplete import SQLAutoComplete -from config import PG_DEFAULT_DRIVER +from config import PG_DEFAULT_DRIVER, SERVER_MODE + +MODULE_NAME = 'sqleditor' # import unquote from urlib for python2.x and python3.x try: @@ -1203,7 +1203,7 @@ def load_file(): # generate full path of file file_path = os.path.join( storage_manager_path, - file_path.lstrip('/') + file_path.lstrip('/').lstrip('\\') ) file_data = None @@ -1271,9 +1271,19 @@ def save_file(): if storage_manager_path is not None: file_path = os.path.join( storage_manager_path, - file_path.lstrip('/') + file_path.lstrip('/').lstrip('\\') ) + try: + # Do not allow user to access outside his storage dir in server mode. + if SERVER_MODE is True: + orig_path = os.path.abspath(file_path) + if not orig_path.startswith(storage_manager_path): + raise Exception( + gettext("Access denied ({})".format(orig_path))) + except Exception as e: + return internal_server_error(errormsg=str(e)) + if hasattr(str, 'decode'): file_content = file_data['file_content'] else: @@ -1281,7 +1291,7 @@ def save_file(): # write to file try: - with open(file_path, 'wb') as output_file: + with open(file_path, 'wb+') as output_file: if hasattr(str, 'decode'): output_file.write(file_content.encode('utf-8')) else:
-- Sent via pgadmin-hackers mailing list (pgadmin-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgadmin-hackers