How can I uudecode in Dart?

160 Views Asked by At

It's pretty easy to load an asset after waiting for Future<Map<String, T>>. It's even easier to embed the file within the program code.

import 'dart:convert' as JSON;

void main() {
  var jsonStr = """{ "en": "Greetings, World!", "cn": "你好,世界!" }""";

  final json = JSON.jsonDecode(jsonStr);
  print(json.length);
}

But suppose I compress and uuencode file.json.gz file.json.gz the JSON string above, obtaining:

begin 644 file.json.gz
M'XL("#1&\F```V9I;&4N:G-O;@"K5E!*S5.R4E!R+TI-+<G,2R_640C/+\I)
G453245!*!DL]V;O@Z=*][_?T/-DQ[?G4'D4EA5HN`+"N/9PX````
`
end

and then embed the result within the code:

import 'dart:convert' as JSON;

void main() {
  var str = """    M'XL("#1&\F```V9I;&4N:G-O;@"K5E!*S5.R4E!R+TI-+<G,2R_640C/+\I)
G453245!*!DL]V;O@Z=*][_?T/-DQ[?G4'D4EA5HN`+"N/9PX````
`""";

  ...    
}

How might I uudecode the uuencoded string in Dart?

Modernizing to base64

As jamesdlin's nicely detailed answer suggests (and notwithstanding that the answer handles uuencode), the use of base64 is superior, and it can be obtained still from uuencode using the -m switch.

> uuencode -m file.json.gz file.json.gz
begin-base64 644 file.json.gz
H4sICH6y8mAAA2ZpbGUuanNvbgCrVlBKzVOyUlByL0pNLcnMSy/WUQjPL8pJUVTSUVBKBks92bvg
6dK97/f0PNkx7fnUHkUlhVouALCuPZw4AAAA
====

In that case we can use base64Decode from dart:convert.

void main() {
  var str = """begin-base64 644 file.json.gz
H4sICH6y8mAAA2ZpbGUuanNvbgCrVlBKzVOyUlByL0pNLcnMSy/WUQjPL8pJUVTSUVBKBks92bvg
6dK97/f0PNkx7fnUHkUlhVouALCuPZw4AAAA
====""";

  ...    
}
1

There are 1 best solutions below

2
On BEST ANSWER

I whipped up a crude uudecode implementation based on Wikipedia's description for the uuencode scheme. Note that the following code is not well tested, and it just decodes the file contents; if you want to write the file to storage using the original filename and permissions, that's left as an exercise for the reader.

import 'dart:typed_data';

Uint8List uudecode(String uuencoded) {
  var lines = uuencoded.trim().split('\n');
  if (lines.length < 3) {
    throw FormatException('Missing preamble and/or postamble.');
  }

  // This ignores the header.

  // Since the input is expected to be a uuencoded string, its code units
  // should be ASCII values.
  var decodedBytes = <int>[];
  for (var line in lines.skip(1)) {
    var data = [for (var codeUnit in line.codeUnits) codeUnit - 32];
    if (data.isEmpty) {
      throw FormatException('Missing length byte.');
    }

    if ((data.length - 1) % 4 != 0) {
      throw FormatException('Encoded data length is not a multiple of 4.');
    }

    var decodedLineLength = data[0];
    if (decodedLineLength >= 64) {
      break;
    }

    // Explicitly restrict each element to 6-bits since the uuencode
    // implementation from GNU sharutils encodes NUL bytes as `.
    for (var i = 1; i < line.codeUnits.length; i += 1) {
      data[i] &= 0x3F;
    }

    var decodedLineBytes = <int>[];
    var i = 1;
    while (true) {
      // Combine the next 4 elements into a 24-bit value.
      var concatenated = 0;
      concatenated |= data[i++] << 18;
      concatenated |= data[i++] << 12;
      concatenated |= data[i++] << 6;
      concatenated |= data[i++];

      // Extract 3 bytes from the 24-bit value.
      decodedLineBytes.add((concatenated >> 16) & 0xFF);
      decodedLineBytes.add((concatenated >> 8) & 0xFF);
      decodedLineBytes.add(concatenated & 0xFF);
      if (decodedLineBytes.length >= decodedLineLength) {
        // Remove padding bytes.
        decodedBytes.addAll(decodedLineBytes.getRange(0, decodedLineLength));
        break;
      }
    }
  }
  return Uint8List.fromList(decodedBytes);
}

void main() {
  var uuencoded = r'''\
begin 644 wikipedia-url.txt
::'1T<#HO+W=W=RYW:6MI<&5D:6$N;W)G#0H`
`
end
''';
  
  var decoded = uudecode(uuencoded);
  print('${decoded.length}: $decoded');
  print('${String.fromCharCodes(decoded)}'); // Prints: http://www.wikipedia.org
}

All that said, unless you have some external requirement that forces you to use uuencode, I'd recommend using base64 instead since its encoded size is slightly smaller, it's far more common nowadays, and since the Dart SDK directly includes base64 support.