2010-08-13 :-)
_ [首都圏外郭放水路見学]首都圏外郭放水路見学
行ってきた。
オレが本当に欲しかったのは明るい広角レンズだということが分かった。
F4 では暗すぎたんだ...
管理室というのはどこもそう変わらないんだなあとか
「首都圏外郭放水路 超厚水路で 歌手、声優として活躍中の水樹奈々さんの "WILD EYES" MUSIC CLIP撮影が行われました」
_ [QuickML][コードリーディング][Ruby]QuickML を読む - メーリングリストの作成と参加
メーリングリストの作成と参加の一連の処理を見てみる。
メーリングリストの作成方法はこう。
横着プログラミング 第5回: QuickML: 超お手軽なメーリングリスト
○○○@quickml.com のような任意のアドレスにいきなりメールを送るだけで、新しいメーリングリストを作成できる。たとえば宴会のメーリングリストを作るには enkai@quickml.com にメールを送ればいい。このとき、 From: と Cc: のアドレスがメーリングリストに登録される。
Subject: 宴会メーリングリスト To: enkai@quickml.com ← 作りたいMLのアドレス From: satoru@namazu.org ← 自分のアドレス Cc: masui@pitecan.com ← 参加者リスト 突然ですが、宴会好きのメーリング ← 本文 リストを作ってみました。
では先ほどの続きで、 process() を見る。
class Session : def process until @socket.closed? begin mail = Mail.new receive_mail(mail) if mail.valid? processor = Processor.new(@config, mail) processor.process end rescue TooLargeMail cleanup_connection report_too_large_mail(mail) if mail.valid? @logger.log "Too Large Mail: #{mail.from}" rescue TooLongLine cleanup_connection @logger.log "Too Long Line: #{mail.from}" end end end
Mail を生成し、mail を受け取り、processor に渡している。
Mail を見る。
Mail クラスは lib/quickml/mail.rb に定義されている。
class Mail def initialize @mail_from = nil @recipients = [] @header = [] @body = "" @charset = nil @content_type = nil @bare = nil end
メールそのものだ。
receive_mail() を見る。
lib/quickml/server.rb に戻る。
class Session : def receive_mail (mail) while line = @socket.safe_gets line.xchomp! command, arg = line.split(/\s+/, 2) command = command.downcase.intern # "HELO" => :helo if @command_table.include?(command) @logger.vlog "Command: #{line}" send(command, mail, arg) else @logger.vlog "Unknown SMTP Command: #{command} #{arg}" @socket.puts "502 Error: command not implemented" end break if command == :quit or command == :data end end
ソケットから 1 行ずつ読み取り、メールを処理する。
ここの処理は SMTP のコマンドを想像すると分かりやすい。
send(command, mail, arg)
send() はこれ。
オブジェクトのメソッド name を、引数に args を渡して呼び出し、メソッドの実行結果を返します。
つまり rcpt, data などのコマンド名をそのままメソッド名として解釈させ、受け取った SMTP コマンドごとの該当するメソッドを呼んでいる。
ML に参加する手順では、from() で参加したいひとのメールアドレス、rcpt() でメーリングリストのアドレスを処理することになる。
from() を見てみる。
def from address = if not self["From"].empty? collect_address(self["From"]).first else @mail_from end address = "unknown" if address.nil? or address.empty? normalize_address(address) end
collect_address() はこう。addresses にメールアドレスを追加していく。
def collect_address (field) address_regex = /(("?)[-0-9a-zA-Z_.+?\/]+\2@[-0-9a-zA-Z]+\.[-0-9a-zA-Z.]+)/ #/ addresses = [] parts = remove_comment_in_field(field).split(',') parts.each {|part| if (/<(.*?)>/ =~ part) or (address_regex =~ part) addresses.push(normalize_address($1)) end } addresses.uniq end
次に rcpt() を見てみる。
class Session : def rcpt (mail, arg) if mail.mail_from.nil? @socket.puts "503 Error: need MAIL command" elsif /^To:\s*<(.*)>/i =~ arg or /^To:\s*(.*)/i =~ arg address = $1 if Mail.address_of_domain?(address, @config.domain) mail.add_recipient(address) @socket.puts "250 ok" else @socket.puts "554 <#{address}>: Recipient address rejected" @logger.vlog "Unacceptable RCPT TO:<#{address}>" end else @socket.puts "501 Syntax: RCPT TO: <address>" end end
ドメインの有無チェックなどがあるが、ようするに add_recipient() してメーリングリストのメールアドレスを追加していく。
class Mail : def add_recipient (address) @recipients.push(normalize_address(address)) end
recipients にひたすら追加している。
受け取ったメールアドレスを格納していったので、次にそれを処理するための Processor を見る。Processor.process() で処理している。
ファイルは lib/quickml/core.rb
class Processor : public def process mail_log if @mail.looping? @logger.log "Looping Mail: from #{@mail.from}" return end @mail.recipients.each {|recipient| process_recipient(recipient) } end
溜め込んだメーリングリストのメールアドレス recipients を処理していく。
process_recipient() を見る。
class Processor : def process_recipient (recipient) mladdress = recipient if to_return_address?(mladdress) handler = ErrorMailHandler.new(@config, @message_charset) handler.handle(@mail) elsif @config.confirm_ml_creation and to_confirmation_address?(mladdress) validate_confirmation(mladdress) else begin @config.ml_mutex(mladdress).synchronize { ml = QuickML.new(@config, mladdress, @mail.from, @message_charset) @message_charset = (@message_charset or ml.charset) (unsubscribe(ml); return) if unsubscribe_requested? submit(ml) } rescue InvalidMLName report_invalid_mladdress(mladdress) end end end
QuickML.new() からがその処理。QuickML クラスはメーリングリストを管理する。
submit() はこう。
class Processor : def submit (ml) if ml.exclude?(@mail.from) @logger.log "Invalid From Address: #{@mail.from}" elsif ml.forward? @logger.log "Forward Address: #{ml.address}" ml.submit(@mail) elsif confirmation_required?(ml) ml.prepare_confirmation(@mail) elsif acceptable_submission?(ml) submit_article(ml) else report_rejection(ml) end end
quickmlrc で confirm_ml_creation を true にしていなければ( デフォルト False )、submit_article() が呼ばれる。
submit_article() を見てみる。
class Processor : def submit_article (ml) @unadded_addresses = [] if ml_address_in_to?(ml) add_member(ml, @mail.from) @mail.collect_cc.each {|address| add_member(ml, address) } end unless @unadded_addresses.empty? report_too_many_members(ml, @unadded_addresses) end ml.submit(@mail) end
ここで、指定したメーリングリストが新規作成される場合は ml.submit(@mail) のみが呼ばれる。メーリングリストがすでに存在しておりそのメーリングリストにメンバーを追加する場合は from で格納したメールアドレスを add_member() で登録する。
add_member() はこう。
class Processor : def add_member (ml, address) begin ml.add_member(address) rescue TooManyMembers @unadded_addresses.push(address) end end
ml.add_member() はこう。
class QuickML : def add_member (address) if exclude?(address) @logger.vlog "Excluded: #{address}" return end return if @active_members.include?(address) raise TooManyMembers if too_many_members? @former_members.delete(address) @active_members.push(address) save_member_file @logger.log "[#{@name}]: Add: #{address}" @added_members.push(address) @member_added_p = true end
メーリングリストにメールアドレスを追加などし、メーリングリストの管理用のファイルを書き込んでいる。
ml.submit() は結局これが呼ばれる。
class QuickML : def _submit (mail) inc_count save_charset remove_alertedp_file subject = Mail.rewrite_subject(mail["Subject"], @short_name, @count) body = rewrite_body(mail) header = [] mail.each_field {|key, value| k = key.downcase next if k == "subject" or k == "reply-to" header.push([key, value]) } header.push(["Subject", subject], ["Reply-To", @address], ["X-Mail-Count",@count]) header.concat(quickml_fields) Mail.send_mail(@config.smtp_host, @config.smtp_port, @logger, :mail_from => @return_address, :recipients => @active_members, :header => header, :body => body) end
メールを組み立て Mail.send_mail() を呼ぶ。
これ。特異クラスとなっている。
class << self def send_mail (smtp_host, smtp_port, logger, optional = {}) mail_from = optional[:mail_from] recipients = optional[:recipients] header = optional[:header] body = optional[:body] if optional[:recipient] raise unless optional[:recipient].kind_of?(String) recipients = [optional[:recipient]] end raise if mail_from.nil? or recipients.nil? or body.nil? or header.nil? contents = "" header.each {|field| key = field.first; value = field.last contents << "#{key}: #{value}\n" if key.kind_of?(String) } contents << "\n" contents << body begin sender = MailSender.new(smtp_host, smtp_port, true) sender.send(contents, mail_from, recipients) rescue => e logger.log "Error: Unable to send mail: #{e.class}: #{e.message}" end end
sender.send() でメール送信する。
これ。
class MailSender : def send (message, mail_from, recipients) recipients = [recipients] if recipients.kind_of?(String) s = TCPSocket.open(@smtp_host, @smtp_port) send_command(s, nil, 220) send_command(s, "EHLO #{Socket.gethostname}", 250) if @use_xverp and @xverp_available and (not mail_from.empty?) send_command(s, "MAIL FROM: <#{mail_from}> XVERP===", 250) else send_command(s, "MAIL FROM: <#{mail_from}>", 250) end recipients.each {|recipient| send_command(s, "RCPT TO: <#{recipient}>", 250) } send_command(s, "DATA", 354) message.each_line {|line| line.sub!(/\r?\n/, '') line.sub!(/^\./, "..") line << "\r\n" s.print(line) } send_command(s, ".", 250) send_command(s, "QUIT", 221) s.close end
SMTP をしゃべっている。
どうして Net::SMTP などを使わずに自力でしゃべっているのか。
ChangeLog を見てみる。
2003-01-17 Satoru Takabayashi <satoru@namazu.org> * lib/quickml/utils.rb (Net::NetPrivate::SMTPCommand): Removed. * lib/quickml/mail.rb (QuickML::Mail::send_mail): Use MailSender instead of Net::SMTP. * lib/quickml/server.rb (QuickML::Session::report_too_large_mail): Change the recipient address: @mail.envelope_from => @mail.from * lib/quickml/mail.rb (QuickML::MailSender): New class.
うーん
MailSender で use_xverp しているので....
module QuickML
class MailSender def initialize (smtp_host, smtp_port, use_xverp = false) @smtp_port = smtp_port @smtp_host = smtp_host @use_xverp = use_xverp @xverp_available = false end
XVERP 関連の処理を描き直したかったのかしら。