@@ -191,11 +191,15 @@
Loading
191 191
        return self._headers[hdrs.CONTENT_TYPE]
192 192
193 193
    def set_content_disposition(
194 -
        self, disptype: str, quote_fields: bool = True, **params: Any
194 +
        self,
195 +
        disptype: str,
196 +
        quote_fields: bool = True,
197 +
        _charset: str = "utf-8",
198 +
        **params: Any,
195 199
    ) -> None:
196 200
        """Sets ``Content-Disposition`` header."""
197 201
        self._headers[hdrs.CONTENT_DISPOSITION] = content_disposition_header(
198 -
            disptype, quote_fields=quote_fields, **params
202 +
            disptype, quote_fields=quote_fields, _charset=_charset, **params
199 203
        )
200 204
201 205
    @abstractmethod

@@ -332,14 +332,41 @@
Loading
332 332
    return default
333 333
334 334
335 +
not_qtext_re = re.compile(r"[^\041\043-\133\135-\176]")
336 +
QCONTENT = {chr(i) for i in range(0x20, 0x7F)} | {"\t"}
337 +
338 +
339 +
def quoted_string(content: str) -> str:
340 +
    """Return 7-bit content as quoted-string.
341 +
342 +
    Format content into a quoted-string as defined in RFC5322 for
343 +
    Internet Message Format. Notice that this is not the 8-bit HTTP
344 +
    format, but the 7-bit email format. Content must be in usascii or
345 +
    a ValueError is raised.
346 +
    """
347 +
    if not (QCONTENT > set(content)):
348 +
        raise ValueError(f"bad content for quoted-string {content!r}")
349 +
    return not_qtext_re.sub(lambda x: "\\" + x.group(0), content)
350 +
351 +
335 352
def content_disposition_header(
336 -
    disptype: str, quote_fields: bool = True, **params: str
353 +
    disptype: str, quote_fields: bool = True, _charset: str = "utf-8", **params: str
337 354
) -> str:
338 -
    """Sets ``Content-Disposition`` header.
355 +
    """Sets ``Content-Disposition`` header for MIME.
356 +
357 +
    This is the MIME payload Content-Disposition header from RFC 2183
358 +
    and RFC 7579 section 4.2, not the HTTP Content-Disposition from
359 +
    RFC 6266.
339 360
340 361
    disptype is a disposition type: inline, attachment, form-data.
341 362
    Should be valid extension token (see RFC 2183)
342 363
364 +
    quote_fields performs value quoting to 7-bit MIME headers
365 +
    according to RFC 7578. Set to quote_fields to False if recipient
366 +
    can take 8-bit file names and field values.
367 +
368 +
    _charset specifies the charset to use when quote_fields is True.
369 +
343 370
    params is a dict with disposition params.
344 371
    """
345 372
    if not disptype or not (TOKEN > set(disptype)):
@@ -353,10 +380,23 @@
Loading
353 380
                raise ValueError(
354 381
                    "bad content disposition parameter" " {!r}={!r}".format(key, val)
355 382
                )
356 -
            qval = quote(val, "") if quote_fields else val
357 -
            lparams.append((key, '"%s"' % qval))
358 -
            if key == "filename":
359 -
                lparams.append(("filename*", "utf-8''" + qval))
383 +
            if quote_fields:
384 +
                if key.lower() == "filename":
385 +
                    qval = quote(val, "", encoding=_charset)
386 +
                    lparams.append((key, '"%s"' % qval))
387 +
                else:
388 +
                    try:
389 +
                        qval = quoted_string(val)
390 +
                    except ValueError:
391 +
                        qval = "".join(
392 +
                            (_charset, "''", quote(val, "", encoding=_charset))
393 +
                        )
394 +
                        lparams.append((key + "*", qval))
395 +
                    else:
396 +
                        lparams.append((key, '"%s"' % qval))
397 +
            else:
398 +
                qval = val.replace("\\", "\\\\").replace('"', '\\"')
399 +
                lparams.append((key, '"%s"' % qval))
360 400
        sparams = "; ".join("=".join(pair) for pair in lparams)
361 401
        value = "; ".join((value, sparams))
362 402
    return value
Files Coverage
aiohttp 97.53%
Project Totals (43 files) 97.53%
1
codecov:
2
  branch: master
3

4
coverage:
5
  range: "95..100"
6

7
  status:
8
    project: no
9

10
flags:
11
  library:
12
    paths:
13
    - aiohttp/
14
  configs:
15
    paths:
16
    - requirements/
17
    - ".git*"
18
    - "*.toml"
19
    - "*.yml"
20
  changelog:
21
    paths:
22
    - CHANGES/
23
    - CHANGES.rst
24
  docs:
25
    paths:
26
    - docs/
27
    - "*.md"
28
    - "*.rst"
29
    - "*.txt"
30
  tests:
31
    paths:
32
    - tests/
33
  tools:
34
    paths:
35
    - tools/
36
  third-party:
37
    paths:
38
    - vendor/
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading