LCOV - code coverage report
Current view: top level - lib/helper - whatwg-fetch.js (source / functions) Hit Total Coverage
Test: lcov.info Lines: 470 504 93.3 %
Date: 2024-12-07 00:20:21 Functions: 4 9 44.4 %
Branches: 43 46 93.5 %

           Branch data     Line data    Source code
       1            [ + ]:        348 : /**
       2                 :        348 :  * Fetch Living Standard — Last Updated 7 June 2023
       3                 :        348 :  * url: https://fetch.spec.whatwg.org/#forbidden-request-header
       4                 :        348 :  *
       5                 :        348 :  * @module  whatwg-fetch
       6                 :        348 :  * @desc    Helper module - Helper functions for the main module {@link module:whatwg-xhr whatwg-xhr}.
       7                 :        348 :  * @version 1.0.0
       8                 :        348 :  * @author  Essam A. El-Sherif
       9                 :        348 :  */
      10                 :        348 : 
      11                 :        348 : /* Import nodeJS core modules */
      12                 :        348 : import { Blob }                               from 'node:buffer';
      13                 :        348 : import { Readable as ReadableStream, Duplex } from 'node:stream';
      14                 :        348 : import { TextEncoder }                        from 'node:util';
      15                 :        348 : import { URLSearchParams }                    from 'node:url';
      16                 :        348 : 
      17                 :        348 : /* Import from whatwg-xhr main module */
      18                 :        348 : import { FormData }           from '../whatwg-xhr.js';
      19                 :        348 : import { disableHeaderCheck } from '../whatwg-xhr.js';
      20                 :        348 : 
      21                 :        348 : /**
      22                 :        348 :  * @const {Set} forbiddenRequestHeaderName
      23                 :        348 :  * @desc  Set of forbidden request-headers, as defined by {@link https://fetch.spec.whatwg.org/#forbidden-request-header WHATWG Fetch Living Standard}.
      24                 :        348 :  */
      25                 :        348 : const forbiddenRequestHeaderName = new Set([
      26                 :        348 :         'accept-charset',
      27                 :        348 :         'accept-encoding',
      28                 :        348 :         'access-control-request-headers',
      29                 :        348 :         'access-control-request-method',
      30                 :        348 :         'connection',
      31                 :        348 :         'content-length',
      32                 :        348 :         'cookie',
      33                 :        348 :         'cookie2',
      34                 :        348 :         'date',
      35                 :        348 :         'dnt',
      36                 :        348 :         'expect',
      37                 :        348 :         'host',
      38                 :        348 :         'keep-alive',
      39                 :        348 :         'origin',
      40                 :        348 :         'referer',
      41                 :        348 :         'set-cookie',
      42                 :        348 :         'te',
      43                 :        348 :         'trailer',
      44                 :        348 :         'transfer-encoding',
      45                 :        348 :         'upgrade',
      46                 :        348 :         'via'
      47                 :        348 : ]);
      48                 :        348 : 
      49                 :        348 : /**
      50                 :        348 :  * Fetch Living Standard — Last Updated 7 June 2023
      51                 :        348 :  * url: https://fetch.spec.whatwg.org/#forbidden-request-header
      52                 :        348 :  *
      53                 :        348 :  * A header (name, value) is forbidden request-header if these steps return true:
      54                 :        348 :  *     1. If name is a byte-case-insensitive match for one of:
      55                 :        348 :  *         `Accept-Charset`
      56                 :        348 :  *         `Accept-Encoding`
      57                 :        348 :  *         `Access-Control-Request-Headers`
      58                 :        348 :  *         `Access-Control-Request-Method`
      59                 :        348 :  *         `Connection`
      60                 :        348 :  *         `Content-Length`
      61                 :        348 :  *         `Cookie`
      62                 :        348 :  *         `Cookie2`
      63                 :        348 :  *         `Date`
      64                 :        348 :  *         `DNT`
      65                 :        348 :  *         `Expect`
      66                 :        348 :  *         `Host`
      67                 :        348 :  *         `Keep-Alive`
      68                 :        348 :  *         `Origin`
      69                 :        348 :  *         `Referer`
      70                 :        348 :  *         `Set-Cookie`
      71                 :        348 :  *         `TE`
      72                 :        348 :  *         `Trailer`
      73                 :        348 :  *         `Transfer-Encoding`
      74                 :        348 :  *         `Upgrade`
      75                 :        348 :  *         `Via`
      76                 :        348 :  *     then return true.
      77                 :        348 :  *
      78                 :        348 :  *     2. If name when byte-lowercased starts with `proxy-` or `sec-`, then return true.
      79                 :        348 :  *
      80                 :        348 :  *     3. If name is a byte-case-insensitive match for one of:
      81                 :        348 :  *         `X-HTTP-Method`
      82                 :        348 :  *         `X-HTTP-Method-Override`
      83                 :        348 :  *         `X-Method-Override`
      84                 :        348 :  *     then:
      85                 :        348 :  *         1. Let parsedValues be the result of getting, decoding, and splitting value.
      86                 :        348 :  *         2. For each method of parsedValues: if the isomorphic encoding of method is
      87                 :        348 :  *            a forbidden method, then return true.
      88                 :        348 :  *
      89                 :        348 :  *     4. Return false.
      90                 :        348 :  *
      91                 :        348 :  *     Note: These are forbidden so the user agent remains in full control over them.
      92                 :        348 :  *     Header names starting with `Sec-` are reserved to allow new headers to be minted
      93                 :        348 :  *     that are safe from APIs using fetch that allow control over headers by developers,
      94                 :        348 :  *     such as XMLHttpRequest. [XHR]
      95                 :        348 :  *     The `Set-Cookie` header is semantically a response header, so it is not useful on
      96                 :        348 :  *     requests. Because `Set-Cookie` headers cannot be combined, they require more complex
      97                 :        348 :  *     handling in the Headers object. It is forbidden here to avoid leaking this complexity
      98                 :        348 :  *     into requests.
      99                 :        348 :  *
     100                 :        348 :  * @func     forbiddenRequestHeader
     101                 :        348 :  * @static
     102                 :        348 :  * @param    {string}  name  - The name of the Http request header.
     103                 :        348 :  * @param    {string}  value - The value of the Http request header.
     104                 :        348 :  * @return   {boolean} true if the Http request header is forbidden, otherwise false.
     105                 :        348 :  * @desc     To determine if the Http request header given by name and value is forbidden by {@link https://fetch.spec.whatwg.org/#forbidden-request-header WHATWG Fetch Living Standard}.
     106                 :        348 :  * @requires module:whatwg-xhr.disableHeaderCheck
     107                 :        348 :  */
     108            [ + ]:        348 : export function forbiddenRequestHeader(name, value){
     109                 :       2310 : 
     110                 :       2310 :         if(disableHeaderCheck)
     111            [ - ]:       2310 :                 return false;
     112                 :       2310 : 
     113                 :       2310 :         // fetch.spec.1. If name is a byte-case-insensitive match for one of the forbiddenRequestHeaderName set, then return true.
     114                 :       2310 :         if(!name || forbiddenRequestHeaderName.has(name.toLowerCase()))
     115       [ + ][ + ]:       2310 :                 return true;
     116                 :       1480 : 
     117                 :       1480 :         // fetch.spec.2. If name when byte-lowercased starts with `proxy-` or `sec-`, then return true.
     118            [ + ]:       2310 :         if(name.toLowerCase().startsWith('proxy-') || name.toLowerCase().startsWith('sec-'))
     119       [ + ][ + ]:       2310 :                 return true;
     120                 :       1475 : 
     121                 :       1475 :         // fetch.spec.3. If name is a byte-case-insensitive match for one of: `X-HTTP-Method`, `X-HTTP-Method-Override`, `X-Method-Override`
     122            [ + ]:       2310 :         if(['x-http-method', 'x-http-method-override', 'x-method-override'].includes(name.toLowerCase())){
     123                 :        144 : 
     124                 :        144 :                 // fetch.spec.3.1. Let parsedValues be the result of getting, decoding, and splitting value.
     125            [ + ]:        144 :                 const parsedValues = value.split(',').map(e => e.trim());
     126                 :        144 : 
     127                 :        144 :                 // fetch.spec.3.2. For each method of parsedValues: if the isomorphic encoding of method is a forbidden method, then return true.
     128                 :        144 :                 for(let method of parsedValues)
     129            [ + ]:        144 :                         if(forbiddenRequestHeaderName.has(method.toLowerCase()))
     130                 :        138 :                                 return true;
     131            [ + ]:        144 :         }
     132                 :       1475 :         return false;
     133                 :        348 : };
     134                 :        348 : 
     135                 :        348 : /**
     136                 :        348 :  * Fetch Living Standard — Last Updated 6 March 2023
     137                 :        348 :  * url: https://fetch.spec.whatwg.org/#methods
     138                 :        348 :  *
     139                 :        348 :  * A method is a byte sequence that matches the method token production.
     140                 :        348 :  * A CORS-safelisted method is a method that is `GET`, `HEAD`, or `POST`.
     141                 :        348 :  * A forbidden method is a method that is a byte-case-insensitive match for `CONNECT`, `TRACE`, or `TRACK`.
     142                 :        348 :  *
     143                 :        348 :  * To normalize a method, if it is a byte-case-insensitive match for `DELETE`, `GET`, `HEAD`, `OPTIONS`, `POST`, or `PUT`, byte-uppercase it.
     144                 :        348 :  *
     145                 :        348 :  * Normalization is done for backwards compatibility and consistency across APIs as methods are actually "case-sensitive".
     146                 :        348 :  * Using `patch` is highly likely to result in a `405 Method Not Allowed`. `PATCH` is much more likely to succeed.
     147                 :        348 :  * There are no restrictions on methods. `CHICKEN` is perfectly acceptable (and not a misspelling of `CHECKIN`). Other than those that are normalized
     148                 :        348 :  * there are no casing restrictions either. `Egg` or `eGg` would be fine, though uppercase is encouraged for consistency.
     149                 :        348 :  *
     150                 :        348 :  * @const {Set} corsSafelistedHttpRequestMethod
     151                 :        348 :  * @static
     152                 :        348 :  * @desc   A CORS-safelisted method is a method that is `GET`, `HEAD`, or `POST`, as defined by {@link https://fetch.spec.whatwg.org/#methods WHATWG Fetch Living Standard}.
     153                 :        348 :  */
     154                 :        348 : export const corsSafelistedHttpRequestMethod = new Set(['get', 'head', 'post']);
     155                 :        348 : 
     156                 :        348 : /**
     157                 :        348 :  * @const {Set} forbiddenHttpRequestMethod
     158                 :        348 :  * @static
     159                 :        348 :  * @desc   A forbidden method is a method that is a byte-case-insensitive match for `CONNECT`, `TRACE`, or `TRACK`, as defined by {@link https://fetch.spec.whatwg.org/#methods WHATWG Fetch Living Standard}..
     160                 :        348 :  */
     161                 :        348 : export const forbiddenHttpRequestMethod = new Set(['connect', 'trace', 'track']);
     162                 :        348 : 
     163                 :        348 : /**
     164                 :        348 :  * Fetch Living Standard — Last Updated 7 June 2023
     165                 :        348 :  * url: https://fetch.spec.whatwg.org/#bodyinit-unions
     166                 :        348 :  *
     167                 :        348 :  * BodyInit unions
     168                 :        348 :  *   typedef (Blob or BufferSource or FormData or URLSearchParams or USVString) XMLHttpRequestBodyInit;
     169                 :        348 :  *   typedef (ReadableStream or XMLHttpRequestBodyInit) BodyInit;
     170                 :        348 :  *
     171                 :        348 :  * @func      isXMLHttpRequestBodyInit
     172                 :        348 :  * @param     {Blob|ArrayBuffer|Uint8Array|DataView|FormData|URLSearchParams|string} body - The request body.
     173                 :        348 :  * @return    {boolean} true if the typeof request body is one of the above types, otherwise false.
     174                 :        348 :  * @desc      To determine the typeof request body, as defined by {@link https://fetch.spec.whatwg.org/#bodyinit-unions WHATWG Fetch Living Standard}.
     175                 :        348 :  * @requires  module:whatwg-xhr.FormData
     176                 :        348 :  */
     177                 :          0 : function isXMLHttpRequestBodyInit(body){
     178                 :          0 : 
     179                 :          0 :         return body instanceof Blob            ||
     180                 :          0 : 
     181                 :          0 :                body instanceof ArrayBuffer     ||
     182                 :          0 :                body instanceof Uint8Array      ||
     183                 :          0 :                body instanceof DataView        ||
     184                 :          0 : 
     185                 :          0 :                body instanceof FormData        ||
     186                 :          0 :                body instanceof URLSearchParams ||
     187                 :          0 : 
     188                 :          0 :                typeof body === 'string';
     189                 :          0 : }
     190                 :        348 : 
     191                 :        348 : /**
     192                 :        348 :  * @func     isXMLHttpRequestBodyInit
     193                 :        348 :  * @param    {Blob|ArrayBuffer|Uint8Array|DataView|FormData|URLSearchParams|string|ReadableStream} body - The request body.
     194                 :        348 :  * @return   {boolean} true if the typeof request body is one of the above types, otherwise false.
     195                 :        348 :  * @desc     To determine the typeof request body, as defined by {@link https://fetch.spec.whatwg.org/#bodyinit-unions WHATWG Fetch Living Standard}.
     196                 :        348 :  */
     197                 :          0 : function isBodyInit(body){
     198                 :          0 : 
     199                 :          0 :         return body instanceof ReadableStream ||
     200                 :          0 :                isXMLHttpRequestBodyInit(body);
     201                 :          0 : }
     202                 :        348 : 
     203                 :        348 : /**
     204                 :        348 :  * Fetch Living Standard — Last Updated 7 June 2023
     205                 :        348 :  * url: https://fetch.spec.whatwg.org/#bodyinit-safely-extract
     206                 :        348 :  *
     207                 :        348 :  * To safely extract a body with type from a byte sequence or BodyInit object object,
     208                 :        348 :  * run these steps:
     209                 :        348 :  *   1. If object is a ReadableStream object, then:
     210                 :        348 :  *      1. Assert: object is neither disturbed nor locked.
     211                 :        348 :  *   2. Return the result of extracting object.
     212                 :        348 :  * Note: The safely extract operation is a subset of the extract operation that is
     213                 :        348 :  * guaranteed to not throw an exception.
     214                 :        348 :  *
     215                 :        348 :  * To extract a body with type from a byte sequence or BodyInit object object, with an
     216                 :        348 :  * optional boolean keepalive (default false), run these steps:
     217                 :        348 :  *   1. Let stream be null.
     218                 :        348 :  *   2. If object is a ReadableStream object, then set stream to object.
     219                 :        348 :  *   3. Otherwise, if object is a Blob object, set stream to the result of running
     220                 :        348 :  *      object’s get stream.
     221                 :        348 :  *   4. Otherwise, set stream to a new ReadableStream object, and set up stream with
     222                 :        348 :  *      byte reading support.
     223                 :        348 :  *   5. Assert: stream is a ReadableStream object.
     224                 :        348 :  *   6. Let action be null.
     225                 :        348 :  *   7. Let source be null.
     226                 :        348 :  *   8. Let length be null.
     227                 :        348 :  *   9. Let type be null.
     228                 :        348 :  *  10. Switch on object:
     229                 :        348 :  *        Blob
     230                 :        348 :  *          Set source to object.
     231                 :        348 :  *          Set length to object’s size.
     232                 :        348 :  *          If object’s type attribute is not the empty byte sequence, set type to its value.
     233                 :        348 :  *        byte sequence
     234                 :        348 :  *          Set source to object.
     235                 :        348 :  *        BufferSource
     236                 :        348 :  *          Set source to a copy of the bytes held by object.
     237                 :        348 :  *        FormData
     238                 :        348 :  *          Set action to this step: run the multipart/form-data encoding algorithm, with object’s
     239                 :        348 :  *          entry list and UTF-8.
     240                 :        348 :  *          Set source to object.
     241                 :        348 :  *          Set length to unclear, see html/6424 for improving this.
     242                 :        348 :  *          Set type to `multipart/form-data; boundary=`, followed by the multipart/form-data
     243                 :        348 :  *          boundary string generated by the multipart/form-data encoding algorithm.
     244                 :        348 :  *        URLSearchParams
     245                 :        348 :  *          Set source to the result of running the application/x-www-form-urlencoded serializer
     246                 :        348 :  *          with object’s list.
     247                 :        348 :  *          Set type to `application/x-www-form-urlencoded;charset=UTF-8`.
     248                 :        348 :  *        scalar value string
     249                 :        348 :  *          Set source to the UTF-8 encoding of object.
     250                 :        348 :  *          Set type to `text/plain;charset=UTF-8`.
     251                 :        348 :  *        ReadableStream
     252                 :        348 :  *          If keepalive is true, then throw a TypeError.
     253                 :        348 :  *          If object is disturbed or locked, then throw a TypeError.
     254                 :        348 :  *  11. If source is a byte sequence, then set action to a step that returns source and
     255                 :        348 :  *      length to source’s length.
     256                 :        348 :  *  12. If action is non-null, then run these steps in parallel:
     257                 :        348 :  *          1. Run action.
     258                 :        348 :  *             Whenever one or more bytes are available and stream is not errored,
     259                 :        348 :  *             enqueue a Uint8Array wrapping an ArrayBuffer containing
     260                 :        348 :  *             the available bytes into stream.
     261                 :        348 :  *             When running action is done, close stream.
     262                 :        348 :  *  13. Let body be a body whose stream is stream, source is source, and length is length.
     263                 :        348 :  *  14. Return (body, type).
     264                 :        348 :  *
     265                 :        348 :  * @func     safelyExtractBodyWithType
     266                 :        348 :  * @static
     267                 :        348 :  * @param    {Blob|ArrayBuffer|Uint8Array|DataView|FormData|URLSearchParams|string|ReadableStream} body - The request body.
     268                 :        348 :  * @param    {boolean} keepAlive - (Optional).
     269                 :        348 :  * @return   {object}  Request body with type.
     270                 :        348 :  * @desc     To return the request body with type, as defined by {@link https://fetch.spec.whatwg.org/#bodyinit-safely-extract WHATWG Fetch Living Standard}.
     271                 :        348 :  * @requires module:whatwg-xhr.FormData
     272                 :        348 :  */
     273            [ + ]:        348 : export function safelyExtractBodyWithType(body, keepAlive = false){
     274                 :        123 : 
     275                 :        123 :         // fetch.spec.1. Let stream be null.
     276                 :        123 :         let stream = null;
     277                 :        123 : 
     278                 :        123 :         // fetch.spec.2. If object is a ReadableStream object, then set stream to object.
     279            [ - ]:        123 :         if(body instanceof ReadableStream){
     280                 :          0 :                 stream = body;
     281                 :          0 :         }
     282                 :        123 :         else
     283                 :        123 :         // fetch.spec.3. Otherwise, if object is a Blob object, set stream to the result
     284                 :        123 :         //               of running object’s get stream.
     285            [ + ]:        123 :         if(body instanceof Blob){
     286                 :         17 :                 stream = Duplex.from(body);
     287            [ + ]:         17 :         }
     288                 :         60 :         else{
     289                 :         60 :         // fetch.spec.4. Otherwise, set stream to a new ReadableStream object, and set up
     290                 :         60 :         //               stream with byte reading support.
     291                 :         60 :                 stream = new ReadableStream({type: 'bytes'});
     292                 :         60 :         }
     293                 :        123 : 
     294                 :        123 :         // fetch.spec.5. Assert: stream is a ReadableStream object.
     295                 :        123 :         {
     296                 :        123 :                 // @todo...
     297                 :        123 :         }
     298                 :        123 : 
     299                 :        123 :         // fetch.spec.6. Let action be null.
     300                 :        123 :         // fetch.spec.7. Let source be null.
     301                 :        123 :         // fetch.spec.8. Let length be null.
     302                 :        123 :         // fetch.spec.9. Let type be null.
     303                 :        123 :         let action = null, source = null, length = null, type = null;
     304                 :        123 : 
     305                 :        123 :         // fetch.spec.10. Switch on object: Blob, byte sequence, BufferSource, FormData,
     306                 :        123 :         //                URLSearchParams, scalar value string, ReadableStream
     307                 :        123 : 
     308                 :        123 :         // fetch.spec.10. Switch on object: Blob
     309            [ + ]:        123 :         if(body instanceof Blob){
     310                 :         17 :                 // stream: NOT null, action: null, source: null, length: null, type: null
     311                 :         17 : 
     312                 :         17 :                 // fetch.spec.10. Blob: Set source to object.
     313                 :         17 :                 source = body;
     314                 :         17 : 
     315                 :         17 :                 // fetch.spec.10. Blob: Set length to object’s size.
     316                 :         17 :                 length = body.size;
     317                 :         17 : 
     318                 :         17 :                 // fetch.spec.10. Blob: If object’s type attribute is not the empty byte sequence,
     319                 :         17 :                 //                      set type to its value.
     320       [ + ][ + ]:         17 :                 type = body.type ? body.type : type;
     321                 :         17 : 
     322                 :         17 :                 // stream: NOT null, action: null, source: NOT null, length: NOT null, type: <depends>
     323            [ + ]:         17 :         }
     324                 :         60 :         else
     325                 :         60 :         // fetch.spec.10. Switch on object: BufferSource
     326                 :         60 :         if(
     327            [ + ]:         60 :             body instanceof ArrayBuffer       ||
     328                 :         57 : 
     329            [ + ]:         60 :             body instanceof Uint8Array        ||
     330            [ + ]:         60 :             body instanceof Uint16Array       ||
     331            [ + ]:         60 :             body instanceof Uint32Array       ||
     332            [ + ]:         60 :             body instanceof Uint8ClampedArray ||
     333            [ + ]:         60 :             body instanceof Int8Array         ||
     334            [ + ]:         60 :             body instanceof Int16Array        ||
     335            [ + ]:         60 :             body instanceof Int32Array        ||
     336            [ + ]:         60 :             body instanceof Float32Array      ||
     337            [ + ]:         60 :             body instanceof Float64Array      ||
     338            [ + ]:         60 :             body instanceof DataView          ||
     339                 :         54 : 
     340                 :         54 :                 body instanceof Buffer
     341            [ + ]:         60 :         ){
     342                 :          6 :                 // fetch.spec.10. Set source to a copy of the bytes held by object.
     343                 :          6 :                 if(body instanceof Buffer)
     344                 :          6 :                         source = Buffer.from(body);
     345                 :          6 :                 else
     346                 :          6 :                 if(body instanceof ArrayBuffer)
     347            [ + ]:          6 :                         source = Buffer.from(Buffer.from(body));
     348                 :          3 :                 else
     349                 :          3 :                         source = Buffer.copyBytesFrom(body);
     350                 :          6 :                 // stream: NOT null, action: null, source: NOT null, length: null, type: null
     351                 :          6 : 
     352                 :          6 :                 // @revise
     353                 :          6 :                 length = source.length;
     354            [ + ]:          6 :         }
     355                 :         54 :         else
     356                 :         54 :         // fetch.spec.10. Switch on object: FormData
     357                 :         54 :         if(isFormDataLike(body)){
     358                 :         54 : 
     359                 :         54 :                 const boundary = `----formdata-undici-${Math.random()}`.replace('.', '').slice(0, 32);
     360                 :         54 :                 const prefix = `--${boundary}\r\nContent-Disposition: form-data`;
     361                 :         54 : 
     362                 :         54 :                 /*! formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
     363                 :         54 :                 const escape = (str) => {
     364                 :          0 :                         return str.replace(/\n/g, '%0A').replace(/\r/g, '%0D').replace(/"/g, '%22');
     365                 :          0 :                 }
     366                 :         54 : 
     367                 :         54 :                 const normalizeLinefeeds = (value) => {
     368                 :          0 :                         return value.replace(/\r?\n|\r/g, '\r\n');
     369                 :          0 :                 }
     370                 :         54 : 
     371                 :         54 :                 const enc = new TextEncoder();
     372                 :         54 :                 const blobParts = [];
     373                 :         54 :                 const rn = new Uint8Array([13, 10]);
     374                 :         54 : 
     375                 :         54 :                 length = 0;
     376                 :         54 : 
     377                 :         54 :                 for(const [name, value] of body){
     378                 :         54 :                         if(typeof value === 'string'){
     379                 :         54 :                                 const chunk = enc.encode(
     380                 :         54 :                                         prefix +
     381                 :         54 :                                         `; name="${escape(normalizeLinefeeds(name))}"` +
     382                 :         54 :                                         `\r\n\r\n${normalizeLinefeeds(value)}\r\n`
     383                 :         54 :                                 );
     384                 :         54 : 
     385                 :         54 :                                 blobParts.push(chunk);
     386                 :         54 :                                 length += chunk.byteLength;
     387                 :         54 :                         }
     388                 :         54 :                         else{
     389                 :         54 :                                 const chunk = enc.encode(
     390                 :         54 :                                         `${prefix}; name="${escape(normalizeLinefeeds(name))}"` +
     391                 :         54 :                                         (value.name ? `; filename="${escape(value.name)}"` : '') + '\r\n' +
     392                 :         54 :                                         `Content-Type: ${value.type || 'application/octet-stream'}\r\n\r\n`);
     393                 :         54 : 
     394                 :         54 :                                 blobParts.push(chunk, value, rn);
     395                 :         54 :                                 length += chunk.byteLength + value.size + rn.byteLength;
     396                 :         54 :                         }
     397                 :         54 :                 }
     398                 :         54 : 
     399                 :         54 :                 const chunk = enc.encode(`--${boundary}--`);
     400                 :         54 :                 blobParts.push(chunk);
     401                 :         54 :                 length += chunk.byteLength;
     402                 :         54 : 
     403                 :         54 :                 // fetch.spec.10. FormData: Set source to object.
     404                 :         54 :                 source = body;
     405                 :         54 : 
     406                 :         54 :                 // fetch.spec.10. FormData: Set action to this step:
     407                 :         54 :                 //                run the multipart/form-data encoding algorithm,
     408                 :         54 :                 //                with object’s entry list and UTF-8.
     409                 :         54 : 
     410                 :         54 :                 action = async function *(){
     411                 :          0 :                         for(const part of blobParts){
     412                 :          0 :                                 if(part.stream){
     413                 :          0 :                                         yield * part.stream();
     414                 :          0 :                                 }
     415                 :          0 :                                 else {
     416                 :          0 :                                         yield part;
     417                 :          0 :                                 }
     418                 :          0 :                         }
     419                 :         54 :                 };
     420                 :         54 : 
     421                 :         54 :                 // fetch.spec.10. FormData: Set type to `multipart/form-data; boundary=`,
     422                 :         54 :                 //                followed by the multipart/form-data boundary string
     423                 :         54 :                 //                generated by the multipart/form-data encoding algorithm.
     424                 :         54 :                 type = 'multipart/form-data; boundary=' + boundary;
     425                 :         54 : 
     426                 :         54 :                 // stream: NOT null, action: NOT null, source: NOT null, length: NOT null, type: NOT null
     427                 :         54 :         }
     428                 :         54 :         else
     429                 :         54 :         // fetch.spec.10. Switch on object: URLSearchParams
     430            [ + ]:         54 :         if(body instanceof URLSearchParams){
     431                 :          4 :                 // fetch.spec.10. Set source to the result of running the application/x-www-form-urlencoded serializer with object’s list.
     432                 :          4 :                 source = body.toString();
     433                 :          4 : 
     434                 :          4 :                 // fetch.spec.10. Set type to `application/x-www-form-urlencoded;charset=UTF-8`.
     435                 :          4 :                 type = 'application/x-www-form-urlencoded;charset=UTF-8';
     436                 :          4 :                 // stream: NOT null, action: null, source: NOT null, length: null, type: NOT null
     437                 :          4 : 
     438                 :          4 :                 // @revise
     439                 :          4 :                 length = source.length;
     440            [ + ]:          4 :         }
     441                 :         50 :         else
     442                 :         50 :         // fetch.spec.10. Switch on object: ReadableStream
     443                 :         50 :         if(body instanceof ReadableStream){
     444                 :         50 :                 // fetch.spec.10. If keepalive is true, then throw a TypeError.
     445                 :         50 :                 // fetch.spec.10. If object is disturbed or locked, then throw a TypeError.
     446                 :         50 :                 // @todo...
     447                 :         50 :         }
     448                 :         50 :         else{
     449                 :         50 :                 // fetch.spec.10. Switch on object: scalar value string
     450                 :         50 :                 body = String(body);
     451                 :         50 : 
     452                 :         50 :                 // fetch.spec.10. Set source to the UTF-8 encoding of object.
     453                 :         50 :                 source = new TextEncoder().encode(body);
     454                 :         50 : 
     455                 :         50 :                 // fetch.spec.10. Set type to `text/plain;charset=UTF-8`.
     456                 :         50 :                 type = 'text/plain;charset=UTF-8';
     457                 :         50 : 
     458                 :         50 :                 // stream: NOT null, action: null, source: NOT null, length: null, type: NOT null
     459                 :         50 : 
     460                 :         50 :                 // @revise
     461                 :         50 :                 length = source.length;
     462                 :         50 :         }
     463                 :        123 : 
     464                 :        123 :         // fetch.spec.11. If source is a byte sequence, then set action to a step
     465                 :        123 :         //                that returns source and length to source’s length.
     466                 :        123 :                 // @todo...
     467                 :        123 : 
     468                 :        123 :         // fetch.spec.12. If action is non-null, then run these steps in parallel:
     469            [ - ]:        123 :         if(action){
     470                 :          0 :                 // @todo...
     471                 :          0 :         }
     472                 :        123 : 
     473                 :        123 :         // fetch.spec.13. Let body be a body whose stream is stream, source is source,
     474                 :        123 :         //                and length is length.
     475                 :        123 :         body = {
     476                 :        123 :                 stream: stream,
     477                 :        123 :                 source: source,
     478                 :        123 :                 length: length,
     479                 :        123 :         };
     480                 :        123 : 
     481                 :        123 :         return {body, type};
     482                 :        123 : }
     483                 :        348 : 
     484                 :        348 : /**
     485                 :        348 :  * @func     isFormDataLike
     486                 :        348 :  * @param    {object} object - The object to check.
     487                 :        348 :  * @return   {boolean} true if the given object is an instanceof a FormData constructor, otherwise false.
     488                 :        348 :  * @desc     To determine if the given object is an instanceof a FormData constructor.
     489                 :        348 :  */
     490            [ + ]:         54 : function isFormDataLike(object){
     491                 :         54 :         return (
     492            [ + ]:         54 :                 object &&
     493                 :         53 : 
     494            [ + ]:         54 :                 typeof object        === 'object'   &&
     495            [ + ]:         54 :                 typeof object.append === 'function' &&
     496            [ + ]:         54 :                 typeof object.delete === 'function' &&
     497            [ + ]:         54 :                 typeof object.get    === 'function' &&
     498            [ + ]:         54 :                 typeof object.getAll === 'function' &&
     499            [ + ]:         54 :                 typeof object.has    === 'function' &&
     500            [ + ]:         54 :                 typeof object.set    === 'function' &&
     501                 :          4 : 
     502                 :          4 :                 object[Symbol.toStringTag] === 'FormData'
     503                 :         54 :         );
     504                 :         54 : }

Generated by: LCOV version 1.14