All files multipart.js

100% Statements 58/58
100% Branches 19/19
100% Functions 3/3
100% Lines 58/58

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 599x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 9x 85x 85x 85x 85x 85x 9x 339x 339x 5x 5x 339x 9x 9x 9x 9x 9x 9x 9x 85x 85x 85x 85x 172x 172x 172x 172x 82x 82x 82x 168x 172x 168x 172x 172x 167x 167x 79x 79x 79x 79x 79x 79x 79x 79x 85x  
// multipart/form-data assembled by hand as one ArrayBuffer (DESIGN 5.1 and 5.2).
//
// wx.uploadFile cannot set a part Content-Type, so in 1.x the `config` part reached the platform as
// text/plain and native evaluate failed with HTTP 415. The body built here is sent with
// wx.request({method: 'POST', header: {'content-type': 'multipart/form-data; boundary=...'}, data}).
import { utf8Encode } from './codec.js';
import { localError } from './errors.js';
import { concatBytes, exactArrayBuffer, toUint8, randomBytes } from './util.js';
 
const CRLF = new Uint8Array([13, 10]);
const BOUNDARY_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
 
export function generateBoundary() {
  const b = randomBytes(24);
  let s = '----YuguFormBoundary';
  for (let i = 0; i < b.length; i++) s += BOUNDARY_ALPHABET[b[i] % BOUNDARY_ALPHABET.length];
  return s;
}
 
function checkToken(v, what) {
  if (typeof v !== 'string' || !v || /["\r\n]/.test(v)) {
    throw localError(90010, 'multipart ' + what + ' must be a non empty string without quotes or line breaks');
  }
}
 
/**
 * parts: [{name, data: string | ArrayBuffer | ArrayBufferView, filename?, contentType?}]
 * A part without filename is a form parameter on the platform; a part with filename is a file.
 * Returns {body: ArrayBuffer, bytes: Uint8Array, boundary, contentType}.
 */
export function buildMultipart(parts, boundary) {
  const b = boundary || generateBoundary();
  checkToken(b, 'boundary');
  const chunks = [];
  for (let i = 0; i < parts.length; i++) {
    const p = parts[i];
    checkToken(p.name, 'part name');
    let head = '--' + b + '\r\nContent-Disposition: form-data; name="' + p.name + '"';
    if (p.filename !== undefined && p.filename !== null) {
      checkToken(p.filename, 'filename');
      head += '; filename="' + p.filename + '"';
    }
    head += '\r\n';
    if (p.contentType) head += 'Content-Type: ' + p.contentType + '\r\n';
    head += '\r\n';
    const data = typeof p.data === 'string' ? utf8Encode(p.data) : toUint8(p.data);
    if (!data) throw localError(90010, 'multipart part ' + p.name + ' has no data');
    chunks.push(utf8Encode(head), data, CRLF);
  }
  chunks.push(utf8Encode('--' + b + '--\r\n'));
  const bytes = concatBytes(chunks);
  return {
    body: exactArrayBuffer(bytes),
    bytes,
    boundary: b,
    contentType: 'multipart/form-data; boundary=' + b,
  };
}