ImapClient.psm1

using module .\STcpClient.psm1
using module .\Tokens.psm1
using module .\Result.psm1
using module .\Utility.psm1

class ImapClient {
  [int]$tag = 0
  $client #[STcpClient]
  $utility #[Utility]

  ImapClient($client) {
    $this.client = $client
    $this.utility = Get-Utility
  }

  [void]Connect() {
    $this.client.Connect()
  }

  [void]Close() {
    $this.client.Close()
  }

  [System.Object]ExecuteCommand([string]$cmd) {
    $this.tag++
    $cmdText = [string]::Format("{0} {1}", $this.getTagText(), $cmd)
    return $this.executeInternal($cmdText)
  }

  [System.Object]SaveEmail([string]$folder, [string]$content) {
    $this.tag++
    $cmdText = $this.getTagText() + " APPEND $folder {" + $content.Length + "}"
    $this.client.SubmitRequest($cmdText)
    $line = $this.client.ReadResponse($true)
    if ($line -and $line.StartsWith("+ Ready for additional command text.")) {
      return $this.executeInternal($content)
    }
    return Get-Result -Success $false -Payload $line -ErrorMessage 'IMAP should return "+ Ready for additional command text." for append command.'
  }

  [System.Object]O365Authenticate([string]$accessToken, [string]$upn) {
    $token = $this.utility.BuildO365Token($accessToken, $upn)
    return $this.xOauth2Authenticate($token)
  }

  [System.Object]Login([string]$user, [string]$pass) {
    return $this.ExecuteCommand("login $user $pass")
  }

  [System.Object]Login([string]$user, [string]$targetMailbox, [string]$pass) {
    return $this.ExecuteCommand("login $user\$targetMailbox $pass")
  }

  [System.Object]executeInternal($cmd) {
    $this.client.SubmitRequest($cmd, $this.redact($cmd))
    $tagText = $this.getTagText()
    $sb = [System.Text.StringBuilder]::new()
    $first = $true
    do {
      if ($first) {
        $first = $false
      }
      else {
        $sb.AppendLine()
      }        
      $line = $this.client.ReadResponse($true)
      $sb.Append($line)
    }
    while(!$line.StartsWith($tagText + " "))
    $parts = $line.Split(" ")
    $success = $parts.Length -gt 1 -and $parts[1] -eq "OK"
    $errorMessage = ""
    if (!$success) {
      $errorMessage = 'IMAP should return "OK" for successful command handling.'
    }
    return Get-Result -Success $success -Payload $sb.ToString() -ErrorMessage $errorMessage
  }

  [System.Object]xOauth2Authenticate([string]$oAuthToken) {
    $cmd = "AUTHENTICATE XOAUTH2 $oAuthToken"
      return $this.ExecuteCommand($cmd)
  }

  [string]redact([string]$text) {
    $parts = $text.Split(" ")
    if ($parts.Length -ge 4 -and $parts[1] -eq "login")
    {
      $parts[3] = "****"
      return [string]::Join(" ", $parts[0..3])
    }
    return $text
  }

  [string]getTagText() {
    return $this.tag.ToString("D4")
  }
}

function Get-ImapClient($TcpClient) {
  <#
    .SYNOPSIS
    Get a ImapClient for IMAP connection.
 
    .DESCRIPTION
    This method returns a Imap Client object which can be used to read/write data through a IMAP connection.
       
    .PARAMETER TcpClient
    The underlying TcpClient that is returned by Get-TcpClient command.
 
    .INPUTS
    None. You cannot pipe objects to Get-ImapClient
 
    .OUTPUTS
    ImapClient object.
 
    .EXAMPLE
    PS>$tcpClient = Get-TcpClient -Server outlook.office365.com -Port 993 -Logger $logger
    PS>$imapClient = Get-ImapClient -TcpClient $tcpClient
  #>

  return [ImapClient]::new($TcpClient)
}