Skip to content
This repository has been archived by the owner on Dec 5, 2023. It is now read-only.

Commit

Permalink
Import more of the command formatting from Net::IMAP
Browse files Browse the repository at this point in the history
  • Loading branch information
ConradIrwin committed Jul 2, 2011
1 parent fae44bc commit 02e063e
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 0 deletions.
1 change: 1 addition & 0 deletions lib/em-imap.rb
Expand Up @@ -8,6 +8,7 @@
$:.unshift File.dirname( __FILE__ )
require 'em-imap/listener'
require 'em-imap/continuation_synchronisation'
require 'em-imap/formatter'
require 'em-imap/command_sender'
require 'em-imap/response_parser'
require 'em-imap/connection'
Expand Down
124 changes: 124 additions & 0 deletions lib/em-imap/formatter.rb
@@ -0,0 +1,124 @@
module EventMachine
module IMAP
class Formatter

# A placeholder so that the command sender knows to treat literal strings specially
class Literal < Struct.new(:str); end

# Format the data to be sent into strings and literals, and call the block
# for each token to be sent.
#
# @param data The data to format,
# @param &block The callback, which will be called with a number of strings and
# EM::IMAP::Formatter::Literal instances.
#
# NOTE: The block is responsible for handling any network-level concerns, such
# as sending literals only with permission.
#
def self.format(data, &block)
new(&block).send_data(data)
end

def initialize(&block)
@block = block
end

def put_string(str)
@block.call str
end

def send_literal(str)
@block.call Literal.new(str)
end

# The remainder of the code in this file is directly from Net::IMAP.
# Copyright (C) 2000 Shugo Maeda <shugo@ruby-lang.org>
def send_data(data)
case data
when nil
put_string("NIL")
when String
send_string_data(data)
when Integer
send_number_data(data)
when Array
send_list_data(data)
when Time
send_time_data(data)
when Symbol
send_symbol_data(data)
when EM::IMAP::Command
send_command(data)
else
data.send_data(self)
end
end

def send_command(cmd)
put_string cmd.tag
put_string " "
put_string cmd.cmd
cmd.args.each do |i|
put_string " "
send_data(i)
end
put_string "\r\n"
end

def send_string_data(str)
case str
when ""
put_string('""')
when /[\x80-\xff\r\n]/n
# literal
send_literal(str)
when /[(){ \x00-\x1f\x7f%*"\\]/n
# quoted string
send_quoted_string(str)
else
put_string(str)
end
end

def send_quoted_string(str)
put_string('"' + str.gsub(/["\\]/n, "\\\\\\&") + '"')
end

def send_number_data(num)
if num < 0 || num >= 4294967296
raise Net::IMAP::DataFormatError, num.to_s
end
put_string(num.to_s)
end

def send_list_data(list)
put_string("(")
first = true
list.each do |i|
if first
first = false
else
put_string(" ")
end
send_data(i)
end
put_string(")")
end

DATE_MONTH = %w(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)

def send_time_data(time)
t = time.dup.gmtime
s = format('"%2d-%3s-%4d %02d:%02d:%02d +0000"',
t.day, DATE_MONTH[t.month - 1], t.year,
t.hour, t.min, t.sec)
put_string(s)
end

def send_symbol_data(symbol)
put_string("\\" + symbol.to_s)
end

end
end
end
73 changes: 73 additions & 0 deletions spec/formatter_spec.rb
@@ -0,0 +1,73 @@
require 'spec_helper'
describe EM::IMAP::Formatter do

before do
@result = []
@formatter = EM::IMAP::Formatter.new do |thing|
if thing.is_a?(String) && @result.last.is_a?(String)
@result[-1] += thing
else
@result << thing
end
end

@format = lambda { |data| @result.tap{ @formatter.send_data data } }
end

it "should format nils" do
@format.call(nil).should == ["NIL"]
end

it "should format simple strings with no quotes" do
@format.call("FETCH").should == ["FETCH"]
end

it "should quote the empty string" do
@format.call("").should == ['""']
end

it "should quote strings with spaces" do
@format.call("hello world").should == ['"hello world"']
end

it "should make strings that contain newlines into literals" do
@format.call("good\nmorning").should == [EM::IMAP::Formatter::Literal.new("good\nmorning")]
end

it "should raise an error on out-of-range ints" do
lambda{ @format.call(2 ** 64) }.should raise_error Net::IMAP::DataFormatError
lambda{ @format.call(-1) }.should raise_error Net::IMAP::DataFormatError
end

it "should be able to format in-range ints" do
@format.call(123).should == ['123']
end

it "should format dates with a leading space" do
@format.call(Time.gm(2011, 1, 1, 10, 10, 10)).should == ['" 1-Jan-2011 10:10:10 +0000"']
end

it "should format times in the 24 hour clock" do
@format.call(Time.gm(2011, 10, 10, 19, 10, 10)).should == ['"10-Oct-2011 19:10:10 +0000"']
end

it "should format lists correctly" do
@format.call([1,"",nil, "three"]).should == ['(1 "" NIL three)']
end

it "should allow for literals within lists" do
@format.call(["oh yes", "oh\nno"]).should == ['("oh yes" ', EM::IMAP::Formatter::Literal.new("oh\nno"), ')']
end

it "should format symbols correctly" do
@format.call(:hi).should == ["\\hi"]
end

it "should format commands correctly" do
@format.call(EM::IMAP::Command.new('RUBY0001', 'SELECT', ['Inbox'])).should == ["RUBY0001 SELECT Inbox\r\n"]
end

it "should format complex commands correctly" do
@format.call(EM::IMAP::Command.new('RUBY1234', 'FETCH', [[Net::IMAP::MessageSet.new([1,2,3])], 'BODY'])).should == ["RUBY1234 FETCH (1,2,3) BODY\r\n"]
end
end

0 comments on commit 02e063e

Please sign in to comment.