validate user data in gridftp
authorMaciej Tronowski <mtro@man.poznan.pl>
Fri, 17 Apr 2015 16:03:25 +0000 (18:03 +0200)
committerMaciej Tronowski <mtro@man.poznan.pl>
Fri, 17 Apr 2015 16:03:25 +0000 (18:03 +0200)
filex/forms.py
filex/ftp.py
filex/static/filex/filex.js
filex/templates/filex/upload.js.html
filex/uploadhandler.py
filex/urls.py
filex/views.py
qcg/templates/qcg/gridftp.html
qcg/views.py

index db6587b..f73b175 100644 (file)
@@ -1,9 +1,20 @@
 # coding=utf-8
+import os
+
 from django import forms
+from django.core.exceptions import ValidationError
+from django.core.validators import RegexValidator
 
 from filex.models import Favorite
 
 
+msg = u'Invalid value'
+host_validator = RegexValidator(r'^(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+'
+                                r'(?:[a-zA-Z]{2,6}\.?|[a-zA-Z0-9-]{2,}(?<!-)\.?))(?::\d+)?$', msg)
+path_validator = RegexValidator(r'^/(?:[^/\0]+/?)*$', msg)
+name_validator = RegexValidator(r'^[^/\0]+$', msg)
+
+
 class FavoriteForm(forms.ModelForm):
     class Meta:
         model = Favorite
@@ -11,28 +22,121 @@ class FavoriteForm(forms.ModelForm):
         widgets = {'owner': forms.HiddenInput()}
 
 
-class NewDirForm(forms.Form):
-    host = forms.CharField(label=u'Host', max_length=256, widget=forms.HiddenInput())
-    path = forms.CharField(label=u'Ścieżka', max_length=1024, widget=forms.HiddenInput())
-    name = forms.CharField(label=u'Nazwa', max_length=256)
+class HostForm(forms.Form):
+    host = forms.CharField(label=u'Host', max_length=256, validators=[host_validator], widget=forms.HiddenInput())
+
+
+class HostPathForm(HostForm):
+    path = forms.CharField(label=u'Ścieżka', max_length=1024, validators=[path_validator], widget=forms.HiddenInput())
+
+    def clean_path(self):
+        return os.path.normpath(self.cleaned_data['path'])
+
+
+class HostPathNameForm(HostPathForm):
+    name = forms.CharField(label=u'Nazwa', max_length=256, validators=[name_validator])
+
+
+class HostItemsForm(HostForm):
+    dirs = forms.MultipleChoiceField(label=u'Katalogi', required=False, widget=forms.MultipleHiddenInput())
+    files = forms.MultipleChoiceField(label=u'Pliki', required=False, widget=forms.MultipleHiddenInput())
+
+    def __init__(self, data=None, *args, **kwargs):
+        super(HostItemsForm, self).__init__(data, *args, **kwargs)
+
+        if data is not None:
+            # accept user defined choices
+            self.fields['dirs'].choices += ((v, v) for v in data.getlist('dirs'))
+            self.fields['files'].choices += ((v, v) for v in data.getlist('files'))
+
+    def clean(self):
+        data = super(HostItemsForm, self).clean()
+
+        if not (data.get('dirs') or data.get('files')):
+            raise ValidationError('No items specified')
+
+        return data
+
+    @staticmethod
+    def _clean_paths(values):
+        errors, cleaned = [], []
+        for name in values:
+            try:
+                path_validator(name)
+            except ValidationError as e:
+                e.message += ' - ' + name
+                errors.append(e)
+            else:
+                cleaned.append(os.path.normpath(name))
+        if errors:
+            raise ValidationError(errors)
+
+        return cleaned
+
+    def clean_dirs(self):
+        return self._clean_paths(self.cleaned_data['dirs'])
+
+    def clean_files(self):
+        return self._clean_paths(self.cleaned_data['files'])
+    
+    
+class RenameForm(HostForm):
+    src = forms.CharField(label=u'Stara nazwa', max_length=1024, validators=[path_validator], widget=forms.HiddenInput())
+    dst = forms.CharField(label=u'Nowa nazwa', max_length=1024, validators=[path_validator])
+
+    def clean_src(self):
+        return os.path.normpath(self.cleaned_data['src'])
+
+    def clean_dst(self):
+        return os.path.normpath(self.cleaned_data['dst'])
+
+
+class ExtractForm(HostPathForm):
+    dst = forms.CharField(label=u'Katalog docelowy', max_length=1024, validators=[path_validator])
+
+    def clean_dst(self):
+        return os.path.normpath(self.cleaned_data['dst'])
+
+
+class CompressForm(HostPathForm):
+    archive = forms.CharField(label=u'Nazwa', max_length=1024, validators=[path_validator])
+    files = forms.MultipleChoiceField(label=u'Pliki', widget=forms.MultipleHiddenInput())
+
+    def __init__(self, data=None, *args, **kwargs):
+        super(CompressForm, self).__init__(data, *args, **kwargs)
+
+        if data is not None:
+            # accept user defined choices
+            self.fields['files'].choices += ((v, v) for v in data.getlist('files'))
+
+    def clean_files(self):
+        errors, cleaned = [], []
+        for name in self.cleaned_data['files']:
+            try:
+                name_validator(name)
+            except ValidationError as e:
+                e.message += ' - ' + name
+                errors.append(e)
+            else:
+                cleaned.append(os.path.normpath(name))
+        if errors:
+            raise ValidationError(errors)
 
+        return cleaned
 
-class RenameForm(forms.Form):
-    host = forms.CharField(label=u'Host', max_length=256, widget=forms.HiddenInput())
-    path = forms.CharField(label=u'Ścieżka', max_length=1024, widget=forms.HiddenInput())
-    src = forms.CharField(label=u'Stara nazwa', max_length=256, widget=forms.HiddenInput())
-    dst = forms.CharField(label=u'Nowa nazwa', max_length=256)
+    def clean_archive(self):
+        return os.path.normpath(self.cleaned_data['archive'])
 
 
-class ArchiveForm(NewDirForm):
+class ArchiveForm(CompressForm):
     ZIP = '.zip'
     GZIP = '.tar.gz'
     BZIP = '.tar.bz2'
 
     TYPE_CHOICES = (
-        (ZIP, 'Archiwum zip'),
-        (GZIP, 'Archiwum tar.gz'),
-        (BZIP, 'Archiwum tar.bz2'),
+        (ZIP, 'zip'),
+        (GZIP, 'tar.gz'),
+        (BZIP, 'tar.bz2'),
     )
 
-    type = forms.ChoiceField(label=u'Typ', choices=TYPE_CHOICES, initial=ZIP)
+    type = forms.ChoiceField(label=u'Typ archiwum', choices=TYPE_CHOICES, initial=ZIP)
index 49f470f..c9e90ff 100644 (file)
@@ -4,7 +4,6 @@ from itertools import chain
 import os
 import re
 from threading import Event
-from urlparse import urlparse
 
 from django.utils.timezone import localtime, UTC
 from gridftp import FTPClient, Buffer, HandleAttr, OperationAttr
@@ -136,7 +135,7 @@ class FTPOperation:
         data = self.listing(url).next()
 
         if data['name'] == '.':
-            data['name'] = os.path.basename(urlparse(url).path.rstrip('/')) or u'/'
+            data['name'] = os.path.basename(os.path.normpath(url))
 
         return data
 
@@ -163,22 +162,28 @@ class FTPOperation:
         return False
 
     def compress(self, server, path, files, archive):
+        for value in [path, archive] + files:
+            if '#' in value:
+                raise ValueError('Illegal character `#` in {}'.format(value))
+
         if self.match_ext(archive, '.tar.gz', '.tgz'):
-            cmd, args = 'tar', ['cvzf', os.path.join(path, archive), '-C', path] + files
+            cmd, args = 'tar', ['cvzf', archive, '-C', path] + files
         elif self.match_ext(archive, '.tar.bz2', '.tbz'):
-            cmd, args = 'tar', ['cvjf', os.path.join(path, archive), '-C', path] + files
+            cmd, args = 'tar', ['cvjf', archive, '-C', path] + files
         elif self.match_ext(archive, '.zip'):
-            cmd, args = 'jar', (['cvMf', os.path.join(path, archive)] +
-                                list(chain.from_iterable(('-C', path, f) for f in files)))
+            cmd, args = 'jar', (['cvMf', archive] + list(chain.from_iterable(('-C', path, f) for f in files)))
         else:
             raise ValueError('Unknown archive type: {}'.format(archive))
 
-        # FIXME handling filename with #
         self.op_attr.set_disk_stack('#'.join(["popen:argv=", cmd] + args))
 
         return self.get(server)
 
     def extract(self, server, archive, dst):
+        for value in [archive, dst]:
+            if '#' in value:
+                raise ValueError('Illegal character `#` in {}'.format(value))
+
         if self.match_ext(archive, '.tar.gz', '.tgz'):
             cmd, args = 'tar', ('xvzf', archive, '-C', dst)
         elif self.match_ext(archive, '.tar.bz2', '.tbz'):
@@ -188,7 +193,6 @@ class FTPOperation:
         else:
             raise ValueError('Unknown archive type: {}'.format(archive))
 
-        # FIXME handling filename with #
         self.op_attr.set_disk_stack('#'.join(("popen:argv=", cmd) + args))
 
         return self.get(server)
index 18d78d4..e7ad4c4 100644 (file)
@@ -141,8 +141,7 @@ $(function(){
             var data = this.model.toJSON();
             data['url_params'] = $.param({
                 host: this.view.host,
-                path: this.view.path.full(),
-                name: this.model.get('name')
+                path: this.view.path.full() + '/' + this.model.get('name')
             });
             data['cid'] = this.model.cid;
 
@@ -350,7 +349,7 @@ $(function(){
                 error: function(collection, response) {
                     view.files.reset();
 
-                    var msg = (response.responseJSON || {}).msg || 'Błąd serwera';
+                    var msg = (response.responseJSON || {}).error || 'Błąd serwera';
 
                     view.$noItems.hide();
                     view.$error.find('.msg').text(msg);
index 9e661d0..e399717 100644 (file)
@@ -65,7 +65,7 @@
                         data.context.find('.progress-info').text('Pominięto');
                 }
 
-                $.getJSON('{% url 'filex:info' %}', {host: host, path: path + file.name}, function(response) {
+                $.getJSON('{% url 'filex:info' %}', {host: host, path: path + '/' + file.name}, function(response) {
                     if (applyToAll != undefined) {
                         finish(applyToAll);
                         return;
index 75a6cd7..c2414ee 100644 (file)
@@ -1,9 +1,11 @@
 from functools import wraps
+import os
 
 from django.core.files.uploadedfile import UploadedFile
 from django.core.files.uploadhandler import FileUploadHandler, StopUpload, StopFutureHandlers
 from django.views.decorators.csrf import csrf_exempt, csrf_protect
 
+from filex.forms import HostPathForm
 from filex.ftp import FTPOperation
 
 
@@ -15,19 +17,16 @@ class FtpUploadHandler(FileUploadHandler):
     def new_file(self, file_name, *args, **kwargs):
         super(FtpUploadHandler, self).new_file(file_name, *args, **kwargs)
 
-        # TODO limit to selected request.path
-        # TODO validate host and path
-        host = self.request.GET.get('host')
-        path = self.request.GET.get('path')
+        form = HostPathForm(self.request.GET)
 
-        if self.request.user.is_anonymous() or not host or not path:
+        if self.request.user.is_anonymous() or not form.is_valid():
             raise StopUpload(connection_reset=True)
 
         if self.ftp is None:
             self.ftp = FTPOperation(self.request.session['proxy'], self.chunk_size)
 
-        self.url = 'gsiftp://' + host + path + self.file_name
-        self.tmp_url = 'gsiftp://' + host + path + self.file_name + '.part'
+        self.url = 'gsiftp://' + form.cleaned_data['host'] + os.path.join(form.cleaned_data['path'], self.file_name)
+        self.tmp_url = self.url + '.part'
 
         self.ftp.put(self.tmp_url)
 
index 2f73dec..7810670 100644 (file)
@@ -3,15 +3,15 @@ from django.conf.urls import patterns, url
 from filex import views
 
 urlpatterns = patterns('',
-    url(r'^list/$', views.list_content, name='list'),
-    url(r'^download/$', views.download, name='download'),
+    url(r'^list/$', views.ListView.as_view(), name='list'),
+    url(r'^download/$', views.DownloadView.as_view(), name='download'),
     url(r'^upload/$', views.upload, name='upload'),
-    url(r'^info/$', views.info, name='info'),
-    url(r'^delete/$', views.delete, name='delete'),
-    url(r'^mkdir/$', views.mkdir, name='mkdir'),
-    url(r'^move/$', views.move, name='move'),
-    url(r'^compress/$', views.compress, name='compress'),
-    url(r'^extract/$', views.extract, name='extract'),
+    url(r'^info/$', views.InfoView.as_view(), name='info'),
+    url(r'^delete/$', views.DeleteView.as_view(), name='delete'),
+    url(r'^mkdir/$', views.MkdirView.as_view(), name='mkdir'),
+    url(r'^move/$', views.MoveView.as_view(), name='move'),
+    url(r'^compress/$', views.CompressView.as_view(), name='compress'),
+    url(r'^extract/$', views.ExtractView.as_view(), name='extract'),
 
     url(r'^fav/add/$', views.fav_add, name='fav_add'),
     url(r'^fav/delete/$', views.fav_delete, name='fav_delete'),
index a4516ed..8ef9d5b 100644 (file)
 from itertools import islice
 import mimetypes
+import os
 
 from django.contrib.auth.decorators import login_required
-from django.core.exceptions import PermissionDenied, SuspiciousOperation
+from django.core.exceptions import PermissionDenied
 from django.http import JsonResponse, StreamingHttpResponse
 from django.shortcuts import get_object_or_404
 from django.template.defaultfilters import filesizeformat
 from django.utils.formats import date_format
 from django.views.decorators.http import require_POST
+from django.views.generic import View
 
-from filex.forms import NewDirForm, RenameForm, FavoriteForm
+from filex.forms import HostPathNameForm, RenameForm, FavoriteForm, HostPathForm, ExtractForm, HostItemsForm, \
+    CompressForm
 from filex.ftp import FTPOperation, FTPException
 from filex.models import Favorite
 from filex.uploadhandler import with_ftp_upload_handler
 
 
-def check_auth(request):
-    if not request.user.is_authenticated():
-        raise PermissionDenied("Login required!")
-    if not request.session['proxy']:
-        raise PermissionDenied("No proxy found!")
+class FTPView(View):
+    method = 'get'
+    form_class = HostPathForm
 
+    @classmethod
+    def as_view(cls, **initkwargs):
+        def process(self, request):
+            if not request.user.is_authenticated():
+                raise PermissionDenied("Login required!")
+            if not request.session['proxy']:
+                raise PermissionDenied("No proxy found!")
 
-def list_content(request):
-    check_auth(request)
+            form = self.form_class(request.POST if self.method == 'post' else request.GET)
 
-    # TODO data validation
-    host = request.GET.get('host')
-    path = request.GET.get('path')
-    if not host or not path:
-        raise SuspiciousOperation("No path or host given!")
+            if not form.is_valid():
+                return JsonResponse({'error': form.errors}, status=400)
 
-    url = 'gsiftp://' + host + path
+            try:
+                return self.handle(FTPOperation(request.session['proxy']), form.cleaned_data)
+            except FTPException as e:
+                status = 400
+                if 'No such file or directory' in e.message:
+                    status = 404
+                elif 'Permission denied' in e.message:
+                    status = 403
 
-    try:
-        listing = FTPOperation(request.session['proxy']).listing(url)
-    except FTPException as e:
-        return JsonResponse({'msg': e.message}, status=400)
+                return JsonResponse({'error': e.message}, status=status)
 
-    data = []
-    # ignore . and .. from beginning of the listing
-    for item in islice(listing, 2, None):
-        item['size'] = filesizeformat(item['size'])
-        item['date'] = date_format(item['date'], 'CUSTOM_DATETIME_FORMAT')
+        setattr(cls, cls.method, process)
 
-        data.append(item)
+        return super(FTPView, cls).as_view(**initkwargs)
 
-    return JsonResponse(data, safe=False)
+    def handle(self, ftp, params):
+        raise NotImplementedError
 
 
-def download(request):
-    check_auth(request)
+class ListView(FTPView):
+    def handle(self, ftp, params):
+        listing = ftp.listing(make_url(params, 'path'))
 
-    # TODO data validation
-    host = request.GET.get('host')
-    path = request.GET.get('path')
-    name = request.GET.get('name')
-    if not host or not path or not name:
-        raise SuspiciousOperation("No path or host or name given!")
+        data = []
+        # ignore . and .. from beginning of the listing
+        for item in islice(listing, 2, None):
+            item['size'] = filesizeformat(item['size'])
+            item['date'] = date_format(item['date'], 'CUSTOM_DATETIME_FORMAT')
 
-    url = 'gsiftp://' + host + path + '/' + name
+            data.append(item)
 
-    mime_type, encoding = mimetypes.guess_type(name)
+        return JsonResponse(data, safe=False)
 
-    response = StreamingHttpResponse(FTPOperation(request.session['proxy']).get(url),
-                                     content_type=mime_type or 'application/octet-stream')
-    response['Content-Disposition'] = 'attachment; filename={}'.format(name)
-    # TODO Content-Length (?)
 
-    if encoding:
-        response['Content-Encoding'] = encoding
+class DownloadView(FTPView):
+    def handle(self, ftp, params):
+        data = ftp.get(make_url(params, 'path'))
 
-    return response
+        name = os.path.basename(params['path'])
+        mime_type, encoding = mimetypes.guess_type(name)
 
+        response = StreamingHttpResponse(data, content_type=mime_type or 'application/octet-stream')
+        response['Content-Disposition'] = u'attachment; filename={}'.format(name)
+        # TODO Content-Length (?)
 
-@with_ftp_upload_handler
-def upload(request):
-    # TODO error handling
-    return JsonResponse({'success': True})
+        if encoding:
+            response['Content-Encoding'] = encoding
 
+        return response
 
-def info(request):
-    check_auth(request)
 
-    # TODO data validation
-    host = request.GET.get('host')
-    path = request.GET.get('path')
-    if not host or not path:
-        raise SuspiciousOperation("No path or host given!")
+class InfoView(FTPView):
+    def handle(self, ftp, params):
+        return JsonResponse(ftp.info(make_url(params, 'path')))
 
-    url = 'gsiftp://' + host + path
 
-    try:
-        return JsonResponse(FTPOperation(request.session['proxy']).info(url))
-    except FTPException as e:
-        status = 400
-        if 'No such file or directory' in e.message:
-            status = 404
-        elif 'Permission denied' in e.message:
-            status = 403
+class DeleteView(FTPView):
+    method = 'post'
+    form_class = HostItemsForm
 
-        return JsonResponse({'msg': e.message}, status=status)
+    def handle(self, ftp, params):
+        url = make_url(params)
+        done, fail = [], {}
 
+        for path in params['dirs']:
+            try:
+                ftp.rmdir(url + path)
+            except FTPException as e:
+                fail[path] = e.message
+            else:
+                done.append(path)
 
-@require_POST
-def delete(request):
-    check_auth(request)
+        for path in params['files']:
+            try:
+                ftp.delete(url + path)
+            except FTPException as e:
+                fail[path] = e.message
+            else:
+                done.append(path)
 
-    # TODO data validation
-    host = request.POST.get('host')
-    path = request.POST.get('path')
-    dirs = request.POST.getlist('dirs')
-    files = request.POST.getlist('files')
-    if not host or not path or not (files or dirs):
-        raise SuspiciousOperation("No path or host or files given!")
+        return JsonResponse({'done': done, 'fail': fail})
 
-    url = 'gsiftp://' + host + path + '/'
-    ftp = FTPOperation(request.session['proxy'])
 
-    done, fail = [], {}
+class MkdirView(FTPView):
+    method = 'post'
+    form_class = HostPathNameForm
 
-    for name in dirs:
-        try:
-            ftp.rmdir(url + name)
-        except FTPException as e:
-            fail[name] = e.message
-        else:
-            done.append(name)
+    def handle(self, ftp, params):
+        ftp.mkdir(make_url(params, 'path', 'name'))
 
-    for name in files:
-        try:
-            ftp.delete(url + name)
-        except FTPException as e:
-            fail[name] = e.message
-        else:
-            done.append(name)
+        return JsonResponse({'success': True})
 
-    return JsonResponse({'done': done, 'fail': fail})
 
+class MoveView(FTPView):
+    method = 'post'
+    form_class = RenameForm
 
-@require_POST
-def mkdir(request):
-    check_auth(request)
+    def handle(self, ftp, params):
+        print params
+        ftp.move(make_url(params, 'src'), make_url(params, 'dst'))
 
-    # TODO actual data validation
-    form = NewDirForm(request.POST)
+        return JsonResponse({'success': True})
 
-    if form.is_valid():
-        host = form.cleaned_data['host']
-        path = form.cleaned_data['path']
-        name = form.cleaned_data['name']
 
-        url = 'gsiftp://' + host + path + '/' + name
+class CompressView(FTPView):
+    method = 'post'
+    form_class = CompressForm
 
+    def handle(self, ftp, params):
         try:
-            FTPOperation(request.session['proxy']).mkdir(url)
-        except FTPException as e:
-            msg = e.message
-        else:
-            return JsonResponse({'success': True})
-    else:
-        msg = form.errors
-
-    return JsonResponse({'msg': msg}, status=400)
-
+            # consume generator with command output
+            list(ftp.compress(make_url(params), params['path'], params['files'], params['archive']))
+        except ValueError as e:
+            return JsonResponse({'error': e.message}, status=400)
 
-@require_POST
-def move(request):
-    check_auth(request)
-
-    # TODO actual data validation
-    form = RenameForm(request.POST)
+        return JsonResponse({'success': True})
 
-    if form.is_valid():
-        host = form.cleaned_data['host']
-        path = form.cleaned_data['path']
-        src = form.cleaned_data['src']
-        dst = form.cleaned_data['dst']
 
-        src_url = 'gsiftp://' + host + path + '/' + src
-        dst_url = 'gsiftp://' + host + path + '/' + dst
+class ExtractView(FTPView):
+    method = 'post'
+    form_class = ExtractForm
 
+    def handle(self, ftp, params):
         try:
-            FTPOperation(request.session['proxy']).move(src_url, dst_url)
-        except FTPException as e:
-            msg = e.message
-        else:
-            return JsonResponse({'success': True})
-    else:
-        msg = form.errors
+            # consume generator with command output
+            list(ftp.extract(make_url(params), params['path'], params['dst']))
+        except ValueError as e:
+            return JsonResponse({'error': e.message}, status=400)
 
-    return JsonResponse({'msg': msg}, status=400)
+        return JsonResponse({'success': True})
 
 
-@require_POST
-def compress(request):
-    check_auth(request)
-
-    # TODO data validation
-    host = request.POST.get('host')
-    path = request.POST.get('path')
-    files = request.POST.getlist('files')
-    archive = request.POST.get('archive')
-    if not host or not path or not files or not archive:
-        raise SuspiciousOperation("No path or host or files or archive given!")
-
-    server = 'gsiftp://' + host
-
-    try:
-        # consume generator with command output
-        list(FTPOperation(request.session['proxy']).compress(server, path, files, archive))
-    except FTPException as e:
-        return JsonResponse({'msg': e.message}, status=400)
-    else:
-        return JsonResponse({'success': True})
+def make_url(params, *parts):
+    return 'gsiftp://' + params['host'] + (os.path.join(*[params[part] for part in parts]) if parts else '')
 
 
 @require_POST
-def extract(request):
-    check_auth(request)
-
-    # TODO data validation
-    host = request.POST.get('host')
-    archive = request.POST.get('archive')
-    dst = request.POST.get('dst')
-    if not host or not archive or not dst:
-        raise SuspiciousOperation("No path or host or files or archive given!")
-
-    server = 'gsiftp://' + host
-
-    try:
-        # consume generator with command output
-        list(FTPOperation(request.session['proxy']).extract(server, archive, dst))
-    except FTPException as e:
-        return JsonResponse({'msg': e.message}, status=400)
-    else:
-        return JsonResponse({'success': True})
+@with_ftp_upload_handler
+def upload(request):
+    return JsonResponse({'success': True})
 
 
 @require_POST
@@ -253,7 +192,7 @@ def fav_add(request):
         return JsonResponse({'group': 'usr', 'host': instance.host, 'path': instance.path,
                              'value': instance.host + instance.path})
 
-    return JsonResponse({'msg': form.errors}, status=400)
+    return JsonResponse({'error': form.errors}, status=400)
 
 
 @require_POST
index 5e3e7a8..0b0d55a 100644 (file)
             }
 
             function failModal(msg) {
-                return function() {
+                return function(xhr) {
                     var $errorModal = $('#error-modal');
 
                     $errorModal.find('#error-modal-label').text('Błąd serwera');
                     $errorModal.find('.modal-body').html($('<h4>', {text: msg}));
 
+                    var error = (xhr.responseJSON || {}).error || undefined;
+
+                    if (typeof error === 'string')
+                        $errorModal.find('.modal-body').append($('<pre>', {text: error}));
+
                     filex.idle();
                     $errorModal.modal();
 
                 };
             }
 
+            function conflictingName(name, modal) {
+                if (filex.files.some(function(item) { return item.get('name') == name })) {
+                    modal.find('.alert').remove();
+
+                    $('<div>', {
+                        'class': 'alert alert-danger',
+                        html: '<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> Plik o podanej już nazwie istnieje!'
+                    }).prependTo(modal.find('.modal-body'));
+
+                    return true;
+                }
+
+                return false;
+            }
+
             filex.files.on('change:checked reset', function() {
                 var selected = filex.selectedFiles().length;
 
@@ -68,7 +88,7 @@
             });
 
             $('#btn-upload').on('click', function() {
-                var url = '{% url 'gridftp_upload' %}?' + $.param({host: filex.host, path: filex.path.full() + '/'});
+                var url = '{% url 'gridftp_upload' %}?' + $.param({host: filex.host, path: filex.path.full()});
 
                 var win = window.open(url, url, 'height=500,width=800');
                 win.focus();
 
             $('#btn-delete').on('click', function() {
                 var selected = _.groupBy(filex.selectedFiles(), function(item) { return item.get('type') }),
-                    dirs = _.map(selected.directory || [], function (item) { return item.get('name') }),
-                    files = _.map(selected.file || [], function (item) { return item.get('name') }),
+                    path = filex.path.full() + '/',
+                    dirs = _.map(selected.directory || [], function (item) { return path + item.get('name') }),
+                    files = _.map(selected.file || [], function (item) { return path + item.get('name') }),
                     data = {
                         host: filex.host,
-                        path: filex.path.full() + '/',
                         dirs: dirs,
                         files: files
                     },
                             $errorModal.find('#error-modal-label').text('Błąd');
 
                             for (var i in keys) {
-                                $('<dt>', {text: keys[i]}).appendTo($errorList);
-                                $('<dd>', {text: response.fail[keys[i]]}).appendTo($errorList);
+                                if(keys.hasOwnProperty(i)) {
+                                    $('<dt>', {text: keys[i].replace(path, '')}).appendTo($errorList);
+                                    $('<dd>', {text: response.fail[keys[i]]}).appendTo($errorList);
+                                }
                             }
 
                             $errorModal.modal();
                 var $this = $(this);
 
                 e.preventDefault();
+
+                if (conflictingName($this.find('#id_name').val(), $this))
+                    return;
+
                 filex.busy();
                 $this.modal('hide');
 
             });
 
             $('#rename-form').on('show.bs.modal', function() {
-                var $this = $(this);
+                $(this).find('#id_dst').val(filex.selectedFiles()[0].get('name'));
+            }).on('submit', function(e) {
+                e.preventDefault();
 
-                var file = filex.selectedFiles()[0];
+                var $this = $(this),
+                    path = filex.path.full() + '/',
+                    newName = $this.find('#id_dst').val(),
+                    data = {
+                        host: filex.host,
+                        src: path + filex.selectedFiles()[0].get('name'),
+                        dst: path + newName
+                    };
 
-                $this.find('#id_host').val(filex.host);
-                $this.find('#id_path').val(filex.path.full());
-                $this.find('#id_src').val(file.get('name'));
-                $this.find('#id_dst').val(file.get('name'));
-            }).on('submit', function(e) {
-                var $this = $(this);
+                if (conflictingName(newName, $this))
+                    return;
 
-                e.preventDefault();
                 filex.busy();
                 $this.modal('hide');
 
-                $.post($this.attr('action'), $this.serialize(), function() {
+                $.post($this.attr('action'), data, function() {
                     status('Nazwę zmieniono pomyślnie');
                     filex.reloadFiles();
                 }, 'json').fail(failModal('Nie udało się zmienić nazwy'));
                 e.preventDefault();
 
                 var $this = $(this),
-                    name = $this.find('#id_name').val(),
+                    name = $this.find('#id_archive').val(),
                     type = $this.find('#id_type').val(),
+                    path = filex.path.full(),
                     archive = name + (name.endsWith(type) ? '' : type),
                     data = {
                         host: filex.host,
-                        path: filex.path.full(),
+                        path: path,
                         files: _.map(filex.selectedFiles(), function (item) { return item.get('name') }),
-                        archive: archive
+                        archive: path  + '/' + archive
                     };
 
-                // check if maybe file with given name exists
-                if (filex.files.some(function(item) { return item.get('name') == archive })) {
-                    $this.find('.alert').remove();
-
-                    $('<div>', {
-                        'class': 'alert alert-danger',
-                        html: '<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> Plik o podanej już nazwie istnieje!'
-                    }).prependTo($this.find('.modal-body'));
-
+                if (conflictingName(archive, $this))
                     return;
-                }
 
                 filex.busy();
                 $this.modal('hide');
 
                 var data = {
                     host: filex.host,
-                    archive: filex.path.full() + '/' + filex.selectedFiles()[0].get('name'),
+                    path: filex.path.full() + '/' + filex.selectedFiles()[0].get('name'),
                     dst: filex.path.full()
                 };
 
index 72ab335..3f3532e 100644 (file)
@@ -15,7 +15,7 @@ from django.utils.timezone import UTC
 from django_openid_auth.views import make_consumer
 from openid.extensions import ax
 
-from filex.forms import NewDirForm, RenameForm, ArchiveForm
+from filex.forms import HostPathNameForm, RenameForm, ArchiveForm
 from qcg.forms import FiltersForm, ColumnsForm, JobDescriptionForm, EnvFormSet
 from qcg.utils import paginator_context
 from qcg.service import update_user_data, submit_job
@@ -184,7 +184,7 @@ def job_new(request):
 @login_required
 def gridftp(request):
     return render(request, 'qcg/gridftp.html',
-                  {'new_dir_form': NewDirForm(), 'rename_form': RenameForm(),  'archive_form': ArchiveForm()})
+                  {'new_dir_form': HostPathNameForm(), 'rename_form': RenameForm(),  'archive_form': ArchiveForm()})
 
 
 def gridftp_upload(request):