1# Copyright (c) 2012-2016 Seafile Ltd. 2# encoding: utf-8 3import hashlib 4import os 5import stat 6import json 7import mimetypes 8import logging 9import posixpath 10 11from django.core.cache import cache 12from django.urls import reverse, resolve 13from django.contrib import messages 14from django.http import HttpResponse, Http404, \ 15 HttpResponseRedirect 16from django.shortcuts import render, redirect 17from django.utils.http import urlquote 18from django.utils.html import escape 19from django.utils.translation import ugettext as _ 20from django.views.decorators.http import condition 21 22import seaserv 23from seaserv import get_repo, get_commits, \ 24 seafserv_threaded_rpc, is_repo_owner, \ 25 get_file_size, seafile_api 26from pysearpc import SearpcError 27 28from seahub.avatar.util import get_avatar_file_storage 29from seahub.auth.decorators import login_required 30from seahub.auth import login as auth_login 31from seahub.auth import get_backends 32from seahub.base.accounts import User 33from seahub.base.decorators import require_POST 34from seahub.base.models import ClientLoginToken 35from seahub.options.models import UserOptions, CryptoOptionNotSetError 36from seahub.profile.models import Profile 37from seahub.share.models import FileShare, UploadLinkShare 38from seahub.revision_tag.models import RevisionTags 39from seahub.utils import render_permission_error, render_error, \ 40 gen_shared_upload_link, is_org_context, \ 41 gen_dir_share_link, gen_file_share_link, get_file_type_and_ext, \ 42 get_user_repos, EMPTY_SHA1, gen_file_get_url, \ 43 new_merge_with_no_conflict, get_max_upload_file_size, \ 44 is_pro_version, FILE_AUDIT_ENABLED, is_valid_dirent_name, \ 45 is_windows_operating_system, seafevents_api, IS_EMAIL_CONFIGURED 46from seahub.utils.star import get_dir_starred_files 47from seahub.utils.repo import get_library_storages, parse_repo_perm 48from seahub.utils.file_op import check_file_lock 49from seahub.utils.timeutils import utc_to_local 50from seahub.utils.auth import get_login_bg_image_path 51import seahub.settings as settings 52from seahub.settings import AVATAR_FILE_STORAGE, \ 53 ENABLE_FOLDER_PERM, ENABLE_REPO_SNAPSHOT_LABEL, \ 54 UNREAD_NOTIFICATIONS_REQUEST_INTERVAL, SHARE_LINK_EXPIRE_DAYS_MIN, \ 55 SHARE_LINK_EXPIRE_DAYS_MAX, SHARE_LINK_EXPIRE_DAYS_DEFAULT, \ 56 UPLOAD_LINK_EXPIRE_DAYS_MIN, UPLOAD_LINK_EXPIRE_DAYS_MAX, UPLOAD_LINK_EXPIRE_DAYS_DEFAULT, \ 57 SEAFILE_COLLAB_SERVER, ENABLE_RESET_ENCRYPTED_REPO_PASSWORD, \ 58 ADDITIONAL_SHARE_DIALOG_NOTE, ADDITIONAL_APP_BOTTOM_LINKS, ADDITIONAL_ABOUT_DIALOG_LINKS, \ 59 DTABLE_WEB_SERVER 60 61from seahub.wopi.settings import ENABLE_OFFICE_WEB_APP 62from seahub.onlyoffice.settings import ONLYOFFICE_DESKTOP_EDITORS_PORTAL_LOGIN 63from seahub.ocm.settings import ENABLE_OCM, OCM_REMOTE_SERVERS 64from seahub.constants import HASH_URLS, PERMISSION_READ 65from seahub.group.settings import GROUP_IMPORT_MEMBERS_EXTRA_MSG 66 67from seahub.weixin.settings import ENABLE_WEIXIN 68 69LIBRARY_TEMPLATES = getattr(settings, 'LIBRARY_TEMPLATES', {}) 70CUSTOM_NAV_ITEMS = getattr(settings, 'CUSTOM_NAV_ITEMS', '') 71 72from constance import config 73 74# Get an instance of a logger 75logger = logging.getLogger(__name__) 76 77def validate_owner(request, repo_id): 78 """ 79 Check whether user in the request owns the repo. 80 81 """ 82 ret = is_repo_owner(request.user.username, repo_id) 83 84 return True if ret else False 85 86def is_registered_user(email): 87 """ 88 Check whether user is registerd. 89 90 """ 91 try: 92 user = User.objects.get(email=email) 93 except User.DoesNotExist: 94 user = None 95 96 return True if user else False 97 98_default_repo_id = None 99def get_system_default_repo_id(): 100 global _default_repo_id 101 if not _default_repo_id: 102 try: 103 _default_repo_id = seaserv.seafserv_threaded_rpc.get_system_default_repo_id() 104 except SearpcError as e: 105 logger.error(e) 106 return _default_repo_id 107 108def check_folder_permission(request, repo_id, path): 109 """Check repo/folder/file access permission of a user. 110 111 Arguments: 112 - `request`: 113 - `repo_id`: 114 - `path`: 115 """ 116 repo_status = seafile_api.get_repo_status(repo_id) 117 if repo_status == 1: 118 return PERMISSION_READ 119 120 username = request.user.username 121 return seafile_api.check_permission_by_path(repo_id, path, username) 122 123def gen_path_link(path, repo_name): 124 """ 125 Generate navigate paths and links in repo page. 126 127 """ 128 if path and path[-1] != '/': 129 path += '/' 130 131 paths = [] 132 links = [] 133 if path and path != '/': 134 paths = path[1:-1].split('/') 135 i = 1 136 for name in paths: 137 link = '/' + '/'.join(paths[:i]) 138 i = i + 1 139 links.append(link) 140 if repo_name: 141 paths.insert(0, repo_name) 142 links.insert(0, '/') 143 144 zipped = list(zip(paths, links)) 145 146 return zipped 147 148def get_file_download_link(repo_id, obj_id, path): 149 """Generate file download link. 150 151 Arguments: 152 - `repo_id`: 153 - `obj_id`: 154 - `filename`: 155 """ 156 return reverse('download_file', args=[repo_id, obj_id]) + '?p=' + \ 157 urlquote(path) 158 159def get_repo_dirents(request, repo, commit, path, offset=-1, limit=-1): 160 """List repo dirents based on commit id and path. Use ``offset`` and 161 ``limit`` to do paginating. 162 163 Returns: A tupple of (file_list, dir_list, dirent_more) 164 165 TODO: Some unrelated parts(file sharing, stars, modified info, etc) need 166 to be pulled out to multiple functions. 167 """ 168 169 dir_list = [] 170 file_list = [] 171 dirent_more = False 172 if commit.root_id == EMPTY_SHA1: 173 return ([], [], False) if limit == -1 else ([], [], False) 174 else: 175 try: 176 dirs = seafile_api.list_dir_by_commit_and_path(commit.repo_id, 177 commit.id, path, 178 offset, limit) 179 if not dirs: 180 return ([], [], False) 181 except SearpcError as e: 182 logger.error(e) 183 return ([], [], False) 184 185 if limit != -1 and limit == len(dirs): 186 dirent_more = True 187 188 username = request.user.username 189 starred_files = get_dir_starred_files(username, repo.id, path) 190 fileshares = FileShare.objects.filter(repo_id=repo.id).filter(username=username) 191 uploadlinks = UploadLinkShare.objects.filter(repo_id=repo.id).filter(username=username) 192 193 view_dir_base = reverse('lib_view', args=[repo.id, repo.name, '']) 194 dl_dir_base = reverse('repo_download_dir', args=[repo.id]) 195 file_history_base = reverse('file_revisions', args=[repo.id]) 196 for dirent in dirs: 197 dirent.last_modified = dirent.mtime 198 dirent.sharelink = '' 199 dirent.uploadlink = '' 200 if stat.S_ISDIR(dirent.props.mode): 201 dpath = posixpath.join(path, dirent.obj_name) 202 if dpath[-1] != '/': 203 dpath += '/' 204 for share in fileshares: 205 if dpath == share.path: 206 dirent.sharelink = gen_dir_share_link(share.token) 207 dirent.sharetoken = share.token 208 break 209 for link in uploadlinks: 210 if dpath == link.path: 211 dirent.uploadlink = gen_shared_upload_link(link.token) 212 dirent.uploadtoken = link.token 213 break 214 p_dpath = posixpath.join(path, dirent.obj_name) 215 dirent.view_link = view_dir_base + '?p=' + urlquote(p_dpath) 216 dirent.dl_link = dl_dir_base + '?p=' + urlquote(p_dpath) 217 dir_list.append(dirent) 218 else: 219 file_list.append(dirent) 220 if repo.version == 0: 221 dirent.file_size = get_file_size(repo.store_id, repo.version, dirent.obj_id) 222 else: 223 dirent.file_size = dirent.size 224 dirent.starred = False 225 fpath = posixpath.join(path, dirent.obj_name) 226 p_fpath = posixpath.join(path, dirent.obj_name) 227 dirent.view_link = reverse('view_lib_file', args=[repo.id, p_fpath]) 228 dirent.dl_link = get_file_download_link(repo.id, dirent.obj_id, 229 p_fpath) 230 dirent.history_link = file_history_base + '?p=' + urlquote(p_fpath) 231 if fpath in starred_files: 232 dirent.starred = True 233 for share in fileshares: 234 if fpath == share.path: 235 dirent.sharelink = gen_file_share_link(share.token) 236 dirent.sharetoken = share.token 237 break 238 239 return (file_list, dir_list, dirent_more) 240 241def get_unencry_rw_repos_by_user(request): 242 """Get all unencrypted repos a logged-in user can read and write. 243 """ 244 username = request.user.username 245 if not username: 246 return [] 247 248 def has_repo(repos, repo): 249 for r in repos: 250 if repo.id == r.id: 251 return True 252 return False 253 254 org_id = request.user.org.org_id if is_org_context(request) else None 255 owned_repos, shared_repos, groups_repos, public_repos = get_user_repos( 256 username, org_id=org_id) 257 258 accessible_repos = [] 259 260 for r in owned_repos: 261 if r.is_virtual: 262 continue 263 264 if not has_repo(accessible_repos, r) and not r.encrypted: 265 accessible_repos.append(r) 266 267 for r in shared_repos + groups_repos + public_repos: 268 if not has_repo(accessible_repos, r) and not r.encrypted: 269 if check_folder_permission(request, r.id, '/') == 'rw': 270 accessible_repos.append(r) 271 272 return accessible_repos 273 274def render_recycle_root(request, repo_id, referer): 275 repo = get_repo(repo_id) 276 if not repo: 277 raise Http404 278 279 return render(request, 'repo_dir_recycle_view.html', { 280 'show_recycle_root': True, 281 'repo': repo, 282 'repo_dir_name': repo.name, 283 'enable_clean': config.ENABLE_USER_CLEAN_TRASH, 284 'referer': referer, 285 }) 286 287def render_recycle_dir(request, repo_id, commit_id, referer): 288 basedir = request.GET.get('base', '') 289 path = request.GET.get('p', '') 290 if not basedir or not path: 291 return render_recycle_root(request, repo_id) 292 293 if basedir[0] != '/': 294 basedir = '/' + basedir 295 if path[-1] != '/': 296 path += '/' 297 298 repo = get_repo(repo_id) 299 if not repo: 300 raise Http404 301 302 try: 303 commit = seafserv_threaded_rpc.get_commit(repo.id, repo.version, commit_id) 304 except SearpcError as e: 305 logger.error(e) 306 referer = request.META.get('HTTP_REFERER', None) 307 next_page = settings.SITE_ROOT if referer is None else referer 308 return HttpResponseRedirect(next_page) 309 310 if not commit: 311 raise Http404 312 313 zipped = gen_path_link(path, '') 314 315 dir_entries = seafile_api.list_dir_by_commit_and_path(commit.repo_id, 316 commit.id, basedir+path, 317 -1, -1) 318 for dirent in dir_entries: 319 if stat.S_ISDIR(dirent.mode): 320 dirent.is_dir = True 321 else: 322 dirent.is_dir = False 323 324 return render(request, 'repo_dir_recycle_view.html', { 325 'show_recycle_root': False, 326 'repo': repo, 327 'repo_dir_name': repo.name, 328 'zipped': zipped, 329 'dir_entries': dir_entries, 330 'commit_id': commit_id, 331 'basedir': basedir, 332 'path': path, 333 'referer': referer, 334 }) 335 336def render_dir_recycle_root(request, repo_id, dir_path, referer): 337 repo = get_repo(repo_id) 338 if not repo: 339 raise Http404 340 341 return render(request, 'repo_dir_recycle_view.html', { 342 'show_recycle_root': True, 343 'repo': repo, 344 'repo_dir_name': os.path.basename(dir_path.rstrip('/')), 345 'dir_path': dir_path, 346 'referer': referer, 347 }) 348 349def render_dir_recycle_dir(request, repo_id, commit_id, dir_path, referer): 350 basedir = request.GET.get('base', '') 351 path = request.GET.get('p', '') 352 if not basedir or not path: 353 return render_dir_recycle_root(request, repo_id, dir_path) 354 355 if basedir[0] != '/': 356 basedir = '/' + basedir 357 if path[-1] != '/': 358 path += '/' 359 360 repo = get_repo(repo_id) 361 if not repo: 362 raise Http404 363 364 try : 365 commit = seafserv_threaded_rpc.get_commit(repo.id, repo.version, commit_id) 366 except SearpcError as e: 367 logger.error(e) 368 referer = request.META.get('HTTP_REFERER', None) 369 next_page = settings.SITE_ROOT if referer is None else referer 370 return HttpResponseRedirect(next_page) 371 372 if not commit: 373 raise Http404 374 375 zipped = gen_path_link(path, '') 376 dir_entries = seafile_api.list_dir_by_commit_and_path(commit.repo_id, 377 commit.id, basedir+path, 378 -1, -1) 379 for dirent in dir_entries: 380 if stat.S_ISDIR(dirent.mode): 381 dirent.is_dir = True 382 else: 383 dirent.is_dir = False 384 385 return render(request, 'repo_dir_recycle_view.html', { 386 'show_recycle_root': False, 387 'repo': repo, 388 'repo_dir_name': os.path.basename(dir_path.rstrip('/')), 389 'zipped': zipped, 390 'dir_entries': dir_entries, 391 'commit_id': commit_id, 392 'basedir': basedir, 393 'path': path, 394 'dir_path': dir_path, 395 'referer': referer, 396 }) 397 398@login_required 399def repo_recycle_view(request, repo_id): 400 if not seafile_api.get_dir_id_by_path(repo_id, '/') or \ 401 check_folder_permission(request, repo_id, '/') != 'rw': 402 return render_permission_error(request, _('Unable to view recycle page')) 403 404 commit_id = request.GET.get('commit_id', '') 405 referer = request.GET.get('referer', '') # for back to 'dir view' page 406 if not commit_id: 407 return render_recycle_root(request, repo_id, referer) 408 else: 409 return render_recycle_dir(request, repo_id, commit_id, referer) 410 411@login_required 412def dir_recycle_view(request, repo_id): 413 dir_path = request.GET.get('dir_path', '') 414 415 if not seafile_api.get_dir_id_by_path(repo_id, dir_path) or \ 416 check_folder_permission(request, repo_id, dir_path) != 'rw': 417 return render_permission_error(request, _('Unable to view recycle page')) 418 419 commit_id = request.GET.get('commit_id', '') 420 referer = request.GET.get('referer', '') # for back to 'dir view' page 421 if not commit_id: 422 return render_dir_recycle_root(request, repo_id, dir_path, referer) 423 else: 424 return render_dir_recycle_dir(request, repo_id, commit_id, dir_path, referer) 425 426@login_required 427def repo_folder_trash(request, repo_id): 428 path = request.GET.get('path', '/') 429 430 if not seafile_api.get_dir_id_by_path(repo_id, path) or \ 431 check_folder_permission(request, repo_id, path) != 'rw': 432 return render_permission_error(request, _('Unable to view recycle page')) 433 434 repo = get_repo(repo_id) 435 if not repo: 436 raise Http404 437 438 if path == '/': 439 name = repo.name 440 else: 441 name = os.path.basename(path.rstrip('/')) 442 443 return render(request, 'repo_folder_trash_react.html', { 444 'repo': repo, 445 'repo_folder_name': name, 446 'path': path, 447 'enable_clean': config.ENABLE_USER_CLEAN_TRASH, 448 }) 449 450def can_access_repo_setting(request, repo_id, username): 451 repo = seafile_api.get_repo(repo_id) 452 if not repo: 453 return (False, None) 454 455 # no settings for virtual repo 456 if repo.is_virtual: 457 return (False, None) 458 459 # check permission 460 if is_org_context(request): 461 repo_owner = seafile_api.get_org_repo_owner(repo_id) 462 else: 463 repo_owner = seafile_api.get_repo_owner(repo_id) 464 is_owner = True if username == repo_owner else False 465 if not is_owner: 466 return (False, None) 467 468 return (True, repo) 469 470@login_required 471def repo_history(request, repo_id): 472 """ 473 List library modification histories. 474 """ 475 user_perm = check_folder_permission(request, repo_id, '/') 476 if not user_perm: 477 return render_permission_error(request, _('Unable to view library modification')) 478 479 repo = get_repo(repo_id) 480 if not repo: 481 raise Http404 482 483 username = request.user.username 484 try: 485 server_crypto = UserOptions.objects.is_server_crypto(username) 486 except CryptoOptionNotSetError: 487 # Assume server_crypto is ``False`` if this option is not set. 488 server_crypto = False 489 490 password_set = False 491 if repo.props.encrypted and \ 492 (repo.enc_version == 1 or (repo.enc_version == 2 and server_crypto)): 493 try: 494 ret = seafile_api.is_password_set(repo_id, username) 495 if ret == 1: 496 password_set = True 497 except SearpcError as e: 498 return render_error(request, e.msg) 499 500 if not password_set: 501 reverse_url = reverse('lib_view', args=[repo_id, repo.name, '']) 502 return HttpResponseRedirect(reverse_url) 503 504 try: 505 current_page = int(request.GET.get('page', '1')) 506 except ValueError: 507 current_page = 1 508 509 per_page = 100 510 commits_all = get_commits(repo_id, per_page * (current_page -1), 511 per_page + 1) 512 commits = commits_all[:per_page] 513 for c in commits: 514 c.show = False if new_merge_with_no_conflict(c) else True 515 516 show_label = False 517 if ENABLE_REPO_SNAPSHOT_LABEL: 518 show_label = True 519 snapshot_labels = RevisionTags.objects.filter(repo_id=repo_id) 520 for c in commits: 521 if c.show: 522 c.labels = [] 523 for label in snapshot_labels: 524 if label.revision_id == c.id: 525 c.labels.append(label.tag.name) 526 527 if len(commits_all) == per_page + 1: 528 page_next = True 529 else: 530 page_next = False 531 532 # for 'go back' 533 referer = request.GET.get('referer', '') 534 535 #template = 'repo_history.html' 536 template = 'repo_history_react.html' 537 538 return render(request, template, { 539 "repo": repo, 540 "commits": commits, 541 'current_page': current_page, 542 'prev_page': current_page-1, 543 'next_page': current_page+1, 544 'page_next': page_next, 545 'user_perm': user_perm, 546 'show_label': show_label, 547 'referer': referer, 548 }) 549 550@login_required 551@require_POST 552def repo_revert_history(request, repo_id): 553 554 next_page = request.META.get('HTTP_REFERER', None) 555 if not next_page: 556 next_page = settings.SITE_ROOT 557 558 repo = get_repo(repo_id) 559 if not repo: 560 messages.error(request, _("Library does not exist")) 561 return HttpResponseRedirect(next_page) 562 563 # perm check 564 perm = check_folder_permission(request, repo_id, '/') 565 username = request.user.username 566 repo_owner = seafile_api.get_repo_owner(repo.id) 567 568 if perm is None or repo_owner != username: 569 messages.error(request, _("Permission denied")) 570 return HttpResponseRedirect(next_page) 571 572 try: 573 server_crypto = UserOptions.objects.is_server_crypto(username) 574 except CryptoOptionNotSetError: 575 # Assume server_crypto is ``False`` if this option is not set. 576 server_crypto = False 577 578 password_set = False 579 if repo.props.encrypted and \ 580 (repo.enc_version == 1 or (repo.enc_version == 2 and server_crypto)): 581 try: 582 ret = seafile_api.is_password_set(repo_id, username) 583 if ret == 1: 584 password_set = True 585 except SearpcError as e: 586 return render_error(request, e.msg) 587 588 if not password_set: 589 reverse_url = reverse('lib_view', args=[repo_id, repo.name, '']) 590 return HttpResponseRedirect(reverse_url) 591 592 commit_id = request.GET.get('commit_id', '') 593 if not commit_id: 594 return render_error(request, _('Please specify history ID')) 595 596 try: 597 seafserv_threaded_rpc.revert_on_server(repo_id, commit_id, request.user.username) 598 messages.success(request, _('Successfully restored the library.')) 599 except SearpcError as e: 600 if e.msg == 'Bad arguments': 601 return render_error(request, _('Invalid arguments.')) 602 elif e.msg == 'No such repo': 603 return render_error(request, _('Library does not exist')) 604 elif e.msg == "Commit doesn't exist": 605 return render_error(request, _('History you specified does not exist')) 606 else: 607 return render_error(request, _('Unknown error')) 608 609 return HttpResponseRedirect(next_page) 610 611def fpath_to_link(repo_id, path, is_dir=False): 612 """Translate file path of a repo to its view link""" 613 if is_dir: 614 repo = seafile_api.get_repo(repo_id) 615 href = reverse('lib_view', args=[repo_id, repo.name, path.strip('/')]) 616 else: 617 if not path.startswith('/'): 618 path = '/' + path 619 href = reverse("view_lib_file", args=[repo_id, path]) 620 621 return '<a href="%s">%s</a>' % (href, escape(path)) 622 623def get_diff(repo_id, arg1, arg2): 624 lists = {'new': [], 'removed': [], 'renamed': [], 'modified': [], 625 'newdir': [], 'deldir': []} 626 627 diff_result = seafserv_threaded_rpc.get_diff(repo_id, arg1, arg2) 628 if not diff_result: 629 return lists 630 631 for d in diff_result: 632 if d.status == "add": 633 lists['new'].append(fpath_to_link(repo_id, d.name)) 634 elif d.status == "del": 635 lists['removed'].append(escape(d.name)) 636 elif d.status == "mov": 637 lists['renamed'].append(escape(d.name) + " ==> " + fpath_to_link(repo_id, d.new_name)) 638 elif d.status == "mod": 639 lists['modified'].append(fpath_to_link(repo_id, d.name)) 640 elif d.status == "newdir": 641 lists['newdir'].append(fpath_to_link(repo_id, d.name, is_dir=True)) 642 elif d.status == "deldir": 643 lists['deldir'].append(escape(d.name)) 644 645 return lists 646 647def create_default_library(request): 648 """Create a default library for user. 649 650 Arguments: 651 - `username`: 652 """ 653 username = request.user.username 654 655 # Disable user guide no matter user permission error or creation error, 656 # so that the guide popup only show once. 657 UserOptions.objects.disable_user_guide(username) 658 659 if not request.user.permissions.can_add_repo(): 660 return 661 662 if is_org_context(request): 663 org_id = request.user.org.org_id 664 default_repo = seafile_api.create_org_repo(name=_("My Library"), 665 desc=_("My Library"), 666 username=username, 667 org_id=org_id) 668 else: 669 default_repo = seafile_api.create_repo(name=_("My Library"), 670 desc=_("My Library"), 671 username=username) 672 sys_repo_id = get_system_default_repo_id() 673 if sys_repo_id is None: 674 return 675 676 try: 677 dirents = seafile_api.list_dir_by_path(sys_repo_id, '/') 678 for e in dirents: 679 obj_name = e.obj_name 680 seafile_api.copy_file(sys_repo_id, '/', obj_name, 681 default_repo, '/', obj_name, username, 0) 682 except SearpcError as e: 683 logger.error(e) 684 return 685 686 UserOptions.objects.set_default_repo(username, default_repo) 687 return default_repo 688 689def get_owned_repo_list(request): 690 """List owned repos. 691 """ 692 username = request.user.username 693 if is_org_context(request): 694 org_id = request.user.org.org_id 695 return seafile_api.get_org_owned_repo_list(org_id, username) 696 else: 697 return seafile_api.get_owned_repo_list(username) 698 699@login_required 700def repo_set_access_property(request, repo_id): 701 ap = request.GET.get('ap', '') 702 seafserv_threaded_rpc.repo_set_access_property(repo_id, ap) 703 repo = seafile_api.get_repo(repo_id) 704 reverse_url = reverse('lib_view', args=[repo_id, repo.name, '']) 705 706 return HttpResponseRedirect(reverse_url) 707 708@login_required 709def validate_filename(request): 710 repo_id = request.GET.get('repo_id') 711 filename = request.GET.get('filename') 712 713 if not (repo_id and filename): 714 return render_error(request) 715 716 result = {'ret':'yes'} 717 718 try: 719 ret = is_valid_dirent_name(filename) 720 except SearpcError: 721 result['ret'] = 'error' 722 else: 723 result['ret'] = 'yes' if ret == 1 else 'no' 724 725 content_type = 'application/json; charset=utf-8' 726 return HttpResponse(json.dumps(result), content_type=content_type) 727 728@login_required 729def file_revisions(request, repo_id): 730 """List file revisions in file version history page. 731 """ 732 repo = get_repo(repo_id) 733 if not repo: 734 error_msg = _("Library does not exist") 735 return render_error(request, error_msg) 736 737 # perm check 738 if not check_folder_permission(request, repo_id, '/'): 739 error_msg = _("Permission denied.") 740 return render_error(request, error_msg) 741 742 path = request.GET.get('p', '/') 743 if not path: 744 return render_error(request) 745 746 if path[-1] == '/': 747 path = path[:-1] 748 749 u_filename = os.path.basename(path) 750 751 filetype, file_ext = [x.lower() for x in get_file_type_and_ext(u_filename)] 752 if filetype == 'text' or filetype == 'markdown': 753 can_compare = True 754 else: 755 can_compare = False 756 757 # Check whether user is repo owner 758 if validate_owner(request, repo_id): 759 is_owner = True 760 else: 761 is_owner = False 762 763 zipped = gen_path_link(path, repo.name) 764 765 can_revert_file = True 766 username = request.user.username 767 768 try: 769 is_locked, locked_by_me = check_file_lock(repo_id, path, username) 770 except Exception as e: 771 logger.error(e) 772 is_locked, locked_by_me = False, False 773 774 repo_perm = seafile_api.check_permission_by_path(repo_id, path, username) 775 if repo_perm != 'rw' or (is_locked and not locked_by_me): 776 can_revert_file = False 777 778 # Whether use new file history API which read file history from db. 779 suffix_list = seafevents_api.get_file_history_suffix() 780 if suffix_list and isinstance(suffix_list, list): 781 suffix_list = [x.lower() for x in suffix_list] 782 else: 783 suffix_list = [] 784 785 use_new_api = True if file_ext in suffix_list else False 786 use_new_style = True if use_new_api and filetype == 'markdown' else False 787 788 if use_new_style: 789 return render(request, 'file_revisions_new.html', { 790 'repo': repo, 791 'path': path, 792 'u_filename': u_filename, 793 'zipped': zipped, 794 'is_owner': is_owner, 795 'can_compare': can_compare, 796 'can_revert_file': can_revert_file, 797 }) 798 799 return render(request, 'file_revisions_old.html', { 800 'repo': repo, 801 'path': path, 802 'u_filename': u_filename, 803 'zipped': zipped, 804 'is_owner': is_owner, 805 'can_compare': can_compare, 806 'can_revert_file': can_revert_file, 807 'can_download_file': parse_repo_perm(repo_perm).can_download, 808 'use_new_api': use_new_api, 809 }) 810 811 812def demo(request): 813 """ 814 Login as demo account. 815 """ 816 from django.conf import settings as dj_settings 817 if not dj_settings.ENABLE_DEMO_USER: 818 raise Http404 819 820 try: 821 user = User.objects.get(email=settings.CLOUD_DEMO_USER) 822 except User.DoesNotExist: 823 logger.warn('CLOUD_DEMO_USER: %s does not exist.' % settings.CLOUD_DEMO_USER) 824 raise Http404 825 826 for backend in get_backends(): 827 user.backend = "%s.%s" % (backend.__module__, backend.__class__.__name__) 828 829 auth_login(request, user) 830 831 redirect_to = settings.SITE_ROOT 832 return HttpResponseRedirect(redirect_to) 833 834def list_inner_pub_repos(request): 835 """List inner pub repos. 836 """ 837 username = request.user.username 838 if is_org_context(request): 839 org_id = request.user.org.org_id 840 return seafile_api.list_org_inner_pub_repos(org_id) 841 842 if not request.cloud_mode: 843 return seafile_api.get_inner_pub_repo_list() 844 845 return [] 846 847def i18n(request): 848 """ 849 Set client language preference, lasts for one month 850 851 """ 852 from django.conf import settings 853 next_page = request.META.get('HTTP_REFERER', settings.SITE_ROOT) 854 855 lang = request.GET.get('lang', settings.LANGUAGE_CODE) 856 if lang not in [e[0] for e in settings.LANGUAGES]: 857 # language code is not supported, use default. 858 lang = settings.LANGUAGE_CODE 859 860 # set language code to user profile if user is logged in 861 if not request.user.is_anonymous: 862 p = Profile.objects.get_profile_by_user(request.user.username) 863 if p is not None: 864 # update exist record 865 p.set_lang_code(lang) 866 else: 867 # add new record 868 Profile.objects.add_or_update(request.user.username, '', '', lang) 869 870 # set language code to client 871 res = HttpResponseRedirect(next_page) 872 res.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang, max_age=30*24*60*60) 873 return res 874 875@login_required 876def repo_download_dir(request, repo_id): 877 repo = get_repo(repo_id) 878 if not repo: 879 return render_error(request, _('Library does not exist')) 880 881 path = request.GET.get('p', '/') 882 if path[-1] != '/': # Normalize dir path 883 path += '/' 884 885 if not seafile_api.get_dir_id_by_path(repo.id, path): 886 return render_error(request, _('"%s" does not exist.') % path) 887 888 if len(path) > 1: 889 dirname = os.path.basename(path.rstrip('/')) # Here use `rstrip` to cut out last '/' in path 890 else: 891 dirname = repo.name 892 893 allow_download = parse_repo_perm(check_folder_permission( 894 request, repo_id, '/')).can_download 895 896 if allow_download: 897 898 dir_id = seafile_api.get_dir_id_by_commit_and_path(repo.id, 899 repo.head_cmmt_id, path) 900 901 is_windows = 0 902 if is_windows_operating_system(request): 903 is_windows = 1 904 905 fake_obj_id = { 906 'obj_id': dir_id, 907 'dir_name': dirname, 908 'is_windows': is_windows 909 } 910 911 token = seafile_api.get_fileserver_access_token( 912 repo_id, json.dumps(fake_obj_id), 'download-dir', request.user.username) 913 914 if not token: 915 return render_error(request, _('Internal Server Error')) 916 917 else: 918 return render_error(request, _('Unable to download "%s"') % dirname ) 919 920 url = gen_file_get_url(token, dirname) 921 from seahub.views.file import send_file_access_msg 922 send_file_access_msg(request, repo, path, 'web') 923 return redirect(url) 924 925def group_events_data(events): 926 """ 927 Group events according to the date. 928 """ 929 event_groups = [] 930 for e in events: 931 e.time = utc_to_local(e.timestamp) 932 e.date = e.time.strftime("%Y-%m-%d") 933 if e.etype == 'repo-update': 934 e.author = e.commit.creator_name 935 elif e.etype == 'repo-create': 936 e.author = e.creator 937 else: 938 e.author = e.repo_owner 939 940 if len(event_groups) == 0 or \ 941 len(event_groups) > 0 and e.date != event_groups[-1]['date']: 942 event_group = {} 943 event_group['date'] = e.date 944 event_group['events'] = [e] 945 event_groups.append(event_group) 946 else: 947 event_groups[-1]['events'].append(e) 948 949 return event_groups 950 951@login_required 952def convert_cmmt_desc_link(request): 953 """Return user to file/directory page based on the changes in commit. 954 """ 955 repo_id = request.GET.get('repo_id') 956 cmmt_id = request.GET.get('cmmt_id') 957 name = request.GET.get('nm') 958 959 repo = get_repo(repo_id) 960 if not repo: 961 raise Http404 962 963 # perm check 964 if check_folder_permission(request, repo_id, '/') is None: 965 raise Http404 966 967 diff_result = seafserv_threaded_rpc.get_diff(repo_id, '', cmmt_id) 968 if not diff_result: 969 raise Http404 970 971 for d in diff_result: 972 if name not in d.name: 973 # skip to next diff_result if file/folder user clicked does not 974 # match the diff_result 975 continue 976 977 if d.status == 'add' or d.status == 'mod': # Add or modify file 978 return HttpResponseRedirect( 979 reverse('view_lib_file', args=[repo_id, '/' + d.name])) 980 elif d.status == 'mov': # Move or Rename non-empty file/folder 981 if '/' in d.new_name: 982 new_dir_name = d.new_name.split('/')[0] 983 reverse_url = reverse('lib_view', args=[repo_id, repo.name, new_dir_name]) 984 return HttpResponseRedirect(reverse_url) 985 else: 986 return HttpResponseRedirect( 987 reverse('view_lib_file', args=[repo_id, '/' + d.new_name])) 988 elif d.status == 'newdir': 989 reverse_url = reverse('lib_view', args=[repo_id, repo.name, d.name.strip('/')]) 990 return HttpResponseRedirect(reverse_url) 991 else: 992 continue 993 994 status_list = [d.status for d in diff_result] 995 # Rename empty file/folder 996 if len(status_list) == 2: 997 if 'add' in status_list and 'del' in status_list: 998 for d in diff_result: 999 if d.status != 'add': 1000 continue 1001 1002 return HttpResponseRedirect( 1003 reverse('view_lib_file', args=[repo_id, '/' + d.name])) 1004 1005 if 'newdir' in status_list and 'deldir' in status_list: 1006 for d in diff_result: 1007 if d.status != 'newdir': 1008 continue 1009 1010 return HttpResponseRedirect( 1011 reverse('view_common_lib_dir', args=[repo_id, d.name])) 1012 1013 # Rename folder with empty files 1014 if len(status_list) > 2: 1015 if 'deldir' in status_list and 'add' in status_list: 1016 for d in diff_result: 1017 if d.status != 'add': 1018 continue 1019 1020 new_dir = d.name.split('/')[0] 1021 return HttpResponseRedirect( 1022 reverse('view_common_lib_dir', args=[repo_id, new_dir])) 1023 1024 raise Http404 1025 1026storage = get_avatar_file_storage() 1027def latest_entry(request, filename): 1028 try: 1029 return storage.modified_time(filename) 1030 except Exception as e: 1031 logger.error(e) 1032 return None 1033 1034@condition(last_modified_func=latest_entry) 1035def image_view(request, filename): 1036 if AVATAR_FILE_STORAGE is None: 1037 raise Http404 1038 1039 # read file from cache, if hit 1040 filename_md5 = hashlib.md5(filename.encode('utf-8')).hexdigest() 1041 cache_key = 'image_view__%s' % filename_md5 1042 file_content = cache.get(cache_key) 1043 if file_content is None: 1044 # otherwise, read file from database and update cache 1045 image_file = storage.open(filename, 'rb') 1046 if not image_file: 1047 raise Http404 1048 file_content = image_file.read() 1049 cache.set(cache_key, file_content, 365 * 24 * 60 * 60) 1050 1051 # Prepare response 1052 content_type, content_encoding = mimetypes.guess_type(filename) 1053 response = HttpResponse(content=file_content, content_type=content_type) 1054 response['Content-Disposition'] = 'inline; filename=%s' % filename 1055 if content_encoding: 1056 response['Content-Encoding'] = content_encoding 1057 return response 1058 1059def custom_css_view(request): 1060 file_content = config.CUSTOM_CSS 1061 response = HttpResponse(content=file_content, content_type='text/css') 1062 return response 1063 1064def underscore_template(request, template): 1065 """Serve underscore template through Django, mainly for I18n. 1066 1067 Arguments: 1068 - `request`: 1069 - `template`: 1070 """ 1071 if not template.startswith('js'): # light security check 1072 raise Http404 1073 1074 return render(request, template, {}) 1075 1076def client_token_login(request): 1077 """Login from desktop client with a generated token. 1078 """ 1079 tokenstr = request.GET.get('token', '') 1080 user = None 1081 if len(tokenstr) == 32: 1082 try: 1083 username = ClientLoginToken.objects.get_username(tokenstr) 1084 except ClientLoginToken.DoesNotExist: 1085 pass 1086 else: 1087 try: 1088 user = User.objects.get(email=username) 1089 for backend in get_backends(): 1090 user.backend = "%s.%s" % (backend.__module__, backend.__class__.__name__) 1091 except User.DoesNotExist: 1092 pass 1093 1094 if user: 1095 if request.user.is_authenticated and request.user.username == user.username: 1096 pass 1097 else: 1098 request.client_token_login = True 1099 auth_login(request, user) 1100 1101 return HttpResponseRedirect(request.GET.get("next", reverse('libraries'))) 1102 1103def choose_register(request): 1104 """ 1105 Choose register 1106 """ 1107 login_bg_image_path = get_login_bg_image_path() 1108 1109 return render(request, 'choose_register.html', { 1110 'enable_weixin': ENABLE_WEIXIN, 1111 'login_bg_image_path': login_bg_image_path 1112 }) 1113 1114 1115@login_required 1116def react_fake_view(request, **kwargs): 1117 1118 username = request.user.username 1119 1120 if resolve(request.path).url_name == 'lib_view': 1121 1122 repo_id = kwargs.get('repo_id', '') 1123 path = kwargs.get('path', '') 1124 1125 if repo_id and path and \ 1126 not check_folder_permission(request, repo_id, path): 1127 1128 converted_repo_path = seafile_api.convert_repo_path(repo_id, path, username) 1129 if not converted_repo_path: 1130 error_msg = 'Permission denied.' 1131 return render_error(request, error_msg) 1132 1133 repo_path_dict = json.loads(converted_repo_path) 1134 1135 converted_repo_id = repo_path_dict['repo_id'] 1136 converted_repo = seafile_api.get_repo(converted_repo_id) 1137 if not converted_repo: 1138 error_msg = 'Library %s not found.' % converted_repo_id 1139 return render_error(request, error_msg) 1140 1141 converted_path = repo_path_dict['path'] 1142 if not seafile_api.get_dirent_by_path(converted_repo_id, converted_path): 1143 error_msg = 'Dirent %s not found.' % converted_path 1144 return render_error(request, error_msg) 1145 1146 if not check_folder_permission(request, converted_repo_id, converted_path): 1147 error_msg = 'Permission denied.' 1148 return render_error(request, error_msg) 1149 1150 next_url = reverse('lib_view', args=[converted_repo_id, 1151 converted_repo.repo_name, 1152 converted_path.strip('/')]) 1153 return HttpResponseRedirect(next_url) 1154 1155 guide_enabled = UserOptions.objects.is_user_guide_enabled(username) 1156 if guide_enabled: 1157 create_default_library(request) 1158 1159 try: 1160 expire_days = seafile_api.get_server_config_int('library_trash', 'expire_days') 1161 except Exception as e: 1162 logger.error(e) 1163 expire_days = -1 1164 1165 folder_perm_enabled = True if is_pro_version() and ENABLE_FOLDER_PERM else False 1166 1167 try: 1168 max_upload_file_size = seafile_api.get_server_config_int('fileserver', 'max_upload_size') 1169 except Exception as e: 1170 logger.error(e) 1171 max_upload_file_size = -1 1172 1173 return render(request, "react_app.html", { 1174 "onlyoffice_desktop_editors_portal_login": ONLYOFFICE_DESKTOP_EDITORS_PORTAL_LOGIN, 1175 "guide_enabled": guide_enabled, 1176 'trash_repos_expire_days': expire_days if expire_days > 0 else 30, 1177 'dtable_web_server': DTABLE_WEB_SERVER, 1178 'max_upload_file_size': max_upload_file_size, 1179 'seafile_collab_server': SEAFILE_COLLAB_SERVER, 1180 'storages': get_library_storages(request), 1181 'library_templates': list(LIBRARY_TEMPLATES.keys()), 1182 'enable_repo_snapshot_label': settings.ENABLE_REPO_SNAPSHOT_LABEL, 1183 'resumable_upload_file_block_size': settings.RESUMABLE_UPLOAD_FILE_BLOCK_SIZE, 1184 'max_number_of_files_for_fileupload': settings.MAX_NUMBER_OF_FILES_FOR_FILEUPLOAD, 1185 'share_link_expire_days_default': SHARE_LINK_EXPIRE_DAYS_DEFAULT, 1186 'share_link_expire_days_min': SHARE_LINK_EXPIRE_DAYS_MIN, 1187 'share_link_expire_days_max': SHARE_LINK_EXPIRE_DAYS_MAX, 1188 'upload_link_expire_days_default': UPLOAD_LINK_EXPIRE_DAYS_DEFAULT, 1189 'upload_link_expire_days_min': UPLOAD_LINK_EXPIRE_DAYS_MIN, 1190 'upload_link_expire_days_max': UPLOAD_LINK_EXPIRE_DAYS_MAX, 1191 'enable_encrypted_library': config.ENABLE_ENCRYPTED_LIBRARY, 1192 'enable_repo_history_setting': config.ENABLE_REPO_HISTORY_SETTING, 1193 'enable_reset_encrypted_repo_password': ENABLE_RESET_ENCRYPTED_REPO_PASSWORD, 1194 'enableFileComment': settings.ENABLE_FILE_COMMENT, 1195 'is_email_configured': IS_EMAIL_CONFIGURED, 1196 'can_add_public_repo': request.user.permissions.can_add_public_repo(), 1197 'folder_perm_enabled': folder_perm_enabled, 1198 'file_audit_enabled': FILE_AUDIT_ENABLED, 1199 'custom_nav_items': json.dumps(CUSTOM_NAV_ITEMS), 1200 'enable_show_contact_email_when_search_user': settings.ENABLE_SHOW_CONTACT_EMAIL_WHEN_SEARCH_USER, 1201 'additional_share_dialog_note': ADDITIONAL_SHARE_DIALOG_NOTE, 1202 'additional_app_bottom_links': ADDITIONAL_APP_BOTTOM_LINKS, 1203 'additional_about_dialog_links': ADDITIONAL_ABOUT_DIALOG_LINKS, 1204 'enable_ocm': ENABLE_OCM, 1205 'ocm_remote_servers': OCM_REMOTE_SERVERS, 1206 'enable_share_to_department': settings.ENABLE_SHARE_TO_DEPARTMENT, 1207 'group_import_members_extra_msg': GROUP_IMPORT_MEMBERS_EXTRA_MSG, 1208 }) 1209