#encoding: UTF-8 =begin /*************************************************************************** * 2023-2024, Michael Uplawski * * This program is free software; you can redistribute it and/or modify * * it under the terms of the WTFPL 2.0 or later, see * * http://www.wtfpl.net/about/ * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * * ***************************************************************************/ =end require_relative 'basic_logging' require_relative 'configuration' require_relative 'newsgroups' # an object of this class represents the headers of a news-article class Headers include BasicLogging # read the headers from the article def initialize(article_text) @config = Configuration.instance line = nil # transform the article to an array. debug('before split, article_text is : ' << article_text) line_array = article_text.split($LN) debug('after split, line_array is : ' << line_array.inspect) # find the first empty line end_index = line_array.index {|ele| ele.strip == ''} # keep the preceding lines. @lines = line_array.slice!(0, end_index) debug('headers: ' << @lines.to_s) # headername: headervalue @headers = {} # fill the headers Hash from the header-lines. # headers may have been line-wrapped. cur_header = nil @lines.each do |l| # has the header been wrapped? if !l.start_with?(/\s+/) # header is all before the first colon begin cur_header = l.match(/^(.*?):/)[1].to_sym rescue Exception => ex error ("Cannot match a header in line " << l << "(" << ex.message << ")") exit false; end # Consider the two following fixes as preliminary until proven. # Getting older, this kind of problem occupies me a lot more than # it should. # # value is all after the first colon # BUGGY: val = l.match(/:(.*)/)[1].strip # BUGFIX 3/2024, use lstrip val = l.match(/:(.*)/)[1].lstrip else # start_with?(' ') # a wrapped value is not devided # BUGGY: val = l.strip # BUGFIX 3/2024 val = l end # add value to the existing if cur_header && @headers[cur_header] @headers[cur_header] += val else # or add a new value @headers[cur_header] = val end #@headers[l.match(/^(.*?):/)[1].to_sym] = l.match(/:(.*)/)[1].strip # h = l.split(':') # @headers[h[0].strip.to_sym] = h[1...h.size].join(':').strip end debug('headers are ' << @headers.to_s) @newsgroups = Newsgroups.new(header(:Newsgroups)) debug('Newsgroups is ' << @newsgroups.inspect) end # returns the value of header 'name' def header(name) # name must be a symbol. if name.respond_to?(:to_sym) @headers[name] else error(name.to_s << ' is not a symbol!') nil end end # Modify headers, if need be. def update() no_archive = @newsgroups.no_archive debug('no_archive should be set now : ' << no_archive.to_s) if no_archive @headers["X-No-Archive".to_sym] = no_archive @headers["Archive".to_sym] = 'no' end if @config.CUSTOM_HEADERS ch = @config.CUSTOM_HEADERS debug('setting custom headers : ' << ch.inspect) @config.CUSTOM_HEADERS.each do |pair| ch = pair.split(':') hn = ch[0].strip hv = ch[1].strip # Ensure header is ascii only if hv.ascii_only? && hn.ascii_only? # <---------- special treatment Post-Processor ----------> hv << ' ' << PROGVERSION.to_s if hn == 'X-Post-Processor' && hv == 'flnews_post_proc' # >----------< @headers[hn.to_sym] = hv else warn "Custom header [#{hn}:#{hv}] should be ASCII only! Header is ignored!" end end @headers.compact! end debug('updated headers are ' << @headers.inspect) end # remove a header def remove(name) @headers.delete(name) if @headers[name] end # basically a replacement for header(name), above. # But you can call self.Newsgroups or self.From etc. def method_missing(method, args = nil) return @headers[method] if @headers[method] error("unknown symbol '#{method}'") end # return the headers as a String. def join htext = '' @headers.each_pair {|h, t| htext << h.to_s << ': ' << t << $LN } debug('joined headers: ' << htext) htext end attr_reader :lines, :newsgroups end # EOF