commit 7c18aba4d4e6156056206afb77dea19d1b442a61
parent 36c79336bbd1c3bb7df1efb80bb6415aa54b6e4e
Author: Gaute Hope <eg@gaute.vetsj.com>
Date: Thu, 12 Feb 2015 19:59:00 +0100
Merge branch 'develop'
Diffstat:
43 files changed, 609 insertions(+), 216 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -16,4 +16,7 @@ Gemfile.lock
# generated file for gnupg test
test/gnupg_test_home/random_seed
test/gnupg_test_home/trustdb.gpg
+test/gnupg_test_home/.gpg-v21-migrated
+test/gnupg_test_home/private-keys-v1.d
+
diff --git a/.travis.yml b/.travis.yml
@@ -3,7 +3,7 @@ language: ruby
rvm:
- 2.1.1
- 2.0.0
- - 1.9.3
+ - 2.2.0
before_install:
- sudo apt-get update -qq
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
@@ -9,23 +9,25 @@ Eric Weikl <eric.weikl at the gmx dot nets>
Paweł Wilk <siefca at the gnu dot orgs>
Ismo Puustinen <ismo at the iki dot fis>
Nicolas Pouillard <nicolas.pouillard at the gmail dot coms>
+Matthieu Rakotojaona <matthieu.rakotojaona at the gmail dot coms>
Michael Stapelberg <michael at the stapelberg dot des>
Eric Sherman <hyperbolist at the gmail dot coms>
+Zeger-Jan van de Weg <mail at the zjvandeweg dot nls>
Tero Tilus <tero at the tilus dot nets>
Ben Walton <bwalton at the artsci.utoronto dot cas>
Scott Bonds <scott at the ggr dot coms>
Mike Stipicevic <stipim at the rpi dot edus>
Martin Bähr <mbaehr at the societyserver dot orgs>
-Matthieu Rakotojaona <matthieu.rakotojaona at the gmail dot coms>
+Timon Vonk <timonv at the gmail dot coms>
Clint Byrum <clint at the ubuntu dot coms>
Wael M. Nasreddine <wael.nasreddine at the gmail dot coms>
Marcus Williams <marcus-sup at the bar-coded dot nets>
Lionel Ott <white.magic at the gmx dot des>
Gaudenz Steinlin <gaudenz at the soziologie dot chs>
+Per Andersson <avtobiff at the gmail dot coms>
Ingmar Vanhassel <ingmar at the exherbo dot orgs>
Mark Alexander <marka at the pobox dot coms>
Edward Z. Yang <ezyang at the mit dot edus>
-Timon Vonk <timonv at the gmail dot coms>
julien@macbook <julien.stechele at the gmail dot coms>
Christopher Warrington <chrisw at the rice dot edus>
W. Trevor King <wking at the drexel dot edus>
@@ -38,49 +40,50 @@ Markus Klinik <mkl at the lambdanaut dot nets>
Bo Borgerson <gigabo at the gmail dot coms>
Atte Kojo <atte.kojo at the reaktor dot fis>
Michael Hamann <michael at the content-space dot des>
-Jonathan Lassoff <jof at the thejof dot coms>
William Erik Baxter <web at the superscript dot coms>
+Jonathan Lassoff <jof at the thejof dot coms>
Grant Hollingworth <grant at the antiflux dot orgs>
Ico Doornekamp <ico at the pruts dot nls>
Adeodato Simó <dato at the net.com.org dot ess>
Daniel Schoepe <daniel.schoepe at the googlemail dot coms>
-James Taylor <james at the jamestaylor dot orgs>
Jason Petsod <jason at the petsod dot orgs>
+James Taylor <james at the jamestaylor dot orgs>
Steve Goldman <sgoldman at the tower-research dot coms>
Robin Burchell <viroteck at the viroteck dot nets>
Peter Harkins <ph at the malaprop dot orgs>
Decklin Foster <decklin at the red-bean dot coms>
+rjg-vB <rthrd at the web dot des>
Cameron Matheson <cam+sup at the cammunism dot orgs>
-Carl Worth <cworth at the cworth dot orgs>
Alex Vandiver <alex at the chmrr dot nets>
+Carl Worth <cworth at the cworth dot orgs>
Jeff Balogh <its.jeff.balogh at the gmail dot coms>
Andrew Pimlott <andrew at the pimlott dot nets>
Matías Aguirre <matiasaguirre at the gmail dot coms>
PaulSmecker <paul.smecker at the gmail dot coms>
-Per Andersson <avtobiff at the gmail dot coms>
Ruthard Baudach <rthrd at the web dot des>
Kornilios Kourtis <kkourt at the cslab.ece.ntua dot grs>
Lars Fischer <fischer at the wiwi.uni-siegen dot des>
madhat2r <MaDhAt2r at the dukefoo dot coms>
-Giorgio Lando <patroclo7 at the gmail dot coms>
Kevin Riggle <kevinr at the free-dissociation dot coms>
+Giorgio Lando <patroclo7 at the gmail dot coms>
Benoît PIERRE <benoit.pierre at the gmail dot coms>
Alvaro Herrera <alvherre at the alvh.no-ip dot orgs>
Steven Lawrance <stl at the koffein dot nets>
Jonah <Jonah at the GoodCoffee dot cas>
ian <itaylor at the uark dot edus>
-Adam Lloyd <adam at the alloy-d dot nets>
Todd Eisenberger <teisenbe at the andrew.cmu dot edus>
-0xACE <0xACE at the users.noreply.github dot coms>
MichaelRevell <mikearevell at the gmail dot coms>
+Adam Lloyd <adam at the alloy-d dot nets>
+0xACE <0xACE at the users.noreply.github dot coms>
Gregor Hoffleit <gregor at the sam.mediasupervision dot des>
-Steven Schmeiser <steven at the schmeiser dot orgs>
+Sharif Olorin <sio at the tesser dot orgs>
Steven Walter <swalter at the monarch.(none)>
-Jon M. Dugan <jdugan at the es dot nets>
-Horacio Sanson <horacio at the skillupjapan.co dot jps>
+Steven Schmeiser <steven at the schmeiser dot orgs>
Stefan Lundström <lundst at the snabb.(none)>
William A. Kennington III <william at the wkennington dot coms>
akojo <atte.kojo at the gmail dot coms>
+Horacio Sanson <horacio at the skillupjapan.co dot jps>
+Jon M. Dugan <jdugan at the es dot nets>
Matthias Vallentin <vallentin at the icir dot orgs>
Johannes Larsen <johs.a.larsen at the gmail dot coms>
Kirill Smelkov <kirr at the landau.phys.spbu dot rus>
diff --git a/History.txt b/History.txt
@@ -1,3 +1,19 @@
+== 0.21.0 / 2015-02-12
+
+* Key binding to fetch GPG key from keyserver (Matthieu Rakotojaona)
+* Replace occurences of File.exists? with File.exist? (Zeger-Jan van de
+ Weg)
+* You can now unsubscribe from mailinglists using an url, if you have a
+ goto-hook setup (Timon Vonk).
+* Forward attribution can be customized using the forward-attribution
+ hook (Ruthard Baudach)
+* Do a few more checks for buffer not nil in the hope to fix a few
+ random crashes
+* Add bash completion (Per Andersson)
+* Replace dl/import with Fiddle (Timon Vonk)
+* Drop support for ruby 1.9.3
+* Add tests for contact manager and persons (Zeger-Jan van de Weg)
+
== 0.20.0 / 2014-10-06
* add man-pages (generated from wiki) (Per Andersson)!
diff --git a/ReleaseNotes b/ReleaseNotes
@@ -1,3 +1,10 @@
+Release 0.21.0:
+
+Several small features as well as polishing (including fetching a GPG key with
+a shortcut and unsubscribing from mailinglist using an url). Several old
+deprecated parts of sup have been modernized. Support for Ruby 1.9.3 has been
+dropped. Have a look in History.txt for the details.
+
Release 0.20.0:
We've got man pages (Mr. Andersson)! We've got OpenBSD support (Scott Bonds)!
diff --git a/bin/sup b/bin/sup
@@ -7,6 +7,7 @@ require 'rubygems'
require 'ncursesw'
require 'sup/util/ncurses'
+require 'sup/util/locale_fiddler'
no_gpgme = false
begin
@@ -102,32 +103,17 @@ global_keymap = Keymap.new do |k|
end
end
-## the following magic enables wide characters when used with a ruby
-## ncurses.so that's been compiled against libncursesw. (note the w.) why
-## this works, i have no idea. much like pretty much every aspect of
-## dealing with curses. cargo cult programming at its best.
require 'rbconfig'
-unless RbConfig::CONFIG['arch'] =~ /openbsd/
- require 'dl/import'
- module LibC
- extend DL.const_defined?(:Importer) ? DL::Importer : DL::Importable
- setlocale_lib = case RbConfig::CONFIG['arch']
- when /darwin/; "libc.dylib"
- when /cygwin/; "cygwin1.dll"
- when /freebsd/; "libc.so.7"
- else; "libc.so.6"
- end
- debug "dynamically loading setlocale() from #{setlocale_lib}"
- begin
- dlload setlocale_lib
- extern "void setlocale(int, const char *)"
- debug "setting locale..."
- LibC.setlocale(6, "") # LC_ALL == 6
- rescue RuntimeError => e
- warn "cannot dlload setlocale(); ncurses wide character support probably broken."
- warn "dlload error was #{e.class}: #{e.message}"
- end
+unless RbConfig::CONFIG['arch'] =~ /openbsd/
+ debug "dynamically loading setlocale()"
+ begin
+ class LibC; extend LocaleFiddler; end
+ debug "setting locale..."
+ LibC.setlocale(6, "")
+ rescue RuntimeError => e
+ warn "cannot dlload setlocale(); ncurses wide character support probably broken."
+ warn "dlload error was #{e.class}: #{e.message}"
end
end
diff --git a/bin/sup-sync-back-maildir b/bin/sup-sync-back-maildir
@@ -60,7 +60,7 @@ $config[:sync_back_to_maildir] = true
begin
sync_performed = []
- sync_performed = File.readlines(Redwood::SYNC_OK_FN).collect { |e| e.strip }.find_all { |e| not e.empty? } if File.exists? Redwood::SYNC_OK_FN
+ sync_performed = File.readlines(Redwood::SYNC_OK_FN).collect { |e| e.strip }.find_all { |e| not e.empty? } if File.exist? Redwood::SYNC_OK_FN
sources = []
## Try to find out sources given in parameters
diff --git a/contrib/completion/_sup.bash b/contrib/completion/_sup.bash
@@ -0,0 +1,102 @@
+# Sup Bash completion
+#
+# * Complete options for all Sup commands.
+# * Disable completion for next option when current option takes an argument.
+# * Complete sources, directories, and files, where applicable.
+
+_sup_cmds() {
+ local cur prev opts sources
+ COMPREPLY=()
+ cur="${COMP_WORDS[COMP_CWORD]}"
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
+ sources="$(sed -n '/uri:/ {s/.*uri:\s*//p}' $HOME/.sup/sources.yaml)"
+
+ case "${1##/*}" in
+ sup-add)
+ opts="--archive -a --unusual -u --sync-back --no-sync-back -s
+ --labels -l --force-new -f --force-account -o --version -v
+ --help -h mbox: maildir:"
+
+ case $prev in
+ --labels|-l|--force-account|-o)
+ COMPREPLY=()
+ return 0
+ ;;
+ esac
+ ;;
+ sup-config|sup-dump)
+ opts="--version -v --help -h"
+ ;;
+ sup-import-dump)
+ opts="--verbose -v --ignore-missing -i --warn-missing -w
+ --abort-missing -a --atomic -t --dry-run -n --version --help
+ -h"
+ ;;
+ sup)
+ opts="--list-hooks -l --no-threads -n --no-initial-poll -o --search
+ -s --compose -c --subject -j --version -v --help -h"
+
+ case $prev in
+ --search|-s|--compose|-c|--subject|-j)
+ COMPREPLY=()
+ return 0
+ ;;
+ esac
+ ;;
+ sup-recover-sources)
+ opts="--unusual --archive --scan-num --help -h $sources"
+
+ case $prev in
+ --scan-num)
+ COMPREPLY=()
+ return 0
+ ;;
+ esac
+ ;;
+ sup-sync)
+ opts="--asis --restore --discard --archive -x --read -r
+ --extra-labels --verbose -v --optimize -o --all-sources
+ --dry-run -n --version --help -h ${sources}"
+
+
+ case $prev in
+ --restore|--extra-labels)
+ COMPREPLY=()
+ return 0
+ ;;
+ esac
+ ;;
+ sup-sync-back-maildir)
+ maildir_sources="$(echo $sources | tr ' ' '\n' | grep maildir)"
+ opts="--no-confirm -n --no-merge -m --list-sources -l
+ --unusual-sources-too -u --version -v --help -h
+ $maildir_sources"
+ ;;
+ sup-tweak-labels)
+ opts="--add -a --remove -r --query -q --verbose -v --very-verbose
+ -e --all-sources --dry-run -n --no-sync-back -o --version
+ --help -h $sources"
+
+ case $prev in
+ --add|-a|--remove|-r|--query|-q)
+ COMPREPLY=()
+ return 0
+ ;;
+ esac
+ ;;
+ esac
+
+ COMPREPLY=( $(compgen -W "$opts" -- ${cur}) )
+ return 0
+}
+
+complete -F _sup_cmds sup \
+ sup-add \
+ sup-config \
+ sup-dump \
+ sup-recover-sources \
+ sup-sync \
+ sup-sync-back-maildir \
+ sup-tweak-labels
+
+complete -F _sup_cmds -o filenames -o plusdirs sup-import-dump
diff --git a/lib/sup.rb b/lib/sup.rb
@@ -105,7 +105,7 @@ module Redwood
o
end
- mode = if File.exists? fn
+ mode = if File.exist? fn
File.stat(fn).mode
else
0600
@@ -113,7 +113,7 @@ module Redwood
if backup
backup_fn = fn + '.bak'
- if File.exists?(fn) && File.size(fn) > 0
+ if File.exist?(fn) && File.size(fn) > 0
File.open(backup_fn, "w", mode) do |f|
File.open(fn, "r") { |old_f| FileUtils.copy_stream old_f, f }
f.fsync
@@ -139,7 +139,7 @@ module Redwood
end
def load_yaml_obj fn, compress=false
- o = if File.exists? fn
+ o = if File.exist? fn
if compress
Zlib::GzipReader.open(fn) { |f| YAML::load f }
else
@@ -180,7 +180,7 @@ module Redwood
return if bypass_sync_check
if $config[:sync_back_to_maildir]
- if not File.exists? Redwood::SYNC_OK_FN
+ if not File.exist? Redwood::SYNC_OK_FN
Redwood.warn_syncback <<EOS
It appears that the "sync_back_to_maildir" option has been changed
from false to true since the last execution of sup.
@@ -191,14 +191,14 @@ Should I complain about this again? (Y/n)
EOS
File.open(Redwood::SYNC_OK_FN, 'w') {|f| f.write(Redwood::MAILDIR_SYNC_CHECK_SKIPPED) } if STDIN.gets.chomp.downcase == 'n'
end
- elsif not $config[:sync_back_to_maildir] and File.exists? Redwood::SYNC_OK_FN
+ elsif not $config[:sync_back_to_maildir] and File.exist? Redwood::SYNC_OK_FN
File.delete(Redwood::SYNC_OK_FN)
end
end
def check_syncback_settings
# don't check if syncback was never performed
- return unless File.exists? Redwood::SYNC_OK_FN
+ return unless File.exist? Redwood::SYNC_OK_FN
active_sync_sources = File.readlines(Redwood::SYNC_OK_FN).collect { |e| e.strip }.find_all { |e| not e.empty? }
return if active_sync_sources.length == 1 and active_sync_sources[0] == Redwood::MAILDIR_SYNC_CHECK_SKIPPED
sources = SourceManager.sources
@@ -338,7 +338,7 @@ EOM
:continuous_scroll => false,
:always_edit_async => false,
}
- if File.exists? filename
+ if File.exist? filename
config = Redwood::load_yaml_obj filename
abort "#{filename} is not a valid configuration file (it's a #{config.class}, not a hash)" unless config.is_a?(Hash)
default_config.merge config
diff --git a/lib/sup/colormap.rb b/lib/sup/colormap.rb
@@ -17,6 +17,9 @@ module Ncurses
## xterm 24-shade grayscale
24.times { |x| color! "g#{x}", (16+6*6*6) + x }
+ elsif Ncurses::NUM_COLORS == -1
+ ## Terminal emulator doesn't appear to support colors
+ fail "sup must be run in a terminal with color support, please check your TERM variable."
end
end
@@ -186,13 +189,13 @@ class Colormap
## Try to use the user defined colors, in case of an error fall back
## to the default ones.
def populate_colormap
- user_colors = if File.exists? Redwood::COLOR_FN
+ user_colors = if File.exist? Redwood::COLOR_FN
debug "loading user colors from #{Redwood::COLOR_FN}"
Redwood::load_yaml_obj Redwood::COLOR_FN
end
## Set attachment sybmol to sane default for existing colorschemes
- if user_colors and user_colors.has_key? :to_me
+ if user_colors and user_colors.has_key? :to_me
user_colors[:with_attachment] = user_colors[:to_me] unless user_colors.has_key? :with_attachment
end
diff --git a/lib/sup/contact.rb b/lib/sup/contact.rb
@@ -16,7 +16,7 @@ class ContactManager
@a2p = {} # alias to person
@e2p = {} # email to person
- if File.exists? fn
+ if File.exist? fn
IO.foreach(fn) do |l|
l =~ /^([^:]*): (.*)$/ or raise "can't parse #{fn} line #{l.inspect}"
aalias, addr = $1, $2
@@ -29,11 +29,13 @@ class ContactManager
def contacts_with_aliases; @a2p.values.uniq end
def update_alias person, aalias=nil
+ ## Deleting old data if it exists
old_aalias = @p2a[person]
- if(old_aalias != nil and old_aalias != "") # remove old alias
+ if old_aalias
@a2p.delete old_aalias
@e2p.delete person.email
end
+ ## Update with new data
@p2a[person] = aalias
unless aalias.nil? || aalias.empty?
@a2p[aalias] = person
diff --git a/lib/sup/crypto.rb b/lib/sup/crypto.rb
@@ -16,6 +16,9 @@ class CryptoManager
[:encrypt, "Encrypt only"]
)
+ KEY_PATTERN = /(-----BEGIN PGP PUBLIC KEY BLOCK.*-----END PGP PUBLIC KEY BLOCK)/m
+ KEYSERVER_URL = "http://pool.sks-keyservers.net:11371/pks/lookup"
+
HookManager.register "gpg-options", <<EOS
Runs before gpg is called, allowing you to modify the options (most
likely you would want to add something to certain commands, like
@@ -212,9 +215,10 @@ EOS
unknown = false
all_output_lines = []
all_trusted = true
+ unknown_fingerprint = nil
verify_result.signatures.each do |signature|
- output_lines, trusted = sig_output_lines signature
+ output_lines, trusted, unknown_fingerprint = sig_output_lines signature
all_output_lines << output_lines
all_output_lines.flatten!
all_trusted &&= trusted
@@ -242,6 +246,8 @@ EOS
end
elsif !unknown
Chunk::CryptoNotice.new(:invalid, summary_line, all_output_lines)
+ elsif unknown_fingerprint
+ Chunk::CryptoNotice.new(:unknown_key, "Unable to determine validity of cryptographic signature", all_output_lines, unknown_fingerprint)
else
unknown_status all_output_lines
end
@@ -351,6 +357,31 @@ EOS
[notice, sig, msg]
end
+ def retrieve fingerprint
+ require 'net/http'
+ uri = URI($config[:keyserver_url] || KEYSERVER_URL)
+ unless uri.scheme == "http" and not uri.host.nil?
+ return "Invalid url: #{uri}"
+ end
+
+ fingerprint = "0x" + fingerprint unless fingerprint[0..1] == "0x"
+ params = {op: "get", search: fingerprint}
+ uri.query = URI.encode_www_form(params)
+
+ begin
+ res = Net::HTTP.get_response(uri)
+ rescue SocketError # Host doesn't exist or we couldn't connect
+ end
+ return "Couldn't get key from keyserver at this address: #{uri}" unless res.is_a?(Net::HTTPSuccess)
+
+ match = KEY_PATTERN.match(res.body)
+ return "No key found" unless match && match.length > 0
+
+ GPGME::Key.import(match[0])
+
+ return nil
+ end
+
private
def unknown_status lines=[]
@@ -394,6 +425,7 @@ private
rescue EOFError
from_key = nil
first_sig = "No public key available for #{signature.fingerprint}"
+ unknown_fpr = signature.fingerprint
end
time_line = "Signature made " + signature.timestamp.strftime("%a %d %b %Y %H:%M:%S %Z") +
@@ -422,7 +454,7 @@ private
output_lines << HookManager.run("sig-output",
{:signature => signature, :from_key => from_key})
end
- return output_lines, trusted
+ return output_lines, trusted, unknown_fpr
end
def key_type key, fpr
diff --git a/lib/sup/draft.rb b/lib/sup/draft.rb
@@ -16,7 +16,7 @@ class DraftManager
def write_draft
offset = @source.gen_offset
fn = @source.fn_for_offset offset
- File.open(fn, "w") { |f| yield f }
+ File.open(fn, "w:UTF-8") { |f| yield f }
PollManager.poll_from @source
end
@@ -33,7 +33,7 @@ class DraftLoader < Source
yaml_properties
def initialize dir=Redwood::DRAFT_DIR
- Dir.mkdir dir unless File.exists? dir
+ Dir.mkdir dir unless File.exist? dir
super DraftManager.source_name, true, false
@dir = dir
@cur_offset = 0
@@ -62,7 +62,7 @@ class DraftLoader < Source
def gen_offset
i = 0
- while File.exists? fn_for_offset(i)
+ while File.exist? fn_for_offset(i)
i += 1
end
i
@@ -75,7 +75,7 @@ class DraftLoader < Source
end
def load_message offset
- raise SourceError, "Draft not found" unless File.exists? fn_for_offset(offset)
+ raise SourceError, "Draft not found" unless File.exist? fn_for_offset(offset)
File.open fn_for_offset(offset) do |f|
RMail::Mailbox::MBoxReader.new(f).each_message do |input|
return RMail::Parser.read(input)
@@ -85,7 +85,7 @@ class DraftLoader < Source
def raw_header offset
ret = ""
- File.open fn_for_offset(offset) do |f|
+ File.open(fn_for_offset(offset), "r:UTF-8") do |f|
until f.eof? || (l = f.gets) =~ /^$/
ret += l
end
@@ -94,13 +94,13 @@ class DraftLoader < Source
end
def each_raw_message_line offset
- File.open(fn_for_offset(offset)) do |f|
+ File.open(fn_for_offset(offset), "r:UTF-8") do |f|
yield f.gets until f.eof?
end
end
def raw_message offset
- IO.read(fn_for_offset(offset))
+ IO.read(fn_for_offset(offset), :encoding => "UTF-8")
end
def start_offset; 0; end
diff --git a/lib/sup/hook.rb b/lib/sup/hook.rb
@@ -83,7 +83,7 @@ class HookManager
@contexts = {}
@tags = {}
- Dir.mkdir dir unless File.exists? dir
+ Dir.mkdir dir unless File.exist? dir
end
attr_reader :tags
diff --git a/lib/sup/index.rb b/lib/sup/index.rb
@@ -105,7 +105,7 @@ EOS
def save
debug "saving index and sources..."
- FileUtils.mkdir_p @dir unless File.exists? @dir
+ FileUtils.mkdir_p @dir unless File.exist? @dir
SourceManager.save_sources
save_index
end
@@ -116,7 +116,7 @@ EOS
def load_index failsafe=false
path = File.join(@dir, 'xapian')
- if File.exists? path
+ if File.exist? path
@xapian = Xapian::WritableDatabase.new(path, Xapian::DB_OPEN)
db_version = @xapian.get_metadata 'version'
db_version = '0' if db_version.empty?
diff --git a/lib/sup/label.rb b/lib/sup/label.rb
@@ -15,7 +15,7 @@ class LabelManager
def initialize fn
@fn = fn
labels =
- if File.exists? fn
+ if File.exist? fn
IO.readlines(fn).map { |x| x.chomp.intern }
else
[]
diff --git a/lib/sup/maildir.rb b/lib/sup/maildir.rb
@@ -68,7 +68,7 @@ class Maildir < Source
File.safe_link tmp_path, new_path
stored = true
ensure
- File.unlink tmp_path if File.exists? tmp_path
+ File.unlink tmp_path if File.exist? tmp_path
end
end #rescue Errno...
end #Dir.chdir
@@ -201,7 +201,7 @@ class Maildir < Source
def trashed? id; maildir_data(id)[2].include? "T"; end
def valid? id
- File.exists? File.join(@dir, id)
+ File.exist? File.join(@dir, id)
end
private
diff --git a/lib/sup/mbox.rb b/lib/sup/mbox.rb
@@ -115,7 +115,7 @@ class MBox < Source
end
def store_message date, from_email, &block
- need_blank = File.exists?(@path) && !File.zero?(@path)
+ need_blank = File.exist?(@path) && !File.zero?(@path)
File.open(@path, "ab") do |f|
f.puts if need_blank
f.puts "From #{from_email} #{date.asctime}"
@@ -180,7 +180,7 @@ class MBox < Source
time = $1
begin
## hack -- make Time.parse fail when trying to substitute values from Time.now
- Time.parse time, 0
+ Time.parse time, Time.at(0)
true
rescue NoMethodError, ArgumentError
warn "found invalid date in potential mbox split line, not splitting: #{l.inspect}"
diff --git a/lib/sup/message.rb b/lib/sup/message.rb
@@ -279,6 +279,12 @@ class Message
end
end
+ def reload_from_source!
+ @chunks = nil
+ load_from_source!
+ end
+
+
def error_message
<<EOS
#@snippet...
diff --git a/lib/sup/message_chunks.rb b/lib/sup/message_chunks.rb
@@ -159,6 +159,7 @@ EOS
"Attachment: #{filename} (#{content_type}; #{@raw_content.size.to_human_size})"
end
end
+ def safe_filename; Shellwords.escape(@filename).gsub("/", "_") end
## an attachment is exapndable if we've managed to decode it into
## something we can display inline. otherwise, it's viewable.
@@ -306,12 +307,13 @@ EOS
end
class CryptoNotice
- attr_reader :lines, :status, :patina_text
+ attr_reader :lines, :status, :patina_text, :unknown_fingerprint
- def initialize status, description, lines=[]
+ def initialize status, description, lines=[], unknown_fingerprint=nil
@status = status
@patina_text = description
@lines = lines
+ @unknown_fingerprint = unknown_fingerprint
end
def patina_color
diff --git a/lib/sup/mode.rb b/lib/sup/mode.rb
@@ -83,7 +83,7 @@ EOS
### helper functions
def save_to_file fn, talk=true
- if File.exists? fn
+ if File.exist? fn
unless BufferManager.ask_yes_or_no "File \"#{fn}\" exists. Overwrite?"
info "Not overwriting #{fn}"
return
@@ -102,37 +102,42 @@ EOS
end
def pipe_to_process command
- Open3.popen3(command) do |input, output, error|
- err, data, * = IO.select [error], [input], nil
-
- unless err.empty?
- message = err.first.read
- if message =~ /^\s*$/
- warn "error running #{command} (but no error message)"
- BufferManager.flash "Error running #{command}!"
- else
- warn "error running #{command}: #{message}"
- BufferManager.flash "Error: #{message}"
+ begin
+ Open3.popen3(command) do |input, output, error|
+ err, data, * = IO.select [error], [input], nil
+
+ unless err.empty?
+ message = err.first.read
+ if message =~ /^\s*$/
+ warn "error running #{command} (but no error message)"
+ BufferManager.flash "Error running #{command}!"
+ else
+ warn "error running #{command}: #{message}"
+ BufferManager.flash "Error: #{message}"
+ end
+ return nil, false
end
- return
- end
- data = data.first
- data.sync = false # buffer input
+ data = data.first
+ data.sync = false # buffer input
- yield data
- data.close # output will block unless input is closed
+ yield data
+ data.close # output will block unless input is closed
- ## BUG?: shows errors or output but not both....
- data, * = IO.select [output, error], nil, nil
- data = data.first
+ ## BUG?: shows errors or output but not both....
+ data, * = IO.select [output, error], nil, nil
+ data = data.first
- if data.eof
- BufferManager.flash "'#{command}' done!"
- nil
- else
- data.read
+ if data.eof
+ BufferManager.flash "'#{command}' done!"
+ return nil, true
+ else
+ return data.read, true
+ end
end
+ rescue Errno::ENOENT
+ # If the command is invalid
+ return nil, false
end
end
end
diff --git a/lib/sup/modes/edit_message_mode.rb b/lib/sup/modes/edit_message_mode.rb
@@ -699,7 +699,7 @@ private
sigfn = (AccountManager.account_for(from_email) ||
AccountManager.default_account).signature
- if sigfn && File.exists?(sigfn)
+ if sigfn && File.exist?(sigfn)
["", "-- "] + File.readlines(sigfn).map { |l| l.chomp }
else
[]
diff --git a/lib/sup/modes/forward_mode.rb b/lib/sup/modes/forward_mode.rb
@@ -1,6 +1,17 @@
module Redwood
class ForwardMode < EditMessageMode
+
+ HookManager.register "forward-attribution", <<EOS
+Generates the attribution for the forwarded message
+(["--- Begin forwarded message from John Doe ---",
+ "--- End forwarded message ---"])
+Variables:
+ message: a message object representing the message being replied to
+ (useful values include message.from.mediumname and message.date)
+Return value:
+ A list containing two strings: the text of the begin line and the text of the end line
+EOS
## TODO: share some of this with reply-mode
def initialize opts={}
header = {
@@ -65,9 +76,17 @@ class ForwardMode < EditMessageMode
protected
def forward_body_lines m
- ["--- Begin forwarded message from #{m.from.mediumname} ---"] +
- m.quotable_header_lines + [""] + m.quotable_body_lines +
- ["--- End forwarded message ---"]
+ attribution = HookManager.run("forward-attribution", :message => m) || default_attribution(m)
+ attribution[0,1] +
+ m.quotable_header_lines +
+ [""] +
+ m.quotable_body_lines +
+ attribution[1,1]
+ end
+
+ def default_attribution m
+ ["--- Begin forwarded message from #{m.from.mediumname} ---",
+ "--- End forwarded message ---"]
end
def send_message
diff --git a/lib/sup/modes/line_cursor_mode.rb b/lib/sup/modes/line_cursor_mode.rb
@@ -65,7 +65,7 @@ protected
def set_cursor_pos p
return if @curpos == p
@curpos = p.clamp @cursor_top, lines
- buffer.mark_dirty
+ buffer.mark_dirty if buffer # not sure why the buffer is gone
set_status
end
diff --git a/lib/sup/modes/text_mode.rb b/lib/sup/modes/text_mode.rb
@@ -24,10 +24,15 @@ class TextMode < ScrollMode
command = BufferManager.ask(:shell, "pipe command: ")
return if command.nil? || command.empty?
- output = pipe_to_process(command) do |stream|
+ output, success = pipe_to_process(command) do |stream|
@text.each { |l| stream.puts l }
end
+ unless success
+ BufferManager.flash "Invalid command: '#{command}' is not an executable"
+ return
+ end
+
if output
BufferManager.spawn "Output of '#{command}'", TextMode.new(output.ascii)
else
diff --git a/lib/sup/modes/thread_index_mode.rb b/lib/sup/modes/thread_index_mode.rb
@@ -1026,7 +1026,7 @@ private
end
def from_width
- [(buffer.content_width.to_f * 0.2).to_i, MIN_FROM_WIDTH].max
+ [(buffer.content_width.to_f * 0.2).to_i, MIN_FROM_WIDTH].max if buffer else MIN_FROM_WIDTH # not sure why the buffer is gone
end
def initialize_threads
diff --git a/lib/sup/modes/thread_view_mode.rb b/lib/sup/modes/thread_view_mode.rb
@@ -89,6 +89,7 @@ EOS
k.add :toggle_wrap, "Toggle wrapping of text", 'w'
k.add :goto_uri, "Goto uri under cursor", 'g'
+ k.add :fetch_and_verify, "Fetch the PGP key on poolserver and re-verify message", "v"
k.add_multi "(a)rchive/(d)elete/mark as (s)pam/mark as u(N)read:", '.' do |kk|
kk.add :archive_and_kill, "Archive this thread and kill buffer", 'a'
@@ -224,10 +225,24 @@ EOS
def unsubscribe_from_list
m = @message_lines[curpos] or return
- if m.list_unsubscribe && m.list_unsubscribe =~ /<mailto:(.*?)(\?subject=(.*?))?>/
+ BufferManager.flash "Can't find List-Unsubscribe header for this message." unless m.list_unsubscribe
+
+ if m.list_unsubscribe =~ /<mailto:(.*?)(\?subject=(.*?))?>/
ComposeMode.spawn_nicely :from => AccountManager.account_for(m.recipient_email), :to => [Person.from_address($1)], :subj => ($3 || "unsubscribe")
- else
- BufferManager.flash "Can't find List-Unsubscribe header for this message."
+ elsif m.list_unsubscribe =~ /<(http.*)?>/
+ unless HookManager.enabled? "goto"
+ BufferManager.flash "You must add a goto.rb hook before you can goto an unsubscribe URI."
+ return
+ end
+
+ begin
+ u = URI.parse($1)
+ rescue URI::InvalidURIError => e
+ BufferManager.flash("Invalid unsubscribe link")
+ return
+ end
+
+ HookManager.run "goto", :uri => Shellwords.escape(u.to_s)
end
end
@@ -374,7 +389,7 @@ EOS
when Chunk::Attachment
default_dir = $config[:default_attachment_save_dir]
default_dir = ENV["HOME"] if default_dir.nil? || default_dir.empty?
- default_fn = File.expand_path File.join(default_dir, chunk.filename)
+ default_fn = File.expand_path File.join(default_dir, chunk.safe_filename)
fn = BufferManager.ask_for_filename :filename, "Save attachment to file or directory: ", default_fn, true
# if user selects directory use file name from message
@@ -403,7 +418,7 @@ EOS
num_errors = 0
m.chunks.each do |chunk|
next unless chunk.is_a?(Chunk::Attachment)
- fn = File.join(folder, chunk.filename)
+ fn = File.join(folder, chunk.safe_filename)
num_errors += 1 unless save_to_file(fn, false) { |f| f.print chunk.raw_content }
num += 1
end
@@ -708,7 +723,7 @@ EOS
command = BufferManager.ask(:shell, "pipe command: ")
return if command.nil? || command.empty?
- output = pipe_to_process(command) do |stream|
+ output, success = pipe_to_process(command) do |stream|
if chunk
stream.print chunk.raw_content
else
@@ -716,6 +731,11 @@ EOS
end
end
+ unless success
+ BufferManager.flash "Invalid command: '#{command}' is not an executable"
+ return
+ end
+
if output
BufferManager.spawn "Output of '#{command}'", TextMode.new(output.ascii)
else
@@ -774,6 +794,27 @@ EOS
BufferManager.flash "No URI found." unless found
end
+ def fetch_and_verify
+ message = @message_lines[curpos]
+ crypto_chunk = message.chunks.select {|chunk| chunk.is_a?(Chunk::CryptoNotice)}.first
+ return unless crypto_chunk
+ return unless crypto_chunk.unknown_fingerprint
+
+ BufferManager.flash "Retrieving key #{crypto_chunk.unknown_fingerprint} ..."
+
+ error = CryptoManager.retrieve crypto_chunk.unknown_fingerprint
+
+ if error
+ BufferManager.flash "Couldn't retrieve key: #{error.to_s}"
+ else
+ BufferManager.flash "Key #{crypto_chunk.unknown_fingerprint} successfully retrieved !"
+ end
+
+ # Re-trigger gpg verification
+ message.reload_from_source!
+ update
+ end
+
private
def initial_state_for m
diff --git a/lib/sup/person.rb b/lib/sup/person.rb
@@ -18,11 +18,16 @@ class Person
@email = email.strip.gsub(/\s+/, " ")
end
- def to_s; "#@name <#@email>" end
+ def to_s
+ if @name
+ "#@name <#@email>"
+ else
+ @email
+ end
+ end
# def == o; o && o.email == email; end
# alias :eql? :==
-# def hash; [name, email].hash; end
def shortname
case @name
@@ -37,26 +42,10 @@ class Person
end
end
- def longname
- if @name && @email
- "#@name <#@email>"
- else
- @email
- end
- end
-
def mediumname; @name || @email; end
- def Person.full_address name, email
- if name && email
- if name =~ /[",@]/
- "#{name.inspect} <#{email}>" # escape quotes
- else
- "#{name} <#{email}>"
- end
- else
- email
- end
+ def longname
+ to_s
end
def full_address
@@ -79,56 +68,74 @@ class Person
end.downcase
end
- ## return "canonical" person using contact manager or create one if
- ## not found or contact manager not available
- def self.from_name_and_email name, email
- ContactManager.instantiated? && ContactManager.person_for(email) || Person.new(name, email)
+ def eql? o; email.eql? o.email end
+ def hash; email.hash end
+
+
+ ## see comments in self.from_address
+ def indexable_content
+ [name, email, email.split(/@/).first].join(" ")
end
- def self.from_address s
- return nil if s.nil?
-
- ## try and parse an email address and name
- name, email = case s
- when /(.+?) ((\S+?)@\S+) \3/
- ## ok, this first match cause is insane, but bear with me. email
- ## addresses are stored in the to/from/etc fields of the index in a
- ## weird format: "name address first-part-of-address", i.e. spaces
- ## separating those three bits, and no <>'s. this is the output of
- ## #indexable_content. here, we reverse-engineer that format to extract
- ## a valid address.
- ##
- ## we store things this way to allow searches on a to/from/etc field to
- ## match any of those parts. a more robust solution would be to store a
- ## separate, non-indexed field with the proper headers. but this way we
- ## save precious bits, and it's backwards-compatible with older indexes.
- [$1, $2]
- when /["'](.*?)["'] <(.*?)>/, /([^,]+) <(.*?)>/
- a, b = $1, $2
- [a.gsub('\"', '"'), b]
- when /<((\S+?)@\S+?)>/
- [$2, $1]
- when /((\S+?)@\S+)/
- [$2, $1]
+ class << self
+
+ def full_address name, email
+ if name && email
+ if name =~ /[",@]/
+ "#{name.inspect} <#{email}>" # escape quotes
+ else
+ "#{name} <#{email}>"
+ end
else
- [nil, s]
+ email
end
+ end
- from_name_and_email name, email
- end
+ ## return "canonical" person using contact manager or create one if
+ ## not found or contact manager not available
+ def from_name_and_email name, email
+ ContactManager.instantiated? && ContactManager.person_for(email) || Person.new(name, email)
+ end
- def self.from_address_list ss
- return [] if ss.nil?
- ss.dup.split_on_commas.map { |s| self.from_address s }
- end
+ def from_address s
+ return nil if s.nil?
+
+ ## try and parse an email address and name
+ name, email = case s
+ when /(.+?) ((\S+?)@\S+) \3/
+ ## ok, this first match cause is insane, but bear with me. email
+ ## addresses are stored in the to/from/etc fields of the index in a
+ ## weird format: "name address first-part-of-address", i.e. spaces
+ ## separating those three bits, and no <>'s. this is the output of
+ ## #indexable_content. here, we reverse-engineer that format to extract
+ ## a valid address.
+ ##
+ ## we store things this way to allow searches on a to/from/etc field to
+ ## match any of those parts. a more robust solution would be to store a
+ ## separate, non-indexed field with the proper headers. but this way we
+ ## save precious bits, and it's backwards-compatible with older indexes.
+ [$1, $2]
+ when /["'](.*?)["'] <(.*?)>/, /([^,]+) <(.*?)>/
+ a, b = $1, $2
+ [a.gsub('\"', '"'), b]
+ when /<((\S+?)@\S+?)>/
+ [$2, $1]
+ when /((\S+?)@\S+)/
+ [$2, $1]
+ else
+ [nil, s]
+ end
+
+ from_name_and_email name, email
+ end
+
+ def from_address_list ss
+ return [] if ss.nil?
+ ss.dup.split_on_commas.map { |s| self.from_address s }
+ end
- ## see comments in self.from_address
- def indexable_content
- [name, email, email.split(/@/).first].join(" ")
end
- def eql? o; email.eql? o.email end
- def hash; email.hash end
end
end
diff --git a/lib/sup/search.rb b/lib/sup/search.rb
@@ -12,7 +12,7 @@ class SearchManager
def initialize fn
@fn = fn
@searches = {}
- if File.exists? fn
+ if File.exist? fn
IO.foreach(fn) do |l|
l =~ /^([^:]*): (.*)$/ or raise "can't parse #{fn} line #{l.inspect}"
@searches[$1] = $2
diff --git a/lib/sup/sent.rb b/lib/sup/sent.rb
@@ -40,7 +40,7 @@ class SentLoader < MBox
def initialize
@filename = Redwood::SENT_FN
- File.open(@filename, "w") { } unless File.exists? @filename
+ File.open(@filename, "w") { } unless File.exist? @filename
super "mbox://" + @filename, true, $config[:archive_sent]
end
diff --git a/lib/sup/util/locale_fiddler.rb b/lib/sup/util/locale_fiddler.rb
@@ -0,0 +1,24 @@
+## the following magic enables wide characters when used with a ruby
+## ncurses.so that's been compiled against libncursesw. (note the w.) why
+## this works, i have no idea. much like pretty much every aspect of
+## dealing with curses. cargo cult programming at its best.
+require 'fiddle'
+require 'fiddle/import'
+
+module LocaleFiddler
+ extend Fiddle::Importer
+
+ SETLOCALE_LIB = case RbConfig::CONFIG['arch']
+ when /darwin/; "libc.dylib"
+ when /cygwin/; "cygwin1.dll"
+ when /freebsd/; "libc.so.7"
+ else; "libc.so.6"
+ end
+
+ dlload SETLOCALE_LIB
+ extern "char *setlocale(int, char const *)"
+
+ def setlocale(type, string)
+ LocaleFiddler.setlocale(type, string)
+ end
+end
diff --git a/sup.gemspec b/sup.gemspec
@@ -38,7 +38,7 @@ SUP: please note that our old mailing lists have been shut down,
s.require_paths = ["lib"]
s.extra_rdoc_files = Dir.glob("man/*")
- s.required_ruby_version = '>= 1.9.3'
+ s.required_ruby_version = '>= 2.0.0'
# this is here to support skipping the xapian-ruby installation on OpenBSD
# because the xapian-ruby gem doesn't install on OpenBSD, you must install
@@ -62,8 +62,9 @@ SUP: please note that our old mailing lists have been shut down,
s.add_development_dependency "bundler", "~> 1.3"
s.add_development_dependency "rake"
- s.add_development_dependency "minitest", "~> 4.7"
- s.add_development_dependency "rr", "~> 1.0.5"
+ s.add_development_dependency 'minitest', '~> 5.5.1'
+ s.add_development_dependency "rr", "~> 1.1"
s.add_development_dependency "gpgme", ">= 2.0.2"
+ s.add_development_dependency "pry"
end
diff --git a/test/integration/test_maildir.rb b/test/integration/test_maildir.rb
@@ -1,6 +1,6 @@
require "test_helper"
-class TestMaildir < MiniTest::Unit::TestCase
+class TestMaildir < Minitest::Test
def setup
@path = Dir.mktmpdir
diff --git a/test/integration/test_mbox.rb b/test/integration/test_mbox.rb
@@ -1,6 +1,6 @@
require "test_helper"
-class TestMbox < MiniTest::Unit::TestCase
+class TestMbox < MiniTest::Test
def setup
@path = Dir.mktmpdir
diff --git a/test/test_crypto.rb b/test/test_crypto.rb
@@ -25,7 +25,7 @@ require 'tmpdir'
module Redwood
-class TestCryptoManager < ::Minitest::Unit::TestCase
+class TestCryptoManager < Minitest::Test
def setup
@from_email = 'sup-test-1@foo.bar'
diff --git a/test/test_header_parsing.rb b/test/test_header_parsing.rb
@@ -6,7 +6,7 @@ require 'stringio'
include Redwood
-class TestMBoxParsing < Minitest::Unit::TestCase
+class TestMBoxParsing < Minitest::Test
def setup
@path = Dir.mktmpdir
diff --git a/test/test_message.rb b/test/test_message.rb
@@ -6,27 +6,9 @@ require 'stringio'
require 'dummy_source'
-# override File.exists? to make it work with StringIO for testing.
-# FIXME: do aliasing to avoid breaking this when sup moves from
-# File.exists? to File.exist?
-
-class File
-
- def File.exists? file
- # puts "fake File::exists?"
-
- if file.is_a?(StringIO)
- return false
- end
- # use the different function
- File.exist?(file)
- end
-
-end
-
module Redwood
-class TestMessage < ::Minitest::Unit::TestCase
+class TestMessage < Minitest::Test
def setup
@path = Dir.mktmpdir
@@ -520,6 +502,82 @@ EOS
end
+ def test_malicious_attachment_names
+
+
+ message = <<EOS
+From: Matthieu Rakotojaona <matthieu.rakotojaona@gmail.com>
+To: reply+0007a7cb7174d1d188fcd420fce83e0f68fe03fc7416cdae92cf0000000110ce4efd92a169ce033d18e1 <reply+0007a7cb7174d1d188fcd420fce83e0f68fe03fc7416cdae92cf0000000110ce4efd92a169ce033d18e1@reply.github.com>
+Subject: Re: [sup] Attachment saving and special characters in filenames (#378)
+In-reply-to: <sup-heliotrope/sup/issues/378@github.com>
+References: <sup-heliotrope/sup/issues/378@github.com>
+X-pgp-key: http://otokar.looc2011.eu/static/matthieu.rakotojaona.asc
+Date: Wed, 14 Jan 2015 22:13:37 +0100
+Message-Id: <1421269972-sup-5245@kpad>
+User-Agent: Sup/git
+Content-Transfer-Encoding: 8bit
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="=-1421270017-526778-1064-1628-1-="
+
+
+--=-1421270017-526778-1064-1628-1-=
+Content-Type: text/plain; charset=UTF-8
+Content-Disposition: inline
+
+Excerpts from Felix Kaiser's message of 2015-01-14 16:36:29 +0100:
+> When saving attachments, sup should replace special characters when suggesting a filename to save the attachment to.
+>
+> I just got an attachment with a name like "foo/2.pdf". sup suggests saving it to /home/fxkr/foo/2.pdf (and fails to save it, of course, if /home/fxkr/foo isn't a directory).
+>
+> I haven't tested the "Save All" feature, but I hope nothing bad happens when there's an attachment called "../../../../../../../home/fxkr/.bashrc" ;-)
+>
+> ---
+> Reply to this email directly or view it on GitHub:
+> https://github.com/sup-heliotrope/sup/issues/378
+
+For tests, here's an email with an attachment filename set to
+sup/.travis.yml (really, this time)
+
+--
+Matthieu Rakotojaona
+
+--=-1421270017-526778-1064-1628-1-=
+Content-Disposition: attachment; filename="sup/.travis.yml"
+Content-Type: text/x-yaml; name="sup/.travis.yml"
+Content-Transfer-Encoding: 8bit
+
+language: ruby
+
+rvm:
+ - 2.1.1
+ - 2.0.0
+ - 1.9.3
+
+before_install:
+ - sudo apt-get update -qq
+ - sudo apt-get install -qq uuid-dev uuid libncursesw5-dev libncursesw5 gnupg2 pandoc
+ - git submodule update --init --recursive
+
+script: bundle exec rake travis
+
+--=-1421270017-526778-1064-1628-1-=--
+EOS
+
+ source = DummySource.new("sup-test://test_blank_header_lines")
+ source.messages = [ message ]
+ source_info = 0
+
+ sup_message = Message.build_from_source(source, source_info)
+ chunks = sup_message.load_from_source!
+
+ # See if attachment filenames can be safely used for saving.
+ # We do that by verifying that any folder-related character (/ or \)
+ # are not interpreted: the filename must not be interpreted into a
+ # path.
+ fn = chunks[3].safe_filename
+ assert_equal(fn, File.basename(fn))
+
+ end
# TODO: test different error cases, malformed messages etc.
# TODO: test different quoting styles, see that they are all divided
diff --git a/test/test_messages_dir.rb b/test/test_messages_dir.rb
@@ -6,27 +6,9 @@ require 'stringio'
require 'dummy_source'
-# override File.exists? to make it work with StringIO for testing.
-# FIXME: do aliasing to avoid breaking this when sup moves from
-# File.exists? to File.exist?
-
-class File
-
- def File.exists? file
- # puts "fake File::exists?"
-
- if file.is_a?(StringIO)
- return false
- end
- # use the different function
- File.exist?(file)
- end
-
-end
-
module Redwood
-class TestMessagesDir < ::Minitest::Unit::TestCase
+class TestMessagesDir < ::Minitest::Test
def setup
@path = Dir.mktmpdir
diff --git a/test/test_yaml_regressions.rb b/test/test_yaml_regressions.rb
@@ -6,7 +6,7 @@ require 'yaml'
require 'sup'
module Redwood
- class TestYamlRegressions < ::Minitest::Unit::TestCase
+ class TestYamlRegressions < ::Minitest::Test
def test_yamling_hash
hsh = {:foo => 42}
reloaded = YAML.load(hsh.to_yaml)
diff --git a/test/unit/fixtures/contacts.txt b/test/unit/fixtures/contacts.txt
@@ -0,0 +1 @@
+RC: Random Contact <random_dude@gmail.com>
+\ No newline at end of file
diff --git a/test/unit/test_contact.rb b/test/unit/test_contact.rb
@@ -0,0 +1,33 @@
+require 'test_helper'
+require 'sup/contact'
+
+module Redwood
+
+class TestContact < Minitest::Test
+ def setup
+ @contact = ContactManager.init(File.expand_path("../fixtures/contacts.txt", __FILE__))
+ @person = Person.new "Terrible Name", "terrible@name.com"
+ end
+
+ def teardown
+ runner = Redwood.const_get "ContactManager".to_sym
+ runner.deinstantiate!
+ end
+
+ def test_contact_manager
+ assert @contact
+ ## 1 contact is imported from the fixture file.
+ assert_equal 1, @contact.contacts.count
+ assert_equal @contact.contact_for("RC").name, "Random Contact"
+
+ assert_nil @contact.contact_for "TN"
+ @contact.update_alias @person, "TN"
+
+ assert @contact.is_aliased_contact?(@person)
+ assert_equal @person, @contact.contact_for("TN")
+
+ assert_equal "TN", @contact.alias_for(@person)
+ end
+end
+
+end
+\ No newline at end of file
diff --git a/test/unit/test_locale_fiddler.rb b/test/unit/test_locale_fiddler.rb
@@ -0,0 +1,15 @@
+require 'test_helper'
+require 'sup/util/locale_fiddler'
+
+class TestFiddle < ::Minitest::Unit::TestCase
+ # TODO this is a silly test
+ def test_fiddle_set_locale
+ before = LocaleDummy.setlocale(6, nil).to_s
+ after = LocaleDummy.setlocale(6, "").to_s
+ assert(before != after, "Expected locale to be fiddled with")
+ end
+end
+
+class LocaleDummy
+ extend LocaleFiddler
+end
diff --git a/test/unit/test_person.rb b/test/unit/test_person.rb
@@ -0,0 +1,37 @@
+require 'test_helper'
+require 'sup'
+
+module Redwood
+
+class TestPerson < Minitest::Test
+ def setup
+ @person = Person.new("Thomassen, Bob", "bob@thomassen.com")
+ @no_name = Person.new(nil, "alice@alice.com")
+ end
+
+ def test_email_must_be_supplied
+ assert_raises (ArgumentError) { Person.new("Alice", nil) }
+ end
+
+ def test_to_string
+ assert_equal "Thomassen, Bob <bob@thomassen.com>", "#{@person}"
+ assert_equal "alice@alice.com", "#{@no_name}"
+ end
+
+ def test_shortname
+ assert_equal "Bob", @person.shortname
+ assert_equal "alice@alice.com", @no_name.shortname
+ end
+
+ def test_mediumname
+ assert_equal "Thomassen, Bob", @person.mediumname
+ assert_equal "alice@alice.com", @no_name.mediumname
+ end
+
+ def test_fullname
+ assert_equal "\"Thomassen, Bob\" <bob@thomassen.com>", @person.full_address
+ assert_equal "alice@alice.com", @no_name.full_address
+ end
+end
+
+end
+\ No newline at end of file