TextualEncoding.decode constructor

TextualEncoding.decode(
  1. String str, {
  2. int offset = 0,
  3. bool allowPreamble = false,
})

Decode from text.

Throws exception if there is no text encoding block found in the encoding.

Data before the encapsulation boundary is permitted if allowPreamble is true (the default is false). That data can be identified by examining TextualEncoding.source in the result and comparing it any offset that was provided.

Implementation

TextualEncoding.decode(String str,
    {int offset = 0, bool allowPreamble = false}) {
  // Set starting offset

  if (offset < 0) {
    throw ArgumentError.value(offset, 'offset', 'is negative');
  }
  var p = offset;

  // Skip whitespace

  while (p < str.length) {
    final ch = str[p];
    if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n') {
      p++;
    } else {
      break;
    }
  }

  // Find the start of the pre-encapsulation header (i.e. the "-----BEGIN")
  //
  // Note: this implementation does not care if it is at the beginning of a
  // line or not.

  if (allowPreamble) {
    // Skip any text before the pre-encapsulation boundary
    p = str.indexOf(_boundaryBegin, p);
    if (p < 0) {
      throw KeyMissing('no Textual Encoding found');
    }
  } else {
    // Must start with the pre-encapsulation boundary (other than whitespace)
    if (!str.startsWith(_boundaryBegin, p)) {
      throw KeyMissing('no Textual Encoding');
    }
  }

  final offsetBegin = p; // record where the block starts

  p += _boundaryBegin.length; // skip over the "-----BEGIN "

  final labelBegin = p; // position just after the space in "-----BEGIN "

  // Skip to end of label (as indicated by "-----")

  var dashLength1 = 0;

  while (p < str.length) {
    final ch = str[p];
    if (ch == '-') {
      dashLength1++;
      if (dashLength1 == 5) {
        p++;
        break; // end of encapsulation boundary reached
      }
    } else if (ch == '\r' || ch == '\n') {
      p++;
      break; // end of line reached
    } else {
      dashLength1 = 0;
    }
    p++;
  }
  // Above loop finishes if "-----" encountered, end of line reached, or
  // end of encoding reached.

  if (dashLength1 != 5) {
    // Did not find the "-----" on the same line.
    throw KeyBad('malformed pre-encapsulation boundary');
  }

  final labelEnd = p - 5; // position where the "-----" starts
  final dataBegin = p; // position immediately after the "-----BEGIN ...-----"

  // Locate end of encapsulated data

  int? dataEnd;

  while (p < str.length) {
    if (str[p] == '-' &&
        p + _boundaryEnd.length < str.length &&
        str.substring(p, p + _boundaryEnd.length) == _boundaryEnd) {
      dataEnd = p;
      p += _boundaryEnd.length;
      break;
    }
    p++;
  }
  // Above loop finishes when "-----END " found or end of encoding.

  if (dataEnd == null) {
    // The "-----END " was not found in the encoding
    throw KeyBad('missing post-encapsulation boundary');
  }

  // Skip over the rest of the post-encapsulation boundary
  //
  // This implementation ignores the end-label (i.e. it does not need to match
  // the begin-label).

  var dashLength2 = 0;

  while (p < str.length) {
    final ch = str[p];
    if (ch == '-') {
      dashLength2++;
      if (dashLength2 == 5) {
        p++;
        break; // end of encapsulation boundary reached
      }
    } else if (ch == '\r' || ch == '\n') {
      p++;
      break; // end of line reached without encountering "-----"
    } else {
      dashLength2 = 0;
    }
    p++;
  }
  // Above loop finishes when "-----" is found on the same line, the end of
  // the line is reached, or the end of the encoding is reached.

  if (dashLength2 != 5) {
    // The "-----" was not found in the rest of the line
    throw KeyBad('malformed post-encapsulation boundary');
  }

  // Skip any CR, LF or CR-LF at end of encapsulation boundary

  if (p < str.length && str[p] == '\r') {
    p++;
  }
  if (p < str.length && str[p] == '\n') {
    p++;
  }

  // Decode the encoded text
  //
  // RFC 7468 specifies the encoded text uses the Base-64 encoding defined
  // in section 4 of [RFC 4648](https://tools.ietf.org/html/rfc4648#section-4)
  // (which uses "+" and "/" as the 62nd and 63rd characters, and "=" for
  // padding). The Dart Base64Codec implements Base-64 as defined by RFC 4648,
  // but the decoded does not allow invalid characters and requires the
  // correct padding.

  final encapsulatedData = StringBuffer();
  //final encapsulatedData = encoding.substring(dataBegin, dataEnd);
  for (var q = dataBegin; q < dataEnd; q++) {
    final ch = str.codeUnitAt(q);
    if ('A'.codeUnitAt(0) <= ch && ch <= 'Z'.codeUnitAt(0) ||
        'a'.codeUnitAt(0) <= ch && ch <= 'z'.codeUnitAt(0) ||
        '0'.codeUnitAt(0) <= ch && ch <= '9'.codeUnitAt(0) ||
        '+'.codeUnitAt(0) == ch ||
        '/'.codeUnitAt(0) == ch ||
        '='.codeUnitAt(0) == ch) {
      // valid character: use
      encapsulatedData.writeCharCode(ch);
    } else if (' \t\n\r'.codeUnits.contains(ch)) {
      // whitespace: ignore
    } else {
      throw KeyBad('unexpected character in base64 text: charcode=$ch');
    }
  }
  try {
    data = base64.decode(encapsulatedData.toString()); // decode base64

    label = str.substring(labelBegin, labelEnd);
    source = TextSource._internal(str, offsetBegin, p);
  } on FormatException catch (e) {
    throw KeyBad('invalid encapsulated encoding: ${e.message}');
  }
}