1import os
2
3import json
4import logging
5import posixpath
6from django.http import HttpResponse
7from django.utils.translation import ugettext as _
8from rest_framework import status
9from rest_framework.authentication import SessionAuthentication
10from rest_framework.permissions import IsAuthenticated
11from rest_framework.response import Response
12from rest_framework.reverse import reverse
13
14from rest_framework.views import APIView
15from urllib.parse import quote
16
17from seahub.api2.authentication import RepoAPITokenAuthentication
18from seahub.repo_api_tokens.utils import get_dir_file_info_list
19from seahub.api2.throttling import UserRateThrottle
20from seahub.api2.utils import api_error, to_python_boolean
21
22from seaserv import seafile_api, get_repo, check_quota
23from pysearpc import SearpcError
24
25import seahub.settings as settings
26from seahub.repo_api_tokens.utils import get_dir_file_recursively
27from seahub.constants import PERMISSION_READ
28from seahub.utils import normalize_dir_path, check_filename_with_rename, gen_file_upload_url, is_valid_dirent_name, \
29    normalize_file_path, render_error, gen_file_get_url, is_pro_version
30from seahub.utils.timeutils import timestamp_to_isoformat_timestr
31
32logger = logging.getLogger(__name__)
33json_content_type = 'application/json; charset=utf-8'
34HTTP_443_ABOVE_QUOTA = 443
35HTTP_520_OPERATION_FAILED = 520
36
37
38def check_folder_permission_by_repo_api(request, repo_id, path):
39    """
40    Check repo/folder/file access permission of a repo_api_token.
41    :param request: request obj
42    :param repo_id: repo's id
43    :param path: repo path
44    :return:
45    """
46    repo_status = seafile_api.get_repo_status(repo_id)
47    if repo_status == 1:
48        return PERMISSION_READ
49
50    return request.repo_api_token_obj.permission  # and return repo_api_token's permission
51
52
53class ViaRepoDirView(APIView):
54    authentication_classes = (RepoAPITokenAuthentication, SessionAuthentication)
55    throttle_classes = (UserRateThrottle,)
56
57    def get_dir_info(self, repo_id, dir_path):
58
59        dir_obj = seafile_api.get_dirent_by_path(repo_id, dir_path)
60        dir_info = {
61            'type': 'dir',
62            'repo_id': repo_id,
63            'parent_dir': os.path.dirname(dir_path.rstrip('/')),
64            'obj_name': dir_obj.obj_name,
65            'obj_id': dir_obj.obj_id,
66            'mtime': timestamp_to_isoformat_timestr(dir_obj.mtime),
67        }
68
69        return dir_info
70
71    def get(self, request, format=None):
72        repo_id = request.repo_api_token_obj.repo_id
73        # argument check
74        recursive = request.GET.get('recursive', '0')
75        if recursive not in ('1', '0'):
76            error_msg = "If you want to get recursive dir entries, you should set 'recursive' argument as '1'."
77            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
78
79        request_type = request.GET.get('type', '')
80        if request_type and request_type not in ('f', 'd'):
81            error_msg = "'type should be 'f' or 'd'."
82            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
83
84        with_thumbnail = request.GET.get('with_thumbnail', 'false')
85        if with_thumbnail not in ('true', 'false'):
86            error_msg = 'with_thumbnail invalid.'
87            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
88
89        with_thumbnail = to_python_boolean(with_thumbnail)
90        thumbnail_size = request.GET.get('thumbnail_size', 48)
91        try:
92            thumbnail_size = int(thumbnail_size)
93        except ValueError:
94            error_msg = 'thumbnail_size invalid.'
95            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
96
97        with_parents = request.GET.get('with_parents', 'false')
98        if with_parents not in ('true', 'false'):
99            error_msg = 'with_parents invalid.'
100            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
101
102        with_parents = to_python_boolean(with_parents)
103
104        # recource check
105        repo = seafile_api.get_repo(repo_id)
106        if not repo:
107            error_msg = 'Library %s not found.' % repo_id
108            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
109
110        parent_dir = request.GET.get('path', '/')
111        parent_dir = normalize_dir_path(parent_dir)
112
113        dir_id = seafile_api.get_dir_id_by_path(repo_id, parent_dir)
114        if not dir_id:
115            error_msg = 'Folder %s not found.' % parent_dir
116            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
117
118        # permission check
119        permission = check_folder_permission_by_repo_api(request, repo_id, parent_dir)
120        if not permission:
121            error_msg = 'Permission denied.'
122            return api_error(status.HTTP_403_FORBIDDEN, error_msg)
123
124        # get dir/file list recursively
125        # username = request.user.username
126        username = seafile_api.get_repo_owner(repo_id)
127        if recursive == '1':
128
129            try:
130                dir_file_info_list = get_dir_file_recursively(repo_id, parent_dir, [])
131            except Exception as e:
132                logger.error(e)
133                error_msg = 'Internal Server Error'
134                return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
135
136            response_dict = {}
137            response_dict['dirent_list'] = []
138
139            if request_type == 'f':
140                for item in dir_file_info_list:
141                    if item['type'] == 'file':
142                        response_dict['dirent_list'].append(item)
143            elif request_type == 'd':
144                for item in dir_file_info_list:
145                    if item['type'] == 'dir':
146                        response_dict['dirent_list'].append(item)
147            else:
148                response_dict['dirent_list'] = dir_file_info_list
149
150            return Response(response_dict)
151
152        parent_dir_list = []
153        if not with_parents:
154            # only return dirent list in current parent folder
155            parent_dir_list.append(parent_dir)
156        else:
157            # if value of 'path' parameter is '/a/b/c' add with_parents's is 'true'
158            # then return dirent list in '/', '/a', '/a/b' and '/a/b/c'.
159            if parent_dir == '/':
160                parent_dir_list.append(parent_dir)
161            else:
162                tmp_parent_dir = '/'
163                parent_dir_list.append(tmp_parent_dir)
164                for folder_name in parent_dir.strip('/').split('/'):
165                    tmp_parent_dir = posixpath.join(tmp_parent_dir, folder_name)
166                    tmp_parent_dir = normalize_dir_path(tmp_parent_dir)
167                    parent_dir_list.append(tmp_parent_dir)
168
169        all_dir_info_list = []
170        all_file_info_list = []
171
172        try:
173            for parent_dir in parent_dir_list:
174                # get dir file info list
175                dir_info_list, file_info_list = get_dir_file_info_list(username,
176                                                                       request_type, repo, parent_dir, with_thumbnail,
177                                                                       thumbnail_size)
178                all_dir_info_list.extend(dir_info_list)
179                all_file_info_list.extend(file_info_list)
180        except Exception as e:
181            logger.error(e)
182            error_msg = 'Internal Server Error'
183            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
184
185        response_dict = {}
186        response_dict["user_perm"] = permission
187        response_dict["dir_id"] = dir_id
188        response_dict["repo_name"] = repo.repo_name
189
190        if request_type == 'f':
191            response_dict['dirent_list'] = all_file_info_list
192        elif request_type == 'd':
193            response_dict['dirent_list'] = all_dir_info_list
194        else:
195            response_dict['dirent_list'] = all_dir_info_list + all_file_info_list
196
197        return Response(response_dict)
198
199    def post(self, request, format=None):
200        repo_id = request.repo_api_token_obj.repo_id
201        # argument check
202        path = request.GET.get('path', None)
203        if not path or path[0] != '/':
204            error_msg = 'path invalid.'
205            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
206
207        if path == '/':
208            error_msg = 'Can not operate root dir.'
209            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
210
211        operation = request.data.get('operation', None)
212        if not operation:
213            error_msg = 'operation invalid.'
214            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
215
216        operation = operation.lower()
217        if operation not in ('mkdir', 'rename', 'revert'):
218            error_msg = "operation can only be 'mkdir', 'rename' or 'revert'."
219            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
220
221        # resource check
222        repo = seafile_api.get_repo(repo_id)
223        if not repo:
224            error_msg = 'Library %s not found.' % repo_id
225            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
226
227        path = path.rstrip('/')
228        username = request.user.username
229        parent_dir = os.path.dirname(path)
230        if operation == 'mkdir':
231            # resource check
232            parent_dir_id = seafile_api.get_dir_id_by_path(repo_id, parent_dir)
233            if not parent_dir_id:
234                error_msg = 'Folder %s not found.' % parent_dir
235                return api_error(status.HTTP_404_NOT_FOUND, error_msg)
236
237            # permission check
238            if check_folder_permission_by_repo_api(request, repo_id, parent_dir) != 'rw':
239                error_msg = 'Permission denied.'
240                return api_error(status.HTTP_403_FORBIDDEN, error_msg)
241
242            new_dir_name = os.path.basename(path)
243
244            if not is_valid_dirent_name(new_dir_name):
245                return api_error(status.HTTP_400_BAD_REQUEST,
246                                 'name invalid.')
247
248            retry_count = 0
249            while retry_count < 10:
250                new_dir_name = check_filename_with_rename(repo_id,
251                                                          parent_dir, new_dir_name)
252                try:
253                    seafile_api.post_dir(repo_id,
254                                         parent_dir, new_dir_name, username)
255                    break
256                except SearpcError as e:
257                    if str(e) == 'file already exists':
258                        retry_count += 1
259                    else:
260                        logger.error(e)
261                        error_msg = 'Internal Server Error'
262                        return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR,
263                                         error_msg)
264
265            new_dir_path = posixpath.join(parent_dir, new_dir_name)
266            dir_info = self.get_dir_info(repo_id, new_dir_path)
267            resp = Response(dir_info)
268
269            return resp
270
271        if operation == 'rename':
272            # resource check
273            dir_id = seafile_api.get_dir_id_by_path(repo_id, path)
274            if not dir_id:
275                error_msg = 'Folder %s not found.' % path
276                return api_error(status.HTTP_404_NOT_FOUND, error_msg)
277
278            # permission check
279            if check_folder_permission_by_repo_api(request, repo_id, path) != 'rw':
280                error_msg = 'Permission denied.'
281                return api_error(status.HTTP_403_FORBIDDEN, error_msg)
282
283            old_dir_name = os.path.basename(path)
284            new_dir_name = request.data.get('newname', None)
285
286            if not new_dir_name:
287                error_msg = 'newname invalid.'
288                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
289
290            if not is_valid_dirent_name(new_dir_name):
291                return api_error(status.HTTP_400_BAD_REQUEST,
292                                 'name invalid.')
293
294            if new_dir_name == old_dir_name:
295                dir_info = self.get_dir_info(repo_id, path)
296                resp = Response(dir_info)
297                return resp
298
299            try:
300                # rename duplicate name
301                new_dir_name = check_filename_with_rename(repo_id, parent_dir, new_dir_name)
302                # rename dir
303                seafile_api.rename_file(repo_id, parent_dir, old_dir_name,
304                                        new_dir_name, username)
305
306                new_dir_path = posixpath.join(parent_dir, new_dir_name)
307                dir_info = self.get_dir_info(repo_id, new_dir_path)
308                resp = Response(dir_info)
309                return resp
310            except SearpcError as e:
311                logger.error(e)
312                error_msg = 'Internal Server Error'
313                return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
314
315        if operation == 'revert':
316            commit_id = request.data.get('commit_id', None)
317            if not commit_id:
318                error_msg = 'commit_id invalid.'
319                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
320
321            if seafile_api.get_dir_id_by_path(repo_id, path):
322                # dir exists in repo
323                if check_folder_permission_by_repo_api(request, repo_id, path) != 'rw':
324                    error_msg = 'Permission denied.'
325                    return api_error(status.HTTP_403_FORBIDDEN, error_msg)
326            else:
327                # dir NOT exists in repo
328                if check_folder_permission_by_repo_api(request, repo_id, '/') != 'rw':
329                    error_msg = 'Permission denied.'
330                    return api_error(status.HTTP_403_FORBIDDEN, error_msg)
331
332            try:
333                seafile_api.revert_dir(repo_id, commit_id, path, username)
334            except Exception as e:
335                logger.error(e)
336                error_msg = 'Internal Server Error'
337                return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
338
339            return Response({'success': True})
340
341
342class ViaRepoUploadLinkView(APIView):
343    authentication_classes = (RepoAPITokenAuthentication, SessionAuthentication)
344    throttle_classes = (UserRateThrottle,)
345
346    def get(self, request, format=None):
347        repo_id = request.repo_api_token_obj.repo_id
348        # recourse check
349        repo = seafile_api.get_repo(repo_id)
350        if not repo:
351            error_msg = 'Library %s not found.' % repo_id
352            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
353
354        parent_dir = request.GET.get('path', '/')
355        dir_id = seafile_api.get_dir_id_by_path(repo_id, parent_dir)
356        if not dir_id:
357            error_msg = 'Folder %s not found.' % parent_dir
358            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
359
360        # permission check
361        if check_folder_permission_by_repo_api(request, repo_id, parent_dir) != 'rw':
362            return api_error(status.HTTP_403_FORBIDDEN,
363                             'You do not have permission to access this folder.')
364
365        if check_quota(repo_id) < 0:
366            return api_error(HTTP_443_ABOVE_QUOTA, "Out of quota.")
367
368        obj_data = {'parent_dir': parent_dir}
369        if is_pro_version():
370            obj_data['anonymous_user'] = request.repo_api_token_obj.app_name
371        obj_id = json.dumps(obj_data)
372        token = seafile_api.get_fileserver_access_token(repo_id,
373                                                        obj_id, 'upload', '',
374                                                        use_onetime=False)
375
376        if not token:
377            error_msg = 'Internal Server Error'
378            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)
379        req_from = request.GET.get('from', 'api')
380        if req_from == 'api':
381            try:
382                replace = to_python_boolean(request.GET.get('replace', '0'))
383            except ValueError:
384                replace = False
385            url = gen_file_upload_url(token, 'upload-api', replace)
386        elif req_from == 'web':
387            url = gen_file_upload_url(token, 'upload-aj')
388        else:
389            error_msg = 'from invalid.'
390            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
391
392        return Response(url)
393
394
395class ViaRepoDownloadLinkView(APIView):
396    authentication_classes = (RepoAPITokenAuthentication, SessionAuthentication)
397    throttle_classes = (UserRateThrottle,)
398
399    def get(self, request):
400        path = request.GET.get('path')
401        if not path:
402            error_msg = 'path invalid.'
403            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
404
405        repo_id = request.repo_api_token_obj.repo_id
406        path = normalize_file_path(path)
407        filename = os.path.basename(path)
408        file_id = seafile_api.get_file_id_by_path(repo_id, path)
409        if not file_id:
410            error_msg = 'File not found'
411            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
412
413        token = seafile_api.get_fileserver_access_token(
414            repo_id, file_id, 'download', request.repo_api_token_obj.app_name,
415            use_onetime=settings.FILESERVER_TOKEN_ONCE_ONLY)
416        download_url = gen_file_get_url(token, filename)
417        return Response(download_url)
418
419
420class RepoInfoView(APIView):
421    authentication_classes = (RepoAPITokenAuthentication, SessionAuthentication)
422    throttle_classes = (UserRateThrottle,)
423
424    def get(self, request):
425        repo_id = request.repo_api_token_obj.repo_id
426        repo = seafile_api.get_repo(repo_id)
427        if not repo:
428            error_msg = 'Library %(repo_id)s not found.' % {'repo_id': repo_id}
429            return api_error(status.HTTP_404_NOT_FOUND, error_msg)
430        data = {
431            'repo_id': repo.id,
432            'repo_name': repo.name,
433        }
434        return Response(data)
435