private static string chunksToString(List <chunk> chunks) { var result = new StringBuilder(); int lineLength = 0; foreach (var chunk in chunks) { // RFC 2047: An 'encoded-word' may not be more than 75 characters long. Each line of a header field that contains one or more 'encoded-word's is limited to 76 characters. if (chunk.Text != null) { result.Append(chunk.Text); lineLength += chunk.Text.Length; while (lineLength > 76) { wrap(result, ref lineLength, 76); } } else // chunk.Base64 != null { // base-64 chunks must be surrounded by "=?utf-8?B?" and followed by "?=". It can be split anywhere except across a UTF-8 sequence. // The minimum space occupied by an encoded-word is 16 characters (because the base64 has to be padded, so min. 4 chars) // This min. length (16 chars) is also guaranteed to be achievable because even the longest unicode sequences can be encoded in four base64 characters. int pos = 0; while (pos < chunk.Base64.Length) { // The first time round is special, because it's the only time when we might wrap _before_ adding a chunk if (pos == 0 && lineLength > 76 - 16) { wrap(result, ref lineLength, 76); } // Figure out how many bytes we can add. Every 3 bytes take 4 characters. int bytes = Math.Min(chunk.Base64.Length - pos, (76 - lineLength - 12) / 4 * 3); Ut.Assert(bytes > 0); // Now move backwards until we hit a utf8 boundary while (pos + bytes < chunk.Base64.Length && chunk.Base64[pos + bytes] >= 0x80 && chunk.Base64[pos + bytes] < 0xC0) { bytes--; } Ut.Assert(bytes > 0); // Add them! lineLength -= result.Length; // coupled with the next change, this increments lineLength by how many chars we're about to append. result.Append("=?UTF-8?B?"); result.Append(Convert.ToBase64String(chunk.Base64, pos, bytes)); result.Append("?="); lineLength += result.Length; pos += bytes; // If we have any bytes left, we must wrap, because that's the only reason we would ever not append everything if (pos < chunk.Base64.Length) { result.Append("\r\n "); lineLength = 1; } } } } return(result.ToString()); }
public byte[] GetEntireOutput() { Ut.Assert(_captureEntire); var result = new byte[_chunks.Sum(ch => ch.Length)]; int offset = 0; foreach (var chunk in _chunks) { Buffer.BlockCopy(chunk.Data, 0, result, offset, chunk.Length); offset += chunk.Length; } return(result); }
private void readComplete(IAsyncResult result, Stream stream) { int bytesRead = stream.EndRead(result); var chunk = _chunks[_chunks.Count - 1]; chunk.Length += bytesRead; if (_dataEvent != null) { var newBytes = new byte[bytesRead]; Array.Copy(chunk.Data, chunk.Length - bytesRead, newBytes, 0, bytesRead); _dataEvent(newBytes); } if (_textEvent != null) { var newChars = new char[bytesRead + 1]; // can have at most one char buffered up in the decoder, plus bytesRead new chars int bytesUsed, charsUsed; bool completed; _decoder.Convert(chunk.Data, chunk.Length - bytesRead, bytesRead, newChars, 0, newChars.Length, false, out bytesUsed, out charsUsed, out completed); Ut.Assert(completed); // it could still be halfway through a character; what this means is that newChars was large enough to accommodate everything _textEvent(new string(newChars, 0, charsUsed)); } if (bytesRead > 0) // continue reading { ReadBegin(stream); } else { Ended = true; if (_textEvent != null) { var newChars = new char[1]; int bytesUsed, charsUsed; bool completed; _decoder.Convert(new byte[0], 0, 0, newChars, 0, newChars.Length, true, out bytesUsed, out charsUsed, out completed); Ut.Assert(completed); if (charsUsed > 0) { _textEvent(new string(newChars, 0, charsUsed)); } } } }
private void processExited(object sender, EventArgs e) { Ut.Assert(_process.HasExited); while (!_stdoutReader.Ended || !_stderrReader.Ended) { Thread.Sleep(20); } Ut.Assert(_stdoutReader.Ended); Ut.Assert(_stderrReader.Ended); if (_captureEntireStdout) { _entireStdout = _stdoutReader.GetEntireOutput(); } if (_captureEntireStderr) { _entireStderr = _stderrReader.GetEntireOutput(); } lock (_lock) { _pauseTimerDue = null; if (_pauseTimer != null) { _pauseTimer.Dispose(); } } _exitCode = _process.ExitCode; if (State != CommandRunnerState.Aborted) { State = CommandRunnerState.Exited; } _startInfo = null; _process.Exited -= processExited; _process = null; if (CommandEnded != null) { CommandEnded(); } _ended.Set(); }
private static void wrap(StringBuilder text, ref int lineLength, int maxLineLength) { Ut.Assert(text.Length > 0); // This method MAY be called with a lineLength less than the max line length. // It MUST add a line wrap, ideally before the max line length, but if not possible then after the max length, possibly even at the very end of the text. // If the line already fits within max line length, just insert a newline at the very end and be done if (lineLength <= maxLineLength) { lineLength = 1; text.Append("\r\n "); return; } // First search back from the max line length position for a wrappable location int pos = text.Length - lineLength + maxLineLength; // "pos < text.Length" is guaranteed by the test above while (true) { if (pos < (text.Length - lineLength) || text[pos] == '\n') // exit if we've reached the start of the current line { pos = -1; break; } if (text[pos] == ' ') { break; } pos--; } // If that worked, make sure it isn't all spaces until the start of the line if (pos >= 0) { int pos2 = pos; while (true) { pos2--; if (pos2 < (text.Length - lineLength) || text[pos2] == '\n') { pos = -1; // found nothing but spaces until the start of the line break; } if (text[pos2] != ' ') { break; // found a non-space, so OK to wrap at "pos" } } } // If that failed, seek forward if (pos < 0) { pos = text.Length - lineLength + maxLineLength + 1; while (true) { if (pos >= text.Length) { break; } if (text[pos] == ' ') { break; } pos++; } } // Insert \r\n at "pos", which either points at the space that becomes the (mandatory) indent for the next line, or is at the very end of the line if (pos >= text.Length) { lineLength = 1; text.Append("\r\n "); } else { lineLength = text.Length - pos; text.Insert(pos, "\r\n"); } }