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