Ejemplo n.º 1
0
    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
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
 def roundtrip(filename):
     return parse_headers(build_header(filename)).filename_unsafe
Ejemplo n.º 5
0
    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
Ejemplo n.º 6
0
    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)
Ejemplo n.º 7
0
def test_roundtrip(name):
    header = build_header(name)
    header = parse_headers(header)
    assert header.filename_unsafe == name