# HG changeset patch # User HIROSE Yuuji # Date 1387158081 -32400 # Node ID e2b6a2e8b5c72b26958ccec79407cde5db320ac6 # Parent f67f5304baacc8896dadc8ff2b7464d53cb03925 Enable inter-member mail Support attachment files diff -r f67f5304baac -r e2b6a2e8b5c7 after5.rb --- a/after5.rb Thu Dec 12 19:57:20 2013 +0900 +++ b/after5.rb Mon Dec 16 10:41:21 2013 +0900 @@ -4,7 +4,7 @@ # Associative Scheduling Table - after5 # (C)2003, 2004, 2006, 2008, 2012, 2013 by HIROSE Yuuji [yuujigentei.org] # $Id: after5.rb,v 1.20 2012/12/03 15:54:20 yuuji Exp $ -# Last modified Thu Dec 12 18:09:01 2013 on firestorm +# Last modified Mon Dec 16 10:38:55 2013 on firestorm # See http://www.gentei.org/~yuuji/software/after5/ # このスクリプトはEUCで保存してください。 $hgid = <<_HGID_.split[1..-2].join(" ") @@ -62,7 +62,7 @@ lf = nl ? "\n" : "" if attrs.is_a?(Hash) for k in attrs.keys - attr += " %s=\"%s\"" % [k, attrs[k]] + attr += " %s=\"%s\"" % [k, attrs[k]] end end body = yield @@ -1099,6 +1099,9 @@ @schedulearea = {'rows'=>'4', 'cols'=>'60', 'name'=>'schedule'} @oldagent = (%r,Mozilla/4, =~ ENV['HTTP_USER_AGENT']) @lang = 0 + @mlbasedir = "ml" + @attachmentdir = "a" + @attachmentmax = 8*1024**2 @mailmode = nil @mailadmdelimiter = "/" @mailadmsuffix = @mailadmdelimiter + "adm" @@ -1355,6 +1358,7 @@ "Send this message to all of group."], 'sendall_done' => ['送信完了', "sending message done"], 'body' => ['本文', 'Body'], + 'rcptto' => ['宛先', 'Recipients'], 'member' => ['メンバー', 'Member'], 'personalmode' => ['自分のだけ表示モード', 'Display Personal Only'], 'normalmode' => ['全員分表示モード', "Display Everyone's"], @@ -1681,7 +1685,7 @@ thru=nil, spoolto=false) # rcptto should be an Array body = NKF.nkf("-j", body) unless thru - subject = NKF.nkf("-jM", subject.strip) + subject = NKF.nkf("-jM", (subject||"No subject").strip) to = safecopy(to) # cleanup tainted address subject.gsub!(/\n/, '') begin @@ -1699,7 +1703,7 @@ # m.puts "Date: #{Time.now.strftime("%a, %d %b %Y %T %z")}" m.print "\n" end - m.print body, "\n" + m.write body m.close else # exec(@attr['mail'], "-s", subject, to) @@ -1723,10 +1727,25 @@ open("/tmp/body", "w") {|w| w.print STDIN.readlines.join w.puts "---" w.puts recipient.join(",\n") + w.puts header.inspect + w.puts "ENV: #{ENV.inspect}" } exit 0 + elsif header['Return-path'] && /-@/ =~ header['Return-path'] + # if VERP is requested + mailcmd = ENV['MAILCMD'] || @opt['sendmail'] + contents = STDIN.readlines + recipient.uniq.each {|r| + local, domain = header['Return-path'].split("@") + newlocal = local+r.sub("@", "=") + verp = newlocal+"@"+domain + verp = safecopy(verp) + open("| #{mailcmd} -f#{verp} -- #{r}", "w") {|m| + m.write contents.join + } + } else - #recipient.unshift "-f"+header['return-path'] if header['return-path'] + recipient.unshift "-f"+header['Return-path'] if header['Return-path'] exec(ENV['MAILCMD'] || @opt['sendmail'], *recipient) end end @@ -2722,11 +2741,12 @@ skip = false while line = body.shift case line.toeuc - when /^$/ - hold << "\n" - break - ## when /^(subject|from): /i - when /^(\S+): /i # if new header comes + # #Below does not work correctly when (from|subject): is final line + # when /^$/ + # hold << "\n" + # break + # ## when /^(subject|from): /i + when /^(\S+): /i, /^$/ # if new header comes or header ends if /^subject:/i =~ hold[0] # check previous header in hold space sj = hold.join.toeuc.sub("Subject: ", "").gsub(tag, "").strip removeregexp && sj && sj.gsub!(removeregexp, "") @@ -2737,10 +2757,14 @@ email, comment = parseaddress(from) if (!comment || comment=="") && comment = @sc.ismembersemail(email) # Reverse conversion of uname<->email - comment = @sc.nickname(comment) || "" + comment = @sc.nickname(comment) || "whoareyou" end hold = ["From: "+rewritefrom(email, comment, fromhack)+"\n"] end + if /^$/ =~ line.toeuc + hold << line + break + end ret += hold hold = [line] when /^\s/ # continued line @@ -2749,6 +2773,51 @@ end ret + hold + body end + def extract_attachment(attachment) + # Must return [text, href] strings to attached files + href = ""; text = "" + dir = @attachmentdir + if %r,(https?://[^/]+), =~ @opt['url'] + server = $1 + else + msg = "`url' not set in after5.cf" + return [msg, msg] + end + + urlbase = sprintf("%s%s/%s", + server, File.dirname(ENV['SCRIPT_NAME']), dir) + count=0 + attachment.each {|a| + basename = safecopy(File.basename(a['filename'])) + filename = safecopy(dir+"/"+basename) + umask = File.umask(022) + sz = a['value'].bytesize + next if sz == 0 + count += 1 + if sz > @attachmentmax + msg = sprintf("%d: %s is too large(%dMB), skipped\n", + count, basename, sz/1024**2) + text += msg; href += msg + next + end + begin + (test(?d, dir) && test(?w, dir)) or Dir.mkdir(dir) + open(filename, "w") {|x| x.write a['value']} + File.chmod(0664, filename) + text += sprintf("%d: %s/%s\n", count, urlbase, basename) + href += sprintf("%d: %s/%s\n", + count, filename, urlbase, basename) + rescue + ensure + File.umask(umask) + end + } + if count>0 + text="\n\n[[Attached files below]]\n"+text + href="[[Attached files]]\n"+href + end + [text.chomp, href.chomp] + end def defaultmladdress(name) prefix = (@opt['mailprefix'] || "") dash = prefix > '' ? "-" : "" @@ -2759,7 +2828,7 @@ # LOCAL=1 DEFAULT=name ./after5.rb -list # $DEFAULT is ML name viamail = ENV['LOCAL'] && ENV['DEFAULT'] # called via mail - from = toadmin = groupmode = fromhack = nil + from = toadmin = groupmode = fromhack = extract_report = nil unless @opt['mailprefix'] && @opt['maildomain'] if viamail STDERR.print msg('sendall_err') % [@opt['conf']] @@ -2772,6 +2841,7 @@ if viamail then prohibitviahttp() name = unquoted(ENV['DEFAULT']) + user = @sc.ismembersemail(ENV['SENDER']) if Regexp.new("(.*)("+Regexp.quote(@mailadmsuffix)+")") =~ name # To: GROUP/adm*@domain # -> Forward to group administrator(s) @@ -2798,7 +2868,11 @@ end else # via http return nil unless checkauth - name = unquoted(@params['name'].untaint) + # HERE CGI Params name, subject, attachment and body + # comes with enctype=multipart/form-data, + # So we get them via Array + name = unquoted(@params['name'][0]['value'].untaint) + user = @params['user'] if @sc.isuser(name) # groupmode = nil elsif grepgroup(name) @@ -2807,39 +2881,58 @@ @O.print @H.p("No such group: #{name}") return true end - nick = @sc.nickname(@params['user']) - from = sprintf("%s <%s>", nick, @params['user']) - body = @params['body'].gsub("\r", "").untaint + nick = @sc.nickname(user) + from = sprintf("%s <%s>", nick, user) + subj = @params['subject'][0]['value'] || "Message from "+@myname + body = @params['body'][0]['value'].gsub("\r", "").untaint + # Extract attachment file + if @params['attachment'].is_a?(Array) + body += (extract_report = extract_attachment(@params['attachment']))[0] + end end - + # Variables here + # name: Destination group/user name + # user: Member account or nil(not member) + # Set values for header rewriting + # We have to setup these variables: + # to: Header To: + # subj: Header Subject: + # from: Header From: + # rcpts: Array of recipients addresses + # header: Hash of additional header values + # body: String of mail body + # spooling: Flag if spool ML files nor not + # mldir: spooling directory name if groupmode # Run as ML + # To: should be ML address + # Return-path: should be verp of PREFIX-RCPT@DOMAIN bracket = @sc.getgroupattr(name, 'subjtag') || @opt['mailbracket'] xmlname = @sc.getgroupattr(name, 'xmlname') || name - mldir = "ml/"+name + mldir = @mlbasedir+"/"+name to = @sc.getgroupattr(name, 'mladdress') || defaultmladdress(name) if @sc.getgroupattr(name, 'fromhack') fromhack = to end spooling = @opt['mlspooling'] + returnpath = to.sub("@", @mailadmsuffix+"-@") else # Run as p2p mail - bracket = "NONE" # Throught Subject - user = @params['user'] || ENV['SENDER'] - if @sc.ismembersemail(user) - sender = defaultmladdress(quoted(user)) + # To: should be Destination user name + # Return-path: should be PREFIX-USER@DOMAIN or $SENDER(not member) + bracket = "NONE" # Through Subject + if user + returnpath = defaultmladdress(quoted(user)) else - sender = user + returnpath = ENV['SENDER'] end ###fromhack = sprintf("%s <%s>", nick, sender) - fromhack = sender + fromhack = returnpath xmlname = name to = name spooling = mldir = nil end - returnpath = to.sub("@", @mailadmsuffix+"-@") adminaddr = to.sub("@", @mailadmsuffix+"@") - subj = @params['subject'] || "Message from "+@myname if bracket == "NONE" sjtag = "" tagre = nil @@ -2852,36 +2945,39 @@ gsub("%i", Regexp.quote(name)). gsub(/%(\d*)c/, '\d+') tagre = Regexp.new(tagpt) - subj = sjtag.strip+" "+subj.gsub(Regexp.new(tagpt), "") + if !viamail + subj = sjtag.strip+" "+subj.gsub(Regexp.new(tagpt), "") + end end if viamail then body = tagify_subj(STDIN.readlines, sjtag, tagre, fromhack).join elsif fromhack # via http - from = rewritefrom(@params['user'], nick, groupmode ? to : sender) + from = rewritefrom(user, nick, groupmode ? to : returnpath) end header = { "X-ML-Driver" => ($hgid || @myname), "X-ML-Driver-URI" => $myurl, + "Return-path" => returnpath } if groupmode header["Reply-to"] = to header["X-ML-Name"] = xmlname header["X-ML-URI"] = sprintf("%s?-groupman+%s", @opt['url'], name) - header["Return-path"] = returnpath end Dir.chdir @mydir - if groupmode - rcpts = if grepgroup(name) + if groupmode # (#includeself) + rcpts = if viamail @sc.members(name) else - [name] - end.collect {|u| mailaddress(u, name).split(/,\s*|\s+/)}.flatten + @sc.members(name) + [user] + end.collect {|u| + mailaddress(u, name).split(/,\s*|\s+/)}.flatten.uniq else rcpts = @sc.mailaddress(name).split(/,\s*|\s+/).flatten rcpts += @sc.mailaddress(user).split(/,\s*|\s+/).flatten # +sender end - ENV["QMAILINJECT"] = "r" # for ML mode, use verp - # + # ENV["QMAILINJECT"] = "r" # for ML mode, use verp + # For vodafone, QMAILINJECT=r doesn't work correctly # On mail mode, check if sender can send message to list. if viamail && @sc.getgroupattr(name, 'limitsender') s = ENV['SENDER'] @@ -2907,6 +3003,7 @@ @O.print @H.elementln("h1"){msg('sendall_done')} @O.print @H.p(sprintf(msg('sendall_head'), nickname(name))+" "+msg('done')) + @O.print @H.elementln("pre"){extract_report[1]} link2home() @O.print footer() return true @@ -2930,8 +3027,12 @@ @O.print @H.elementln("h2") { sprintf(msg('sendall_head'), nickname(name)) } - list = groupmode ? @sc.members(name) : [name, user] - @O.print @H.p(sprintf("%s: %s", msg('member'), + if groupmode # (#includeself) + list = @sc.members(name) # +user + else + list = [name, user] + end + @O.print @H.p(sprintf("%s: %s", msg('rcptto'), list.collect {|u| @H.element("abbr", "title"=>u){ @sc.nickname(u) @@ -2939,15 +3040,31 @@ }.join(",\n"))) @O.print @H.p(sprintf("(total %d)", list.length))+"\n" @O.print \ - @H.elementln("form", {'action' => @myname+'?-list', 'method'=>"POST"}) { + @H.elementln("form", { + 'action' => @myname+'?-list', 'method'=>"POST", + 'enctype' => "multipart/form-data"}) { @H.elementln("table"){ @H.elementln("tr"){ + @H.element("td"){"To"} + \ + @H.element("td"){ + @H.element("code"){ + groupmode ? defaultmladdress(quoted(name)) : @sc.nickname(name) + } + } + } + \ + @H.elementln("tr"){ @H.element("td"){"Subject"} + \ @H.element("td"){ @H.text("subject", "", 40, 128) } } + \ @H.elementln("tr"){ + @H.element("td"){"Attachment"} + \ + @H.element("td"){ + @H.element("input", "name"=>"attachment", "type"=>"file", 'multiple'=>true){} + } + } + \ + @H.elementln("tr"){ @H.element("td"){ msg('body') } + \ @@ -4099,15 +4216,45 @@ query = ARGV.join("&") end - for unit in query.split(/\&/) - if /^([a-z][-_0-9@%a-z.]*)=(.*)/i =~ unit - key, value = $1, $2 - #value.gsub!(/%(..)/){[$1.hex].pack("c")} # これでURLデコードが一発 - decode!(value) - decode!(key) - value = Kconv::toeuc(value) # EUCに変換 - printf "[%s] = %s\n", key, value if $DEBUG - argument[key] = value + if ENV['CONTENT_TYPE'] && + %r,multipart/form-data.*boundary=(.*),i =~ ENV['CONTENT_TYPE'] + boundary = $1 + query.split("\r\n--"+boundary).each {|unit| + if /Content-Disposition.*\bname=([\'\"])?(\S*)\1/i =~ unit + argument.has_key?(key=$2) or argument[key]=[] + newvalue = Hash.new + if /^Content.*filename=([\'\"])?(\S*)\1/i =~ unit + newvalue['filename'] = $2 + end + newvalue['value'] = unit.sub(/.*\r\n\r\n/m, "") + if /^Content-type:\s*(\S*)/i =~ unit + newvalue['content-type'] = $1 + else + newvalue['value'].gsub!("\r\n", "\n") + end + argument[key] << newvalue + end + } + if $DEBUG + open("/tmp/body", "w"){|x| + x.write query.split("\r\n--"+boundary+"\r\n")[-1] + argument.each{|k,v| + x.printf(" %s => %s\n", k.inspect, v.inspect)} + x.printf("boundary=\n%s\n",boundary) + x.printf("ENV=%s\n",ENV.inspect) + x.write query} + end + else + for unit in query.split(/\&/) + if /^([a-z][-_0-9@%a-z.]*)=(.*)/i =~ unit + key, value = $1, $2 + #value.gsub!(/%(..)/){[$1.hex].pack("c")} # これでURLデコードが一発 + decode!(value) + decode!(key) + value = Kconv::toeuc(value) # EUCに変換 + printf "[%s] = %s\n", key, value if $DEBUG + argument[key] = value + end end end argument