def __init__( self, f: MemoryStoredFile, request: Request, disposition: str = "attachment", cache_max_age: int = 604800, content_type: None = None, content_encoding: None = None, ) -> None: """ :param f: the ``UploadedFile`` file field value. :type f: :class:`depot.io.interfaces.StoredFile` :param request: Current request. :type request: :class:`pyramid.request.Request` :param disposition: :type disposition: :param cache_max_age: The number of seconds that should be used to HTTP cache this response. :param content_type: The content_type of the response. :param content_encoding: The content_encoding of the response. It's generally safe to leave this set to ``None`` if you're serving a binary file. This argument will be ignored if you also leave ``content-type`` as ``None``. """ if f.public_url: raise HTTPMovedPermanently(f.public_url) content_encoding, content_type = self._get_type_and_encoding( content_encoding, content_type, f ) super(StoredFileResponse, self).__init__( conditional_response=True, content_type=content_type, content_encoding=content_encoding, ) app_iter = None if ( request is not None and not get_settings()["kotti.depot_replace_wsgi_file_wrapper"] ): environ = request.environ if "wsgi.file_wrapper" in environ: app_iter = environ["wsgi.file_wrapper"](f, _BLOCK_SIZE) if app_iter is None: app_iter = FileIter(f) self.app_iter = app_iter # assignment of content_length must come after assignment of app_iter self.content_length = f.content_length self.last_modified = f.last_modified if cache_max_age is not None: self.cache_expires = cache_max_age self.cache_control.public = True self.etag = self.generate_etag(f) disposition = rfc6266_parser.build_header( f.filename, disposition=disposition, filename_compat=unidecode(f.filename) ) if isinstance(disposition, bytes): disposition = disposition.decode("iso-8859-1") self.content_disposition = disposition
def video_encodings_download(request, course_key_string): """ Returns a CSV report containing the encoded video URLs for video uploads in the following format: Video ID,Name,Status,Profile1 URL,Profile2 URL aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa,video.mp4,Complete,http://example.com/prof1.mp4,http://example.com/prof2.mp4 """ course = _get_and_validate_course(course_key_string, request.user) if not course: return HttpResponseNotFound() def get_profile_header(profile): """Returns the column header string for the given profile's URLs""" # Translators: This is the header for a CSV file column # containing URLs for video encodings for the named profile # (e.g. desktop, mobile high quality, mobile low quality) return _(u"{profile_name} URL").format(profile_name=profile) profile_whitelist = VideoUploadConfig.get_profile_whitelist() videos, __ = _get_videos(course) videos = list(videos) name_col = _("Name") duration_col = _("Duration") added_col = _("Date Added") video_id_col = _("Video ID") status_col = _("Status") profile_cols = [get_profile_header(profile) for profile in profile_whitelist] def make_csv_dict(video): """ Makes a dictionary suitable for writing CSV output. This involves extracting the required items from the original video dict and converting all keys and values to UTF-8 encoded string objects, because the CSV module doesn't play well with unicode objects. """ # Translators: This is listed as the duration for a video that has not # yet reached the point in its processing by the servers where its # duration is determined. duration_val = str(video["duration"]) if video["duration"] > 0 else _("Pending") ret = dict( [ (name_col, video["client_video_id"]), (duration_col, duration_val), (added_col, video["created"].isoformat()), (video_id_col, video["edx_video_id"]), (status_col, video["status"]), ] + [ (get_profile_header(encoded_video["profile"]), encoded_video["url"]) for encoded_video in video["encoded_videos"] if encoded_video["profile"] in profile_whitelist ] ) return { key.encode("utf-8"): value.encode("utf-8") for key, value in ret.items() } response = HttpResponse(content_type="text/csv") # Translators: This is the suggested filename when downloading the URL # listing for videos uploaded through Studio filename = _("{course}_video_urls").format(course=course.id.course) # See https://tools.ietf.org/html/rfc6266#appendix-D response["Content-Disposition"] = rfc6266_parser.build_header( filename + ".csv", filename_compat="video_urls.csv" ) writer = csv.DictWriter( response, [ col_name.encode("utf-8") for col_name in [name_col, duration_col, added_col, video_id_col, status_col] + profile_cols ], dialect=csv.excel ) writer.writeheader() for video in videos: writer.writerow(make_csv_dict(video)) return response
def video_encodings_download(request, course_key_string): """ Returns a CSV report containing the encoded video URLs for video uploads in the following format: Video ID,Name,Status,Profile1 URL,Profile2 URL aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa,video.mp4,Complete,http://example.com/prof1.mp4,http://example.com/prof2.mp4 """ course = _get_and_validate_course(course_key_string, request.user) if not course: return HttpResponseNotFound() def get_profile_header(profile): """Returns the column header string for the given profile's URLs""" # Translators: This is the header for a CSV file column # containing URLs for video encodings for the named profile # (e.g. desktop, mobile high quality, mobile low quality) return _("{profile_name} URL").format(profile_name=profile) profile_whitelist = VideoUploadConfig.get_profile_whitelist() videos = list(_get_videos(course)) name_col = _("Name") duration_col = _("Duration") added_col = _("Date Added") video_id_col = _("Video ID") status_col = _("Status") profile_cols = [get_profile_header(profile) for profile in profile_whitelist] def make_csv_dict(video): """ Makes a dictionary suitable for writing CSV output. This involves extracting the required items from the original video dict and converting all keys and values to UTF-8 encoded string objects, because the CSV module doesn't play well with unicode objects. """ # Translators: This is listed as the duration for a video that has not # yet reached the point in its processing by the servers where its # duration is determined. duration_val = str(video["duration"]) if video["duration"] > 0 else _("Pending") ret = dict( [ (name_col, video["client_video_id"]), (duration_col, duration_val), (added_col, video["created"].isoformat()), (video_id_col, video["edx_video_id"]), (status_col, video["status"]), ] + [ (get_profile_header(encoded_video["profile"]), encoded_video["url"]) for encoded_video in video["encoded_videos"] if encoded_video["profile"] in profile_whitelist ] ) return { key.encode("utf-8"): value.encode("utf-8") for key, value in ret.items() } response = HttpResponse(content_type="text/csv") # Translators: This is the suggested filename when downloading the URL # listing for videos uploaded through Studio filename = _("{course}_video_urls").format(course=course.id.course) # See https://tools.ietf.org/html/rfc6266#appendix-D response["Content-Disposition"] = rfc6266_parser.build_header( filename + ".csv", filename_compat="video_urls.csv" ) writer = csv.DictWriter( response, [ col_name.encode("utf-8") for col_name in [name_col, duration_col, added_col, video_id_col, status_col] + profile_cols ], dialect=csv.excel ) writer.writeheader() for video in videos: writer.writerow(make_csv_dict(video)) return response
def roundtrip(filename): return parse_headers(build_header(filename)).filename_unsafe
def __init__( self, f: MemoryStoredFile, request: Request, disposition: str = "attachment", cache_max_age: int = 604800, content_type: None = None, content_encoding: None = None, ) -> None: """ :param f: the ``UploadedFile`` file field value. :type f: :class:`depot.io.interfaces.StoredFile` :param request: Current request. :type request: :class:`pyramid.request.Request` :param disposition: :type disposition: :param cache_max_age: The number of seconds that should be used to HTTP cache this response. :param content_type: The content_type of the response. :param content_encoding: The content_encoding of the response. It's generally safe to leave this set to ``None`` if you're serving a binary file. This argument will be ignored if you also leave ``content-type`` as ``None``. """ if f.public_url: raise HTTPMovedPermanently(f.public_url) content_encoding, content_type = self._get_type_and_encoding( content_encoding, content_type, f) super(StoredFileResponse, self).__init__( conditional_response=True, content_type=content_type, content_encoding=content_encoding, ) app_iter = None if (request is not None and not get_settings()["kotti.depot_replace_wsgi_file_wrapper"]): environ = request.environ if "wsgi.file_wrapper" in environ: app_iter = environ["wsgi.file_wrapper"](f, _BLOCK_SIZE) if app_iter is None: app_iter = FileIter(f) self.app_iter = app_iter # assignment of content_length must come after assignment of app_iter self.content_length = f.content_length self.last_modified = f.last_modified if cache_max_age is not None: self.cache_expires = cache_max_age self.cache_control.public = True self.etag = self.generate_etag(f) disposition = rfc6266_parser.build_header(f.filename, disposition=disposition, filename_compat=unidecode( f.filename)) if isinstance(disposition, bytes): disposition = disposition.decode("iso-8859-1") self.content_disposition = disposition
def static_file(self, urlpath, response=None): root = os.path.abspath(self._cmd.root) + os.sep res = HttpResponse() mimetype = None disposition = None if isinstance(self._cmd.name, RePatternType) and self._cmd.replacement: # pylint: disable=protected-access filename = self._cmd.name.sub(self._cmd.replacement, urlpath) else: filename = urlpath filename = os.path.abspath(os.path.join(root, filename.strip('/\\'))) if not filename.startswith(root): raise self.req_error(403, "Access denied.") if not os.path.exists(filename) or not os.path.isfile(filename): raise self.req_error(404, "File does not exist.") if not os.access(filename, os.R_OK): raise self.req_error(403, "You do not have permission to access this file.") if self._cmd.content_type: mimetype = self._cmd.content_type if isinstance(response, HttpResponse): res = response if not mimetype: mimetype = res.get_header('Content-type') disposition = res.get_header('Content-disposition') if not mimetype or mimetype == '__MAGIC__': try: mime = magic.open(magic.MAGIC_MIME_TYPE) mime.load() mimetype = mime.file(filename) except AttributeError: mimetype = magic.from_file(filename, mime = True) if mimetype == 'image/svg': mimetype += '+xml' if mimetype: res.add_header('Content-type', mimetype) else: mimetype = mimetype.lower() if mimetype.startswith('text/') \ and self._cmd.charset \ and mimetype.find('charset') == -1: res.add_header('Content-type', "%s; charset=%s" % (mimetype, self._cmd.charset)) else: res.add_header('Content-type', mimetype) if disposition: attachment = parse_headers(disposition) if attachment.disposition == 'attachment' \ and not attachment.filename_unsafe: res.add_header('Content-disposition', build_header(os.path.basename(filename))) stats = os.stat(filename) res.add_header('Last-Modified', time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(stats.st_mtime))) if_modified = self.headers.get('If-Modified-Since') if if_modified: if_modified = self.parse_date(if_modified.split(';')[0].strip()) if if_modified >= int(stats.st_mtime): return res.set_code(304).set_send_body(False) f = None body = binary_type() if self.command != 'HEAD': with open(filename, 'rb') as f: while True: buf = f.read(BUFFER_SIZE) if not buf: break body += buf if f: f.close() return res.set_code(200).add_data(body)
def test_roundtrip(name): header = build_header(name) header = parse_headers(header) assert header.filename_unsafe == name