Browse Source

HUE-8273 [filebrowser] Support white spaces in filenames

jdesjean 7 years ago
parent
commit
95687342e3

+ 4 - 4
apps/filebrowser/src/filebrowser/templates/listdir_components.mako

@@ -828,7 +828,7 @@ from filebrowser.conf import ENABLE_EXTRACT_UPLOADED_ARCHIVE
       return {
         name: file.name,
         path: file.path,
-        url: file.url,
+        url: encodeURI(file.url),
         type: file.type,
         permissions: file.rwx,
         mode: file.mode,
@@ -1351,7 +1351,7 @@ from filebrowser.conf import ENABLE_EXTRACT_UPLOADED_ARCHIVE
 
         $("#renameFileName").text(self.selectedFile().path);
 
-        $("#newNameInput").val(self.selectedFile().name);
+        $("#newNameInput").val(encodeURI(self.selectedFile().name));
 
         $("#renameForm").attr("action", "/filebrowser/rename?next=${url('filebrowser.views.view', path='')}" + self.currentPath());
 
@@ -2071,7 +2071,7 @@ from filebrowser.conf import ENABLE_EXTRACT_UPLOADED_ARCHIVE
           catch (exc) {
           }
           var options = {
-            url: '/filebrowser/upload/file?dest=' + ops.path,
+            url: '/filebrowser/upload/file?dest=' + encodeURI(ops.path),
             paramName: 'hdfs_file',
             params: {
               dest: ops.path
@@ -2105,7 +2105,7 @@ from filebrowser.conf import ENABLE_EXTRACT_UPLOADED_ARCHIVE
                 newDest = ops.path + '/' + file.fullPath.substr(0, file.fullPath.length - file.name.length);
               }
               this.options.params.dest = newDest;
-              this.options.url = '/filebrowser/upload/file?dest=' + newDest;
+              this.options.url = '/filebrowser/upload/file?dest=' + encodeURI(newDest);
             },
             uploadprogress: function (file, progress) {
               $('[data-dz-name]').each(function (cnt, item) {

+ 8 - 8
apps/filebrowser/src/filebrowser/urls.py

@@ -23,16 +23,16 @@ urlpatterns = patterns('filebrowser.views',
   url(r'^$', 'index', name='index'),
 
   # Catch-all for viewing a file (display) or a directory (listdir)
-  url(r'^view=(?P<path>.*)$', 'view', name='view'),
+  url(r'^view=(?P<path>[\s\S.]*)$', 'view', name='view'),
 
-  url(r'^listdir=(?P<path>.*)$', 'listdir', name='listdir'),
-  url(r'^display=(?P<path>.*)$', 'display', name='display'),
-  url(r'^stat=(?P<path>.*)$', 'stat', name='stat'),
-  url(r'^content_summary=(?P<path>.*)$', 'content_summary', name='content_summary'),
-  url(r'^download=(?P<path>.*)$', 'download', name='download'),
+  url(r'^listdir=(?P<path>[\s\S.]*)$', 'listdir', name='listdir'),
+  url(r'^display=(?P<path>[\s\S.]*)$', 'display', name='display'),
+  url(r'^stat=(?P<path>[\s\S.]*)$', 'stat', name='stat'),
+  url(r'^content_summary=(?P<path>[\s\S.]*)$', 'content_summary', name='content_summary'),
+  url(r'^download=(?P<path>[\s\S.]*)$', 'download', name='download'),
   url(r'^status$', 'status', name='status'),
-  url(r'^home_relative_view=(?P<path>.*)$', 'home_relative_view', name='home_relative_view'),
-  url(r'^edit=(?P<path>.*)$', 'edit', name='edit'),
+  url(r'^home_relative_view=(?P<path>[\s\S.]*)$', 'home_relative_view', name='home_relative_view'),
+  url(r'^edit=(?P<path>[\s\S.]*)$', 'edit', name='edit'),
 
   # POST operations
   url(r'^save$', 'save_file'),

+ 26 - 21
apps/filebrowser/src/filebrowser/views.py

@@ -334,7 +334,7 @@ def parse_breadcrumbs(path):
       if url and not url.endswith('/'):
         url += '/'
       url += part
-      breadcrumbs.append({'url': url, 'label': part})
+      breadcrumbs.append({'url': urllib.quote(url.encode('utf-8')), 'label': part})
     return breadcrumbs
 
 
@@ -358,11 +358,11 @@ def listdir(request, path):
     breadcrumbs = parse_breadcrumbs(path)
 
     data = {
-        'path': path,
+        'path': urllib.quote(path.encode('utf-8')),
         'file_filter': file_filter,
         'breadcrumbs': breadcrumbs,
-        'current_dir_path': path,
-        'current_request_path': request.path,
+        'current_dir_path': urllib.quote(path.encode('utf-8')),
+        'current_request_path': urllib.quote(request.path.encode('utf-8')),
         'home_directory': request.fs.isdir(home_dir_path) and home_dir_path or None,
         'cwd_set': True,
         'is_superuser': request.user.username == request.fs.superuser,
@@ -490,9 +490,9 @@ def listdir_paged(request, path):
 
     is_fs_superuser = _is_hdfs_superuser(request)
     data = {
-        'path': path,
+        'path': urllib.quote(path.encode('utf-8')),
         'breadcrumbs': breadcrumbs,
-        'current_request_path': request.path,
+        'current_request_path': urllib.quote(request.path.encode('utf-8')),
         'is_trash_enabled': is_trash_enabled,
         'files': page.object_list if page else [],
         'page': _massage_page(page) if page else {},
@@ -502,7 +502,7 @@ def listdir_paged(request, path):
         # The following should probably be deprecated
         'cwd_set': True,
         'file_filter': 'any',
-        'current_dir_path': path,
+        'current_dir_path': urllib.quote(path.encode('utf-8')),
         'is_fs_superuser': is_fs_superuser,
         'groups': is_fs_superuser and [str(x) for x in Group.objects.values_list('name', flat=True)] or [],
         'users': is_fs_superuser and [str(x) for x in User.objects.values_list('username', flat=True)] or [],
@@ -535,7 +535,7 @@ def _massage_stats(request, stats):
     path = stats['path']
     normalized = request.fs.normpath(path)
     return {
-        'path': normalized,
+        'path': urllib.quote(normalized.encode('utf-8')),
         'name': stats['name'],
         'stats': stats.to_json_dict(),
         'mtime': datetime.fromtimestamp(stats['mtime']).strftime('%B %d, %Y %I:%M %p') if stats['mtime'] else '',
@@ -1074,8 +1074,11 @@ def generic_op(form_class, request, op, parameter_names, piggyback=None, templat
                 return format_preserving_redirect(request, next)
             ret["success"] = True
             try:
-                if piggyback:
-                    piggy_path = form.cleaned_data[piggyback]
+                if piggyback: # TODO: result does not support array.
+                    if isinstance(form.cleaned_data, list):
+                        piggy_path = form.cleaned_data[0][piggyback]
+                    else:
+                        piggy_path = form.cleaned_data[piggyback]
                     ret["result"] = _massage_stats(request, stat_absolute_path(piggy_path ,request.fs.stats(piggy_path)))
             except Exception, e:
                 # Hard to report these more naturally here.  These happen either
@@ -1099,6 +1102,8 @@ def generic_op(form_class, request, op, parameter_names, piggyback=None, templat
 
 def rename(request):
     def smart_rename(src_path, dest_path):
+        src_path = urllib.unquote(src_path)
+        dest_path = urllib.unquote(dest_path)
         """If dest_path doesn't have a directory specified, use same dir."""
         if "#" in dest_path:
           raise PopupException(_("Could not rename folder \"%s\" to \"%s\": Hashes are not allowed in filenames." % (src_path, dest_path)))
@@ -1113,7 +1118,7 @@ def rename(request):
 
 def set_replication(request):
     def smart_set_replication(src_path, replication_factor):
-        result = request.fs.set_replication(src_path, replication_factor)
+        result = request.fs.set_replication(urllib.unquote(src_path), replication_factor)
         if not result:
             raise PopupException(_("Setting of replication factor failed"))
 
@@ -1126,7 +1131,7 @@ def mkdir(request):
         # No absolute directory specification allowed.
         if posixpath.sep in name or "#" in name:
             raise PopupException(_("Could not name folder \"%s\": Slashes or hashes are not allowed in filenames." % name))
-        request.fs.mkdir(request.fs.join(path, name))
+        request.fs.mkdir(request.fs.join(urllib.unquote(path), urllib.unquote(name)))
 
     return generic_op(MkDirForm, request, smart_mkdir, ["path", "name"], "path")
 
@@ -1136,7 +1141,7 @@ def touch(request):
         # No absolute path specification allowed.
         if posixpath.sep in name:
             raise PopupException(_("Could not name file \"%s\": Slashes are not allowed in filenames." % name))
-        request.fs.create(request.fs.join(path, name))
+        request.fs.create(request.fs.join(urllib.unquote(path), name))
 
     return generic_op(TouchForm, request, smart_touch, ["path", "name"], "path")
 
@@ -1146,7 +1151,7 @@ def rmtree(request):
     params = ["path"]
     def bulk_rmtree(*args, **kwargs):
         for arg in args:
-            request.fs.do_as_user(request.user, request.fs.rmtree, arg['path'], 'skip_trash' in request.GET)
+            request.fs.do_as_user(request.user, request.fs.rmtree, urllib.unquote(arg['path']), 'skip_trash' in request.GET)
     return generic_op(RmTreeFormSet, request, bulk_rmtree, ["path"], None,
                       data_extractor=formset_data_extractor(recurring, params),
                       arg_extractor=formset_arg_extractor,
@@ -1159,7 +1164,7 @@ def move(request):
     params = ['src_path']
     def bulk_move(*args, **kwargs):
         for arg in args:
-            request.fs.rename(arg['src_path'], arg['dest_path'])
+            request.fs.rename(urllib.unquote(arg['src_path']), urllib.unquote(arg['dest_path']))
     return generic_op(RenameFormSet, request, bulk_move, ["src_path", "dest_path"], None,
                       data_extractor=formset_data_extractor(recurring, params),
                       arg_extractor=formset_arg_extractor,
@@ -1174,7 +1179,7 @@ def copy(request):
         for arg in args:
             if arg['src_path'] == arg['dest_path']:
                 raise PopupException(_('Source path and destination path cannot be same'))
-            request.fs.copy(arg['src_path'], arg['dest_path'], recursive=True, owner=request.user)
+            request.fs.copy(urllib.unquote(arg['src_path']), urllib.unquote(arg['dest_path']), recursive=True, owner=request.user)
     return generic_op(CopyFormSet, request, bulk_copy, ["src_path", "dest_path"], None,
                       data_extractor=formset_data_extractor(recurring, params),
                       arg_extractor=formset_arg_extractor,
@@ -1188,7 +1193,7 @@ def chmod(request):
     def bulk_chmod(*args, **kwargs):
         op = curry(request.fs.chmod, recursive=request.POST.get('recursive', False))
         for arg in args:
-            op(arg['path'], arg['mode'])
+            op(urllib.unquote(arg['path']), arg['mode'])
     # mode here is abused: on input, it's a string, but when retrieved,
     # it's an int.
     return generic_op(ChmodFormSet, request, bulk_chmod, ['path', 'mode'], "path",
@@ -1213,7 +1218,7 @@ def chown(request):
     def bulk_chown(*args, **kwargs):
         op = curry(request.fs.chown, recursive=request.POST.get('recursive', False))
         for arg in args:
-            varg = [arg[param] for param in param_names]
+            varg = [urllib.unquote(arg[param]) if param == 'path' else arg[param] for param in param_names]
             op(*varg)
 
     return generic_op(ChownFormSet, request, bulk_chown, param_names, "path",
@@ -1228,7 +1233,7 @@ def trash_restore(request):
     params = ["path"]
     def bulk_restore(*args, **kwargs):
         for arg in args:
-            request.fs.do_as_user(request.user, request.fs.restore, arg['path'])
+            request.fs.do_as_user(request.user, request.fs.restore, urllib.unquote(arg['path']))
     return generic_op(RestoreFormSet, request, bulk_restore, ["path"], None,
                       data_extractor=formset_data_extractor(recurring, params),
                       arg_extractor=formset_arg_extractor,
@@ -1277,7 +1282,7 @@ def _upload_file(request):
 
     if form.is_valid():
         uploaded_file = request.FILES['hdfs_file']
-        dest = scheme_absolute_path(request.GET['dest'], form.cleaned_data['dest'])
+        dest = scheme_absolute_path(request.GET['dest'], urllib.unquote(form.cleaned_data['dest']))
         filepath = request.fs.join(dest, uploaded_file.name)
 
         if request.fs.isdir(dest) and posixpath.sep in uploaded_file.name:
@@ -1300,7 +1305,7 @@ def _upload_file(request):
             raise PopupException(msg)
 
         response.update({
-          'path': filepath,
+          'path': urllib.quote(filepath.encode('utf-8')),
           'result': _massage_stats(request, stat_absolute_path(filepath, request.fs.stats(filepath))),
           'next': request.GET.get("next")
         })

+ 4 - 4
desktop/core/src/desktop/static/desktop/js/apiHelper.js

@@ -558,7 +558,7 @@ var ApiHelper = (function () {
     if (options.pathParts.length > 0 && (options.pathParts[0] === '/' || options.pathParts[0] === '')) {
       options.pathParts.shift();
     }
-    var url = HDFS_API_PREFIX + options.pathParts.join("/") + '?format=json&sortby=name&descending=false&pagesize=' + (options.pageSize || 500) + '&pagenum=' + (options.page || 1);
+    var url = HDFS_API_PREFIX + encodeURI(options.pathParts.join("/")) + '?format=json&sortby=name&descending=false&pagesize=' + (options.pageSize || 500) + '&pagenum=' + (options.page || 1);
     if (options.filter) {
       url += '&filter=' + options.filter;
     }
@@ -613,7 +613,7 @@ var ApiHelper = (function () {
   ApiHelper.prototype.fetchAdlsPath = function (options) {
     var self = this;
     options.pathParts.shift();
-    var url = ADLS_API_PREFIX + options.pathParts.join("/") + '?format=json&sortby=name&descending=false&pagesize=' + (options.pageSize || 500) + '&pagenum=' + (options.page || 1);
+    var url = ADLS_API_PREFIX + encodeURI(options.pathParts.join("/")) + '?format=json&sortby=name&descending=false&pagesize=' + (options.pageSize || 500) + '&pagenum=' + (options.page || 1);
     if (options.filter) {
       url += '&filter=' + options.filter;
     }
@@ -664,7 +664,7 @@ var ApiHelper = (function () {
    */
   ApiHelper.prototype.fetchGitContents = function (options) {
     var self = this;
-    var url = GIT_API_PREFIX + '?path=' + options.pathParts.join("/") + '&fileType=' + options.fileType;
+    var url = GIT_API_PREFIX + '?path=' + encodeURI(options.pathParts.join("/")) + '&fileType=' + options.fileType;
     var fetchFunction = function (storeInCache) {
       if (options.timeout === 0) {
         self.assistErrorCallback(options)({ status: -1 });
@@ -715,7 +715,7 @@ var ApiHelper = (function () {
   ApiHelper.prototype.fetchS3Path = function (options) {
     var self = this;
     options.pathParts.shift(); // remove the trailing /
-    var url = S3_API_PREFIX + options.pathParts.join("/") + '?format=json&sortby=name&descending=false&pagesize=' + (options.pageSize || 500) + '&pagenum=' + (options.page || 1);
+    var url = S3_API_PREFIX + encodeURI(options.pathParts.join("/")) + '?format=json&sortby=name&descending=false&pagesize=' + (options.pageSize || 500) + '&pagenum=' + (options.page || 1);
     if (options.filter) {
       url += '&filter=' + options.filter;
     }

+ 2 - 0
desktop/core/src/desktop/static/desktop/js/assist/assistStorageEntry.js

@@ -63,6 +63,7 @@ var AssistStorageEntry = (function () {
       }
     }
     self.path += self.definition.name;
+    self.path = encodeURI(self.path);
     self.currentPage = 1;
     self.hasMorePages = true;
     self.preview = ko.observable();
@@ -136,6 +137,7 @@ var AssistStorageEntry = (function () {
         return file.name !== '.' && file.name !== '..';
       });
       self.entries($.map(filteredFiles, function (file) {
+        file.url = encodeURI(file.url);
         return new AssistStorageEntry({
           type: self.type,
           definition: file,

+ 1 - 0
desktop/core/src/desktop/templates/hue.mako

@@ -999,6 +999,7 @@ ${ smart_unicode(login_modal(request).content) | n,unicode }
               baseURL += (baseURL.indexOf('?') > -1 ? '&' : '?') + self.currentQueryString();
               self.currentQueryString(null);
             }
+            baseURL = encodeURI(baseURL);
             $.ajax({
               url: baseURL + (baseURL.indexOf('?') > -1 ? '&' : '?') +'is_embeddable=true' + self.extraEmbeddableURLParams(),
               beforeSend: function (xhr) {

+ 3 - 2
desktop/libs/indexer/src/indexer/indexers/sql.py

@@ -15,6 +15,7 @@
 # limitations under the License.import logging
 
 import logging
+import urllib
 
 from django.contrib.auth.models import User
 from django.core.urlresolvers import reverse
@@ -55,9 +56,9 @@ class SQLIndexer(object):
     kudu_partition_columns = destination['kuduPartitionColumns']
     comment = destination['description']
 
-    source_path = source['path']
+    source_path = urllib.unquote(source['path'])
     external = not destination['useDefaultLocation']
-    external_path = destination['nonDefaultLocation']
+    external_path = urllib.unquote(destination['nonDefaultLocation'])
 
     load_data = destination['importData']
     skip_header = destination['hasHeader']

+ 2 - 1
desktop/libs/notebook/src/notebook/api.py

@@ -19,6 +19,7 @@ import json
 import logging
 
 import sqlparse
+import urllib
 
 from django.core.urlresolvers import reverse
 from django.db.models import Q
@@ -633,7 +634,7 @@ def export_result(request):
   notebook = json.loads(request.POST.get('notebook', '{}'))
   snippet = json.loads(request.POST.get('snippet', '{}'))
   data_format = json.loads(request.POST.get('format', 'hdfs-file'))
-  destination = json.loads(request.POST.get('destination', ''))
+  destination = urllib.unquote(json.loads(request.POST.get('destination', '')))
   overwrite = json.loads(request.POST.get('overwrite', 'false'))
   is_embedded = json.loads(request.POST.get('is_embedded', 'false'))
   start_time = json.loads(request.POST.get('start_time', '-1'))

+ 2 - 2
desktop/libs/notebook/src/notebook/connectors/hiveserver2.py

@@ -501,7 +501,7 @@ class HS2Api(Api):
 
     upload(target_file, handle, self.request.user, db, self.request.fs, max_rows=max_rows, max_bytes=max_bytes)
 
-    return '/filebrowser/view=%s' % target_file
+    return '/filebrowser/view=%s' % urllib.quote(urllib.quote(target_file.encode('utf-8'))) # Quote twice, because of issue in the routing on client
 
 
   def export_data_as_table(self, notebook, snippet, destination, is_temporary=False, location=None):
@@ -557,7 +557,7 @@ DROP TABLE IF EXISTS `%(table)s`;
       'location': self.request.fs.netnormpath(destination),
       'hql': query.hql_query
     }
-    success_url = '/filebrowser/view=%s' % destination
+    success_url = '/filebrowser/view=%s' % urllib.quote(destination.encode('utf-8'))
 
     return hql, success_url
 

+ 2 - 1
desktop/libs/notebook/src/notebook/models.py

@@ -19,6 +19,7 @@ import json
 import logging
 import math
 import numbers
+import urllib
 import uuid
 
 from django.utils.html import escape
@@ -102,7 +103,7 @@ def make_notebook(name='Browse', description='', editor_type='hive', statement='
     'type': 'notebook' if is_notebook else 'query-%s' % editor_type,
     'showHistory': True,
     'isSaved': is_saved,
-    'onSuccessUrl': on_success_url,
+    'onSuccessUrl': urllib.quote(on_success_url.encode('utf-8')) if on_success_url else None,
     'pubSubUrl': pub_sub_url,
     'skipHistorify': skip_historify,
     'isManaged': is_task,