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 81x 81x 81x 81x 81x 9x 323x 323x 5x 5x 323x 9x 9x 9x 9x 9x 9x 9x 81x 81x 81x 81x 164x 164x 164x 164x 78x 78x 78x 160x 164x 160x 164x 164x 159x 159x 75x 75x 75x 75x 75x 75x 75x 75x 81x  
// 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,
  };
}