def sitemap(request): section = request.GET.get('section') # no section means the index page app = request.GET.get('app_name') page = request.GET.get('p', 1) if 'debug' in request.GET and settings.SITEMAP_DEBUG_AVAILABLE: try: sitemaps = get_sitemaps() if not section: if page != 1: raise EmptyPage content = render_index_xml(sitemaps) else: sitemap_object = sitemaps.get((section, amo.APPS.get(app))) if not sitemap_object: raise InvalidSection content = sitemap_object.render_xml(app, page) except EmptyPage: raise Http404('Page %s empty' % page) except PageNotAnInteger: raise Http404('No page "%s"' % page) except InvalidSection: raise Http404('No sitemap available for section: %r' % section) response = HttpResponse(content, content_type='application/xml') else: path = get_sitemap_path(section, app, page) response = HttpResponseXSendFile(request, path, content_type='application/xml') patch_cache_control(response, max_age=60 * 60) return response
def download_file(request, file_id, type=None, file_=None, addon=None): """ Download given file. `addon` and `file_` parameters can be passed to avoid the database query. If the file is disabled or belongs to an unlisted version, requires an add-on developer or appropriate reviewer for the channel. If the file is deleted or belongs to a deleted version or add-on, reviewers can still access but developers can't. """ def is_appropriate_reviewer(addon, channel): return (acl.is_reviewer(request, addon) if channel == amo.RELEASE_CHANNEL_LISTED else acl.check_unlisted_addons_reviewer(request)) if not file_: file_ = get_object_or_404(File.objects, pk=file_id) if not addon: # Include deleted add-ons in the queryset, we'll check for that below. addon = get_object_or_404(Addon.unfiltered, pk=file_.version.addon_id) version = file_.version channel = version.channel if version.deleted or addon.is_deleted: # Only the appropriate reviewer can see deleted things. use_cdn = False has_permission = is_appropriate_reviewer(addon, channel) elif (addon.is_disabled or file_.status == amo.STATUS_DISABLED or channel == amo.RELEASE_CHANNEL_UNLISTED): # Only the appropriate reviewer or developers of the add-on can see # disabled or unlisted things. use_cdn = False has_permission = (is_appropriate_reviewer(addon, channel) or acl.check_addon_ownership( request, addon, dev=True, ignore_disabled=True)) else: # Everyone can see public things, and we can use the CDN in that case. use_cdn = True has_permission = True if not has_permission: log.info('download file {file_id}: addon/version/file not public and ' 'user {user_id} does not have relevant permissions.'.format( file_id=file_id, user_id=request.user.pk)) raise http.Http404() # Not owner or admin. if use_cdn: attachment = bool(type == 'attachment') loc = urlparams(file_.get_file_cdn_url(attachment=attachment), filehash=file_.hash) response = http.HttpResponseRedirect(loc) response['X-Target-Digest'] = file_.hash else: response = HttpResponseXSendFile( request, file_.current_file_path, content_type='application/x-xpinstall') response['Access-Control-Allow-Origin'] = '*' return response
def download_file(request, file_id, type=None, file_=None, addon=None): def is_appropriate_reviewer(addon, channel): return (acl.is_reviewer(request, addon) if channel == amo.RELEASE_CHANNEL_LISTED else acl.check_unlisted_addons_reviewer(request)) if not file_: file_ = get_object_or_404(File.objects, pk=file_id) if not addon: addon = get_object_or_404(Addon.objects, pk=file_.version.addon_id) channel = file_.version.channel if addon.is_disabled or file_.status == amo.STATUS_DISABLED: if (is_appropriate_reviewer(addon, channel) or acl.check_addon_ownership( request, addon, dev=True, ignore_disabled=True)): return HttpResponseXSendFile( request, file_.guarded_file_path, content_type='application/x-xpinstall') else: log.info(u'download file {file_id}: addon/file disabled and ' u'user {user_id} is not an owner or reviewer.'.format( file_id=file_id, user_id=request.user.pk)) raise http.Http404() # Not owner or admin. if channel == amo.RELEASE_CHANNEL_UNLISTED: if (acl.check_unlisted_addons_reviewer(request) or acl.check_addon_ownership( request, addon, dev=True, ignore_disabled=True)): return HttpResponseXSendFile( request, file_.file_path, content_type='application/x-xpinstall') else: log.info(u'download file {file_id}: version is unlisted and ' u'user {user_id} is not an owner or reviewer.'.format( file_id=file_id, user_id=request.user.pk)) raise http.Http404() # Not owner or admin. attachment = bool(type == 'attachment') loc = urlparams(file_.get_file_cdn_url(attachment=attachment), filehash=file_.hash) response = http.HttpResponseRedirect(loc) response['X-Target-Digest'] = file_.hash return response
def download_source(request, version_id): version = get_object_or_404(Version.objects, pk=version_id) # General case: version is listed. if version.channel == amo.RELEASE_CHANNEL_LISTED: if not (version.source and (acl.check_addon_ownership( request, version.addon, dev=True, ignore_disabled=True))): raise http.Http404() else: if not owner_or_unlisted_reviewer(request, version.addon): raise http.Http404 # Not listed, not owner or unlisted reviewer. res = HttpResponseXSendFile(request, version.source.path) path = version.source.path if not isinstance(path, str): path = path.decode('utf8') name = os.path.basename(path.replace(u'"', u'')) disposition = u'attachment; filename="{0}"'.format(name).encode('utf8') res['Content-Disposition'] = disposition return res
def download_source(request, version_id): """ Download source code for a given version_id. Requires developer of the add-on or admin reviewer permission. If the version or add-on is deleted, developers can't access. If the version source code wasn't provided, but the user had the right permissions, a 404 is raised. """ # Include deleted versions in the queryset, we'll check for that below. version = get_object_or_404(Version.unfiltered, pk=version_id) addon = version.addon # Channel doesn't matter, source code is only available to admin reviewers # or developers of the add-on. If the add-on, version or file is deleted or # disabled, then only admins can access. has_permission = acl.action_allowed(request, amo.permissions.REVIEWS_ADMIN) if ( addon.status != amo.STATUS_DISABLED and not version.files.filter(status=amo.STATUS_DISABLED).exists() and not version.deleted and not addon.is_deleted ): # Don't rely on 'admin' parameter for check_addon_ownership(), it # doesn't check the permission we want to check. has_permission = has_permission or acl.check_addon_ownership( request, addon, admin=False, dev=True ) if not has_permission: raise http.Http404() response = HttpResponseXSendFile(request, version.source.path) path = version.source.path if not isinstance(path, str): path = path.decode('utf8') name = os.path.basename(path.replace('"', '')) disposition = 'attachment; filename="{0}"'.format(name).encode('utf8') response['Content-Disposition'] = disposition response['Access-Control-Allow-Origin'] = '*' return response
def serve_file_upload(request, uuid): """ This is to serve file uploads using authenticated download URLs. This is currently used by the "scanner" services. """ upload = shortcuts.get_object_or_404(FileUpload, uuid=uuid) access_token = request.GET.get('access_token') if not access_token: log.error('Denying access to %s, no token.', upload.id) raise PermissionDenied if not constant_time_compare(access_token, upload.access_token): log.error('Denying access to %s, token invalid.', upload.id) raise PermissionDenied if not upload.path: log.info('Preventing access to %s, upload path is falsey.' % upload.id) return http.HttpResponseGone('upload path does not exist anymore') return HttpResponseXSendFile( request, upload.path, content_type='application/octet-stream' )
def download_file(request, file_id, type=None, file_=None, addon=None): """ Download given file. `addon` and `file_` parameters can be passed to avoid the database query. If the file is disabled or belongs to an unlisted version, requires an add-on developer or appropriate reviewer for the channel. If the file is deleted or belongs to a deleted version or add-on, reviewers can still access but developers can't. """ def is_appropriate_reviewer(addon, channel): return (acl.is_reviewer(request, addon) if channel == amo.RELEASE_CHANNEL_LISTED else acl.check_unlisted_addons_viewer_or_reviewer(request)) if not file_: file_ = get_object_or_404(File.objects, pk=file_id) if not addon: # Include deleted add-ons in the queryset, we'll check for that below. addon = get_object_or_404(Addon.unfiltered, pk=file_.version.addon_id) version = file_.version channel = version.channel if version.deleted or addon.is_deleted: # Only the appropriate reviewer can see deleted things. use_cdn = False has_permission = is_appropriate_reviewer(addon, channel) elif (addon.is_disabled or file_.status == amo.STATUS_DISABLED or channel == amo.RELEASE_CHANNEL_UNLISTED): # Only the appropriate reviewer or developers of the add-on can see # disabled or unlisted things. use_cdn = False has_permission = is_appropriate_reviewer( addon, channel) or acl.check_addon_ownership( request, addon, allow_developer=True, allow_mozilla_disabled_addon=True, allow_site_permission=True, ) else: # Everyone can see public things, and we can use the CDN in that case. use_cdn = True has_permission = True if not has_permission: log.debug('download file {file_id}: addon/version/file not public and ' 'user {user_id} does not have relevant permissions.'.format( file_id=file_id, user_id=request.user.pk)) raise http.Http404() # Not owner or admin. attachment = bool(type == 'attachment') if use_cdn: # When serving the file for the general public through the CDN, we need # to obey regional restrictions region_code = request.META.get('HTTP_X_COUNTRY_CODE', None) if (region_code and AddonRegionalRestrictions.objects.filter( addon=addon, excluded_regions__contains=region_code.upper()).exists()): response = http.HttpResponse(status=451) url = 'https://www.mozilla.org/about/policy/transparency/' response['Link'] = f'<{url}>; rel="blocked-by"' else: # When using the CDN URL, we do a redirect, so we can't set # Content-Disposition: attachment for attachments. To work around # this, if attachment=True, get_file_cdn_url() changes the path to # something we recognize in the nginx config. loc = urlparams(file_.get_file_cdn_url(attachment=attachment), filehash=file_.hash) response = http.HttpResponseRedirect(loc) response['X-Target-Digest'] = file_.hash # Always add a Vary header to deal with caching in different regions. patch_vary_headers(response, ['X-Country-Code']) else: # Here we're returning a X-Accel-Redirect, we can set # Content-Disposition: attachment ourselves in HttpResponseXSendFile: # nginx won't override it if present. response = HttpResponseXSendFile( request, file_.current_file_path, content_type='application/x-xpinstall', attachment=attachment, ) response['Access-Control-Allow-Origin'] = '*' return response
def test_adds_content_disposition_header(self): response = HttpResponseXSendFile(request=None, path='/', attachment=True) assert response.has_header('Content-Disposition') assert response['Content-Disposition'] == 'attachment'
def test_adds_etag_header(self): etag = '123' response = HttpResponseXSendFile(request=None, path='/', etag=etag) assert response.has_header('ETag') assert response['ETag'] == quote_etag(etag)
def test_normalizes_path(self): path = '/some/../path/' response = HttpResponseXSendFile(request=None, path=path) assert response[settings.XSENDFILE_HEADER] == os.path.normpath(path) assert not response.has_header('Content-Disposition')
def download_file_upload(request, uuid): upload = get_object_or_404(FileUpload, uuid=uuid) return HttpResponseXSendFile(request, upload.path, content_type='application/octet-stream')
def download_file(request, file_id, download_type=None, **kwargs): """ Download the file identified by `file_id` parameter. If the file is disabled or belongs to an unlisted version, requires an add-on developer or appropriate reviewer for the channel. If the file is deleted or belongs to a deleted version or add-on, reviewers can still access but developers can't. """ def is_appropriate_reviewer(addon, channel): return ( acl.is_reviewer(request, addon) if channel == amo.RELEASE_CHANNEL_LISTED else acl.check_unlisted_addons_viewer_or_reviewer(request) ) file_ = get_object_or_404(File.objects, pk=file_id) # Include deleted add-ons in the queryset, we'll check for that below. addon = get_object_or_404( Addon.unfiltered.all().no_transforms(), pk=file_.version.addon_id ) version = file_.version channel = version.channel if version.deleted or addon.is_deleted: # Only the appropriate reviewer can see deleted things. has_permission = is_appropriate_reviewer(addon, channel) apply_georestrictions = False elif ( addon.is_disabled or file_.status == amo.STATUS_DISABLED or channel == amo.RELEASE_CHANNEL_UNLISTED ): # Only the appropriate reviewer or developers of the add-on can see # disabled or unlisted things. has_permission = is_appropriate_reviewer( addon, channel ) or acl.check_addon_ownership( request, addon, allow_developer=True, allow_mozilla_disabled_addon=True, allow_site_permission=True, ) apply_georestrictions = False else: # Public case: we're either directly downloading the file or # redirecting, but in any case we have permission in the general sense, # though georestrictions are in effect. has_permission = True apply_georestrictions = True region_code = request.META.get('HTTP_X_COUNTRY_CODE', None) # Whether to set Content-Disposition: attachment header or not, to force # the file to be downloaded rather than installed (used by admin/reviewer # tools). attachment = download_type == 'attachment' if not has_permission: log.debug( 'download file {file_id}: addon/version/file not public and ' 'user {user_id} does not have relevant permissions.'.format( file_id=file_id, user_id=request.user.pk ) ) response = http.HttpResponseNotFound() elif ( apply_georestrictions and region_code and AddonRegionalRestrictions.objects.filter( addon=addon, excluded_regions__contains=region_code.upper() ).exists() ): response = http.HttpResponse(status=451) url = 'https://www.mozilla.org/about/policy/transparency/' response['Link'] = f'<{url}>; rel="blocked-by"' else: # We're returning a X-Accel-Redirect, we can set # Content-Disposition: attachment ourselves in HttpResponseXSendFile: # nginx won't override it if present. response = HttpResponseXSendFile( request, file_.current_file_path, content_type='application/x-xpinstall', attachment=attachment, ) # Always add a few headers to the response (even errors). patch_cache_control(response, max_age=60 * 60 * 24) patch_vary_headers(response, ['X-Country-Code']) response['Access-Control-Allow-Origin'] = '*' return response
def test_normalizes_path(self): path = '/some/../path/' resp = HttpResponseXSendFile(request=None, path=path) assert resp[settings.XSENDFILE_HEADER] == os.path.normpath(path)