#encoding: ASCII
require 'barby/barcode'

module Barby

  class Code39 < Barcode1D

    WIDE   = W = true
    NARROW = N = false

    ENCODINGS = {
      ' ' => [N,W,W,N,N,N,W,N,N], '$' => [N,W,N,W,N,W,N,N,N],
      '%' => [N,N,N,W,N,W,N,W,N], '+' => [N,W,N,N,N,W,N,W,N],
      '-' => [N,W,N,N,N,N,W,N,W], '.' => [W,W,N,N,N,N,W,N,N],
      '/' => [N,W,N,W,N,N,N,W,N], '0' => [N,N,N,W,W,N,W,N,N],
      '1' => [W,N,N,W,N,N,N,N,W], '2' => [N,N,W,W,N,N,N,N,W],
      '3' => [W,N,W,W,N,N,N,N,N], '4' => [N,N,N,W,W,N,N,N,W],
      '5' => [W,N,N,W,W,N,N,N,N], '6' => [N,N,W,W,W,N,N,N,N],
      '7' => [N,N,N,W,N,N,W,N,W], '8' => [W,N,N,W,N,N,W,N,N],
      '9' => [N,N,W,W,N,N,W,N,N], 'A' => [W,N,N,N,N,W,N,N,W],
      'B' => [N,N,W,N,N,W,N,N,W], 'C' => [W,N,W,N,N,W,N,N,N],
      'D' => [N,N,N,N,W,W,N,N,W], 'E' => [W,N,N,N,W,W,N,N,N],
      'F' => [N,N,W,N,W,W,N,N,N], 'G' => [N,N,N,N,N,W,W,N,W],
      'H' => [W,N,N,N,N,W,W,N,N], 'I' => [N,N,W,N,N,W,W,N,N],
      'J' => [N,N,N,N,W,W,W,N,N], 'K' => [W,N,N,N,N,N,N,W,W],
      'L' => [N,N,W,N,N,N,N,W,W], 'M' => [W,N,W,N,N,N,N,W,N],
      'N' => [N,N,N,N,W,N,N,W,W], 'O' => [W,N,N,N,W,N,N,W,N],
      'P' => [N,N,W,N,W,N,N,W,N], 'Q' => [N,N,N,N,N,N,W,W,W],
      'R' => [W,N,N,N,N,N,W,W,N], 'S' => [N,N,W,N,N,N,W,W,N],
      'T' => [N,N,N,N,W,N,W,W,N], 'U' => [W,W,N,N,N,N,N,N,W],
      'V' => [N,W,W,N,N,N,N,N,W], 'W' => [W,W,W,N,N,N,N,N,N],
      'X' => [N,W,N,N,W,N,N,N,W], 'Y' => [W,W,N,N,W,N,N,N,N],
      'Z' => [N,W,W,N,W,N,N,N,N]
    }

    #In extended mode, each character is replaced with two characters from the "normal" encoding
    EXTENDED_ENCODINGS = {
      "\000" => '%U',    " " => " ",     "@"  => "%V",    "`" =>    "%W",
      "\001" => '$A',    "!" => "/A",    "A"  => "A",     "a" =>    "+A",
      "\002" => '$B',    '"' => "/B",    "B"  => "B",     "b" =>    "+B",
      "\003" => '$C',    "#" => "/C",    "C"  => "C",     "c" =>    "+C",
      "\004" => '$D',    "$" => "/D",    "D"  => "D",     "d" =>    "+D",
      "\005" => '$E',    "%" => "/E",    "E"  => "E",     "e" =>    "+E",
      "\006" => '$F',    "&" => "/F",    "F"  => "F",     "f" =>    "+F",
      "\007" => '$G',    "'" => "/G",    "G"  => "G",     "g" =>    "+G",
      "\010" => '$H',    "(" => "/H",    "H"  => "H",     "h" =>    "+H",
      "\011" => '$I',    ")" => "/I",    "I"  => "I",     "i" =>    "+I",
      "\012" => '$J',    "*" => "/J",    "J"  => "J",     "j" =>    "+J",
      "\013" => '$K',    "+" => "/K",    "K"  => "K",     "k" =>    "+K",
      "\014" => '$L',    "," => "/L",    "L"  => "L",     "l" =>    "+L",
      "\015" => '$M',    "-" => "-",     "M"  => "M",     "m" =>    "+M",
      "\016" => '$N',    "." => ".",     "N"  => "N",     "n" =>    "+N",
      "\017" => '$O',    "/" => "/O",    "O"  => "O",     "o" =>    "+O",
      "\020" => '$P',    "0" => "0",     "P"  => "P",     "p" =>    "+P",
      "\021" => '$Q',    "1" => "1",     "Q"  => "Q",     "q" =>    "+Q",
      "\022" => '$R',    "2" => "2",     "R"  => "R",     "r" =>    "+R",
      "\023" => '$S',    "3" => "3",     "S"  => "S",     "s" =>    "+S",
      "\024" => '$T',    "4" => "4",     "T"  => "T",     "t" =>    "+T",
      "\025" => '$U',    "5" => "5",     "U"  => "U",     "u" =>    "+U",
      "\026" => '$V',    "6" => "6",     "V"  => "V",     "v" =>    "+V",
      "\027" => '$W',    "7" => "7",     "W"  => "W",     "w" =>    "+W",
      "\030" => '$X',    "8" => "8",     "X"  => "X",     "x" =>    "+X",
      "\031" => '$Y',    "9" => "9",     "Y"  => "Y",     "y" =>    "+Y",
      "\032" => '$Z',    ":" => "/Z",    "Z"  => "Z",     "z" =>    "+Z",
      "\033" => '%A',    ";" => "%F",    "["  => "%K",    "{" =>    "%P",
      "\034" => '%B',    "<" => "%G",    "\\" => "%L",    "|" =>    "%Q",
      "\035" => '%C',    "=" => "%H",    "]"  => "%M",    "}" =>    "%R",
      "\036" => '%D',    ">" => "%I",    "^"  => "%N",    "~" =>    "%S",
      "\037" => '%E',    "?" => "%J",    "_"  => "%O",    "\177" => "%T"
    }

    CHECKSUM_VALUES = {
      '0' => 0,   '1' => 1,   '2' => 2,   '3' => 3,
      '4' => 4,   '5' => 5,   '6' => 6,   '7' => 7,
      '8' => 8,   '9' => 9,   'A' => 10,  'B' => 11,
      'C' => 12,  'D' => 13,  'E' => 14,  'F' => 15,
      'G' => 16,  'H' => 17,  'I' => 18,  'J' => 19,
      'K' => 20,  'L' => 21,  'N' => 23,  'M' => 22,
      'O' => 24,  'P' => 25,  'Q' => 26,  'R' => 27,
      'S' => 28,  'T' => 29,  'U' => 30,  'V' => 31,
      'W' => 32,  'X' => 33,  'Y' => 34,  'Z' => 35,
      '-' => 36,  '.' => 37,  ' ' => 38,  '$' => 39,
      '/' => 40,  '+' => 41,  '%' => 42
    }

    START_ENCODING = [N,W,N,N,W,N,W,N,N] # *
    STOP_ENCODING  = [N,W,N,N,W,N,W,N,N] # *

    attr_accessor :data, :extended, :include_checksum
    attr_writer :spacing, :narrow_width, :wide_width

    # Do not surround "data" with the mandatory "*" as is this is done automically for you.
    # So instead of passing "*123456*" as "data", just pass "123456".
    def initialize(data, extended=false)
      self.data = data
      self.extended = extended
      raise(ArgumentError, "data is not valid (extended=#{extended?})") unless valid?
      yield self if block_given?
    end

    #Returns the characters that were passed in, no matter it they're part of
    #the extended charset or if they're already encodable, "normal" characters
    def raw_characters
      data.split(//)
    end

    #Returns the encodable characters. If extended mode is enabled, each character will
    #first be replaced by two characters from the encodable charset
    def characters
      chars = raw_characters
      extended ? chars.map{|c| EXTENDED_ENCODINGS[c].split(//) }.flatten : chars
    end

    def characters_with_checksum
      characters + [checksum_character]
    end

    def encoded_characters
      characters.map{|c| encoding_for(c) }
    end

    def encoded_characters_with_checksum
      encoded_characters + [checksum_encoding]
    end

    #The data part of the encoding (no start+stop characters)
    def data_encoding
      encoded_characters.join(spacing_encoding)
    end

    def data_encoding_with_checksum
      encoded_characters_with_checksum.join(spacing_encoding)
    end

    def encoding
      return encoding_with_checksum if include_checksum?
      start_encoding+spacing_encoding+data_encoding+spacing_encoding+stop_encoding
    end

    def encoding_with_checksum
      start_encoding+spacing_encoding+data_encoding_with_checksum+spacing_encoding+stop_encoding
    end

    #Checksum is optional
    def checksum
      characters.inject(0) do |sum,char|
        sum + CHECKSUM_VALUES[char]
      end % 43
    end

    def checksum_character
      CHECKSUM_VALUES.invert[checksum]
    end

    def checksum_encoding
      encoding_for(checksum_character)
    end

    #Set include_checksum to true to make +encoding+ include the checksum
    def include_checksum?
      include_checksum
    end

    #Takes an array of WIDE/NARROW values and returns the string representation for
    #those bars and spaces, using wide_width and narrow_width
    def encoding_for_bars(*bars_and_spaces)
      bar = false
      bars_and_spaces.flatten.map do |width|
        bar = !bar
        (bar ? '1' : '0') * (width == WIDE ? wide_width : narrow_width)
      end.join
    end

    #Returns the string representation for a single character
    def encoding_for(character)
      encoding_for_bars(ENCODINGS[character])
    end

    #Spacing between the characters in xdims. Spacing will be inserted
    #between each character in the encoding
    def spacing
      @spacing ||= 1
    end

    def spacing_encoding
      '0' * spacing
    end

    def narrow_width
      @narrow_width ||= 1
    end

    def wide_width
      @wide_width ||= 2
    end

    def extended?
      extended
    end

    def start_encoding
      encoding_for_bars(START_ENCODING)
    end

    def stop_encoding
      encoding_for_bars(STOP_ENCODING)
    end

    def valid?
      if extended?
        raw_characters.all?{|c| EXTENDED_ENCODINGS.include?(c) }
      else
        raw_characters.all?{|c| ENCODINGS.include?(c) }
      end
    end

    def to_s
      data
    end

  end

end


require 'barby/outputter'
require 'chunky_png'

module Barby

  #Renders the barcode to a PNG image using chunky_png (gem install chunky_png)
  #
  #Registers the to_png, to_datastream and to_canvas methods
  class PngOutputter < Outputter

    register :to_png, :to_image, :to_datastream

    attr_writer :xdim, :ydim, :width, :height, :margin

    def initialize(*)
      super
      @xdim, @height, @margin = nil
    end

    #Creates a PNG::Canvas object and renders the barcode on it
    def to_image(opts={})
      with_options opts do
        canvas = ChunkyPNG::Image.new(full_width, full_height, ChunkyPNG::Color::WHITE)

        if barcode.two_dimensional?
          x, y = margin, margin
          booleans.each do |line|
            line.each do |bar|
              if bar
                x.upto(x+(xdim-1)) do |xx|
                  y.upto y+(ydim-1) do |yy|
                    canvas[xx,yy] = ChunkyPNG::Color::BLACK
                  end
                end
              end
              x += xdim
            end
            y += ydim
            x = margin
          end
        else
          x, y = margin, margin
          booleans.each do |bar|
            if bar
              x.upto(x+(xdim-1)) do |xx|
                y.upto y+(height-1) do |yy|
                  canvas[xx,yy] = ChunkyPNG::Color::BLACK
                end
              end
            end
            x += xdim
          end
        end

        canvas
      end
    end

    #Create a ChunkyPNG::Datastream containing the barcode image
    #
    # :constraints - Value is passed on to ChunkyPNG::Image#to_datastream
    #                E.g. to_datastream(:constraints => {:color_mode => ChunkyPNG::COLOR_GRAYSCALE})
    def to_datastream(*a)
      constraints = a.first && a.first[:constraints] ? [a.first[:constraints]] : []
      to_image(*a).to_datastream(*constraints)
    end

    #Renders the barcode to a PNG image
    def to_png(*a)
      to_datastream(*a).to_s
    end

    def width
      length * xdim
    end

    def height
      barcode.two_dimensional? ? (ydim * encoding.length) : (@height || 100)
    end

    def full_width
      width + (margin * 2)
    end

    def full_height
      height + (margin * 2)
    end

    def xdim
      @xdim || 1
    end

    def ydim
      @ydim || xdim
    end

    def margin
      @margin || 10
    end

    def length
      barcode.two_dimensional? ? encoding.first.length : encoding.length
    end

  end

end


#encoding: ASCII
require 'barby/barcode'

module Barby

  #Code 128 barcodes
  #
  #Note that you must provide a type for each object, either by passing a string
  #as a second parameter to Code128.new or by instantiating one of the child classes.
  #
  #You can switch type by using the CODEA, CODEB and CODEC characters:
  #
  # "\305" => A
  # "\306" => B
  # "\307" => C
  #
  #As an example, here's one that starts out as type A and then switches to B and then C:
  #
  #  Code128A.new("ABC123\306def\3074567")
  #
  #
  #GS1-128/EAN-128/UCC-128
  #
  #To make a GS1-128 code, prefix the data with FNC1 and the Application Identifier:
  #
  #  #AI=00, data=12345
  #  Code128.new("#{Code128::FNC1}0012345")
  class Code128 < Barcode1D

    FNC1 = "\xc1"
    FNC2 = "\xc2"
    FNC3 = "\xc3"
    FNC4 = "\xc4"
    CODEA = "\xc5"
    CODEB = "\xc6"
    CODEC = "\xc7"
    SHIFT = "\xc8"
    STARTA = "\xc9"
    STARTB = "\xca"
    STARTC = "\xcb"

    STOP = '11000111010'
    TERMINATE = '11'

    ENCODINGS = {
      0 => "11011001100", 1 => "11001101100", 2 => "11001100110",
      3 => "10010011000", 4 => "10010001100", 5 => "10001001100",
      6 => "10011001000", 7 => "10011000100", 8 => "10001100100",
      9 => "11001001000", 10 => "11001000100", 11 => "11000100100",
      12 => "10110011100", 13 => "10011011100", 14 => "10011001110",
      15 => "10111001100", 16 => "10011101100", 17 => "10011100110",
      18 => "11001110010", 19 => "11001011100", 20 => "11001001110",
      21 => "11011100100", 22 => "11001110100", 23 => "11101101110",
      24 => "11101001100", 25 => "11100101100", 26 => "11100100110",
      27 => "11101100100", 28 => "11100110100", 29 => "11100110010",
      30 => "11011011000", 31 => "11011000110", 32 => "11000110110",
      33 => "10100011000", 34 => "10001011000", 35 => "10001000110",
      36 => "10110001000", 37 => "10001101000", 38 => "10001100010",
      39 => "11010001000", 40 => "11000101000", 41 => "11000100010",
      42 => "10110111000", 43 => "10110001110", 44 => "10001101110",
      45 => "10111011000", 46 => "10111000110", 47 => "10001110110",
      48 => "11101110110", 49 => "11010001110", 50 => "11000101110",
      51 => "11011101000", 52 => "11011100010", 53 => "11011101110",
      54 => "11101011000", 55 => "11101000110", 56 => "11100010110",
      57 => "11101101000", 58 => "11101100010", 59 => "11100011010",
      60 => "11101111010", 61 => "11001000010", 62 => "11110001010",
      63 => "10100110000", 64 => "10100001100", 65 => "10010110000",
      66 => "10010000110", 67 => "10000101100", 68 => "10000100110",
      69 => "10110010000", 70 => "10110000100", 71 => "10011010000",
      72 => "10011000010", 73 => "10000110100", 74 => "10000110010",
      75 => "11000010010", 76 => "11001010000", 77 => "11110111010",
      78 => "11000010100", 79 => "10001111010", 80 => "10100111100",
      81 => "10010111100", 82 => "10010011110", 83 => "10111100100",
      84 => "10011110100", 85 => "10011110010", 86 => "11110100100",
      87 => "11110010100", 88 => "11110010010", 89 => "11011011110",
      90 => "11011110110", 91 => "11110110110", 92 => "10101111000",
      93 => "10100011110", 94 => "10001011110", 95 => "10111101000",
      96 => "10111100010", 97 => "11110101000", 98 => "11110100010",
      99 => "10111011110", 100 => "10111101110", 101 => "11101011110",
      102 => "11110101110", 103 => "11010000100", 104 => "11010010000",
      105 => "11010011100"
    }

    VALUES = {
      'A' => {
        0 => " ",      1 => "!",        2 => "\"",
        3 => "#",       4 => "$",        5 => "%",
        6 => "&",       7 => "'",        8 => "(",
        9 => ")",       10 => "*",       11 => "+",
        12 => ",",      13 => "-",       14 => ".",
        15 => "/",      16 => "0",       17 => "1",
        18 => "2",      19 => "3",       20 => "4",
        21 => "5",      22 => "6",       23 => "7",
        24 => "8",      25 => "9",       26 => ":",
        27 => ";",      28 => "<",       29 => "=",
        30 => ">",      31 => "?",       32 => "@",
        33 => "A",      34 => "B",       35 => "C",
        36 => "D",      37 => "E",       38 => "F",
        39 => "G",      40 => "H",       41 => "I",
        42 => "J",      43 => "K",       44 => "L",
        45 => "M",      46 => "N",       47 => "O",
        48 => "P",      49 => "Q",       50 => "R",
        51 => "S",      52 => "T",       53 => "U",
        54 => "V",      55 => "W",       56 => "X",
        57 => "Y",      58 => "Z",       59 => "[",
        60 => "\\",     61 => "]",       62 => "^",
        63 => "_",      64 => "\000",    65 => "\001",
        66 => "\002",   67 => "\003",    68 => "\004",
        69 => "\005",   70 => "\006",    71 => "\a",
        72 => "\b",     73 => "\t",      74 => "\n",
        75 => "\v",     76 => "\f",      77 => "\r",
        78 => "\016",   79 => "\017",    80 => "\020",
        81 => "\021",   82 => "\022",    83 => "\023",
        84 => "\024",   85 => "\025",    86 => "\026",
        87 => "\027",   88 => "\030",    89 => "\031",
        90 => "\032",   91 => "\e",      92 => "\034",
        93 => "\035",   94 => "\036",    95 => "\037",
        96 => FNC3,     97 => FNC2,      98 => SHIFT,
        99 => CODEC,    100 => CODEB,    101 => FNC4,
        102 => FNC1,    103 => STARTA,   104 => STARTB,
        105 => STARTC
      }.invert,

      'B' => {
        0 => " ", 1 => "!", 2 => "\"", 3 => "#", 4 => "$", 5 => "%",
        6 => "&", 7 => "'", 8 => "(", 9 => ")", 10 => "*", 11 => "+",
        12 => ",", 13 => "-", 14 => ".", 15 => "/", 16 => "0", 17 => "1",
        18 => "2", 19 => "3", 20 => "4", 21 => "5", 22 => "6", 23 => "7",
        24 => "8", 25 => "9", 26 => ":", 27 => ";", 28 => "<", 29 => "=",
        30 => ">", 31 => "?", 32 => "@", 33 => "A", 34 => "B", 35 => "C",
        36 => "D", 37 => "E", 38 => "F", 39 => "G", 40 => "H", 41 => "I",
        42 => "J", 43 => "K", 44 => "L", 45 => "M", 46 => "N", 47 => "O",
        48 => "P", 49 => "Q", 50 => "R", 51 => "S", 52 => "T", 53 => "U",
        54 => "V", 55 => "W", 56 => "X", 57 => "Y", 58 => "Z", 59 => "[",
        60 => "\\", 61 => "]", 62 => "^", 63 => "_", 64 => "`", 65 => "a",
        66 => "b", 67 => "c", 68 => "d", 69 => "e", 70 => "f", 71 => "g",
        72 => "h", 73 => "i", 74 => "j", 75 => "k", 76 => "l", 77 => "m",
        78 => "n", 79 => "o", 80 => "p", 81 => "q", 82 => "r", 83 => "s",
        84 => "t", 85 => "u", 86 => "v", 87 => "w", 88 => "x", 89 => "y",
        90 => "z", 91 => "{", 92 => "|", 93 => "}", 94 => "~", 95 => "\177",
        96 => FNC3, 97 => FNC2, 98 => SHIFT, 99 => CODEC, 100 => FNC4,
        101 => CODEA, 102 => FNC1, 103 => STARTA, 104 => STARTB,
        105 => STARTC,
      }.invert,

      'C' => {
        0 => "00", 1 => "01", 2 => "02", 3 => "03", 4 => "04", 5 => "05",
        6 => "06", 7 => "07", 8 => "08", 9 => "09", 10 => "10", 11 => "11",
        12 => "12", 13 => "13", 14 => "14", 15 => "15", 16 => "16", 17 => "17",
        18 => "18", 19 => "19", 20 => "20", 21 => "21", 22 => "22", 23 => "23",
        24 => "24", 25 => "25", 26 => "26", 27 => "27", 28 => "28", 29 => "29",
        30 => "30", 31 => "31", 32 => "32", 33 => "33", 34 => "34", 35 => "35",
        36 => "36", 37 => "37", 38 => "38", 39 => "39", 40 => "40", 41 => "41",
        42 => "42", 43 => "43", 44 => "44", 45 => "45", 46 => "46", 47 => "47",
        48 => "48", 49 => "49", 50 => "50", 51 => "51", 52 => "52", 53 => "53",
        54 => "54", 55 => "55", 56 => "56", 57 => "57", 58 => "58", 59 => "59",
        60 => "60", 61 => "61", 62 => "62", 63 => "63", 64 => "64", 65 => "65",
        66 => "66", 67 => "67", 68 => "68", 69 => "69", 70 => "70", 71 => "71",
        72 => "72", 73 => "73", 74 => "74", 75 => "75", 76 => "76", 77 => "77",
        78 => "78", 79 => "79", 80 => "80", 81 => "81", 82 => "82", 83 => "83",
        84 => "84", 85 => "85", 86 => "86", 87 => "87", 88 => "88", 89 => "89",
        90 => "90", 91 => "91", 92 => "92", 93 => "93", 94 => "94", 95 => "95",
        96 => "96", 97 => "97", 98 => "98", 99 => "99", 100 => CODEB, 101 => CODEA,
        102 => FNC1, 103 => STARTA, 104 => STARTB, 105 => STARTC
      }.invert
    }

    CONTROL_CHARACTERS = VALUES['A'].invert.values_at(*(64..95).to_a)

    attr_reader :type

    def initialize(data, type=nil)
      if type
        self.type = type
        self.data = "#{data}"
      else
        self.type, self.data = self.class.determine_best_type_for_data("#{data}")
      end
      raise ArgumentError, 'Data not valid' unless valid?
    end

    def type=(type)
      type = type.upcase
      raise ArgumentError, 'type must be A, B or C' unless type =~ /^[ABC]$/
      @type = type
    end

    def to_s
      full_data
    end

    def data
      @data
    end

    #Returns the data for this barcode plus that for the entire extra chain,
    #excluding all change codes
    def full_data
      data + full_extra_data
    end

    #Returns the data for this barcode plus that for the entire extra chain,
    #including all change codes prefixing each extra
    def full_data_with_change_codes
      data + full_extra_data_with_change_code
    end

    #Returns the full_data for the extra or an empty string if there is no extra
    def full_extra_data
      return '' unless extra
      extra.full_data
    end

    #Returns the full_data for the extra with the change code for the extra
    #prepended. If there is no extra, an empty string is returned
    def full_extra_data_with_change_code
      return '' unless extra
      change_code_for(extra) + extra.full_data_with_change_codes
    end

    #Set the data for this barcode. If the barcode changes
    #character set, an extra will be created.
    def data=(data)
      data, *extra = data.split(/([#{CODEA+CODEB+CODEC}])/n)
      @data = data || ''
      self.extra = extra.join unless extra.empty?
    end

    #An "extra" is present if the barcode changes character set. If
    #a 128A barcode changes to C, the extra will be an instance of
    #Code128C. Extras can themselves have an extra if the barcode
    #changes character set again. It's like a linked list, and when
    #there are no more extras, the barcode ends with that object.
    #Most barcodes probably don't change charsets and don't have extras.
    def extra
      return @extra if defined?(@extra)
      @extra = nil
    end

    #Set the extra for this barcode. The argument is a string starting with the
    #"change character set" symbol. The string may contain several character
    #sets, in which case the extra will itself have an extra.
    def extra=(extra)
      raise ArgumentError, "Extra must begin with \\305, \\306 or \\307" unless extra =~ /^[#{CODEA+CODEB+CODEC}]/n
      type, data = extra[0,1], extra[1..-1]
      @extra = class_for(type).new(data)
    end

    #Get an array of the individual characters for this barcode. Special
    #characters like FNC1 will be present. Characters from extras are not
    #present.
    def characters
      chars = data.split(//n)

      if type == 'C'
        result = []
        count = 0
        while count < chars.size
          if chars[count] =~ /^\d$/
            #If encountering a digit, next char/byte *must* be second digit in pair. I.e. if chars[count] is 5,
            #chars[count+1] must be /[0-9]/, otherwise it's not valid
            result << "#{chars[count]}#{chars[count+1]}"
            count += 2
          else
            result << chars[count]
            count += 1
          end
        end
        result
      else
        chars
      end
    end

    #Return the encoding of this barcode as a string of 1 and 0
    def encoding
      start_encoding+data_encoding+extra_encoding+checksum_encoding+stop_encoding
    end

    #Returns the encoding for the data part of this barcode, without any extras
    def data_encoding
      characters.map do |char|
        encoding_for char
      end.join
    end

    #Returns the data encoding of this barcode and extras.
    def data_encoding_with_extra_encoding
      data_encoding+extra_encoding
    end

    #Returns the data encoding of this barcode's extra and its
    #extra until the barcode ends.
    def extra_encoding
      return '' unless extra
      change_code_encoding_for(extra) + extra.data_encoding + extra.extra_encoding
    end

    #Calculate the checksum for the data in this barcode. The data includes
    #data from extras.
    def checksum
      pos = 0
      (numbers+extra_numbers).inject(start_num) do |sum,number|
        pos += 1
        sum + (number * pos)
      end % 103
    end

    #Get the encoding for the checksum
    def checksum_encoding
      encodings[checksum]
    end

  #protected

    #Returns the numeric values for the characters in the barcode in an array
    def numbers
      characters.map do |char|
        values[char]
      end
    end

    #Returns the numeric values for extras
    def extra_numbers
      return [] unless extra
      [change_code_number_for(extra)] + extra.numbers + extra.extra_numbers
    end

    def encodings
      ENCODINGS
    end

    #The start encoding starts the barcode
    def stop_encoding
      STOP+TERMINATE
    end

    #Find the encoding for the specified character for this barcode
    def encoding_for(char)
      encodings[values[char]]
    end

    def change_code_for_class(klass)
      {Code128A => CODEA, Code128B => CODEB, Code128C => CODEC}[klass]
    end

    #Find the character that changes the character set to the one
    #represented in +barcode+
    def change_code_for(barcode)
      change_code_for_class(barcode.class)
    end

    #Find the numeric value for the character that changes the character
    #set to the one represented in +barcode+
    def change_code_number_for(barcode)
      values[change_code_for(barcode)]
    end

    #Find the encoding to change to the character set in +barcode+
    def change_code_encoding_for(barcode)
      encodings[change_code_number_for(barcode)]
    end

    def class_for(character)
      self.class.class_for(character)
    end

    #Is the data in this barcode valid? Does a lookup of every character
    #and checks if it exists in the character set. An empty data string
    #will also be reported as invalid.
    def valid?
      characters.any? && characters.all?{|c| values.include?(c) }
    end

    def values
      VALUES[type]
    end

    def start_character
      case type
      when 'A' then STARTA
      when 'B' then STARTB
      when 'C' then STARTC
      end
    end

    def start_num
      values[start_character]
    end

    def start_encoding
      encodings[start_num]
    end

    CTRL_RE = /#{CONTROL_CHARACTERS.join('|')}/
    LOWR_RE = /[a-z]/
    DGTS_RE = /\d{4,}/

    # pairs of digits and FNC1 characters
    CODEC_CHARS_RE = /(?:\d{2}|#{FNC1}){2,}/

    class << self

      def class_for(character)
        case character
        when 'A' then Code128A
        when 'B' then Code128B
        when 'C' then Code128C
        when CODEA then Code128A
        when CODEB then Code128B
        when CODEC then Code128C
        end
      end

      #Insert code shift and switch characters where appropriate to get the
      #shortest encoding possible
      def apply_shortest_encoding_for_data(data)
        extract_codec(data).map do |block|
          if codec_segment?(block)
            "#{CODEC}#{block}"
          else
            if control_before_lowercase?(block)
              handle_code_a(block)
            else
              handle_code_b(block)
            end
          end
        end.join
      end

      def determine_best_type_for_data(data)
        data = apply_shortest_encoding_for_data(data)
        type = case data.slice!(0)
               when CODEA then 'A'
               when CODEB then 'B'
               when CODEC then 'C'
               end
        [type, data]
      end

    private

      #Extract all CODEC segments from the data. 4 or more evenly numbered contiguous digits.
      #
      #  #                                        C       A or B  C         A or B
      #  extract_codec("12345abc678910DEF11") => ["1234", "5abc", "678910", "DEF11"]
      def extract_codec(data)
        data.split(/
          (^(?:#{CODEC_CHARS_RE})) # matches digits that appear at the beginning of the barcode
          |
          ((?:#{CODEC_CHARS_RE})(?!\d)) # matches digits that appear later in the barcode
        /x).reject(&:empty?)
      end

      def codec_segment?(data)
        data =~ /\A#{CODEC_CHARS_RE}\Z/
      end

      def control_character?(char)
        char =~ CTRL_RE
      end

      def lowercase_character?(char)
        char =~ LOWR_RE
      end

      #Handle a Code A segment which may contain Code B parts, but may not
      #contain any Code C parts.
      def handle_code_a(data)
        indata = data.dup
        outdata = CODEA.dup #We know it'll be A
        while char = indata.slice!(0)
          if lowercase_character?(char) #Found a lower case character (Code B)
            if control_before_lowercase?(indata)
              outdata << SHIFT << char #Control character appears before a new lowercase, use shift
            else
              outdata << handle_code_b(char+indata) #Switch to Code B
              break
            end
          else
            outdata << char
          end
        end#while

        outdata
      end

      #Handle a Code B segment which may contain Code A parts, but may not
      #contain any Code C parts.
      def handle_code_b(data)
        indata = data.dup
        outdata = CODEB.dup #We know this is going to start with Code B
        while char = indata.slice!(0)
          if control_character?(char) #Found a control character (Code A)
            if control_before_lowercase?(indata)    #There is another control character before any lowercase, so
              outdata << handle_code_a(char+indata) #switch over to Code A.
              break
            else
              outdata << SHIFT << char #Can use a shift to only encode this char as Code A
            end
          else
            outdata << char
          end
        end#while

        outdata
      end

      #Test str to see if a control character (Code A) appears
      #before a lower case character (Code B).
      #
      #Returns true only if it contains a control character and a lower case
      #character doesn't appear before it.
      def control_before_lowercase?(str)
        ctrl = str =~ CTRL_RE
        char = str =~ LOWR_RE

        ctrl && (!char || ctrl < char)
      end

    end#class << self

  end#class Code128

  class Code128A < Code128

    def initialize(data)
      super(data, 'A')
    end

  end

  class Code128B < Code128

    def initialize(data)
      super(data, 'B')
    end

  end

  class Code128C < Code128

    def initialize(data)
      super(data, 'C')
    end

  end

end


require 'barby/barcode'

module Barby

  #Standard/Industrial 2 of 5, non-interleaved
  #
  #Checksum not included by default, to include it,
  #set include_checksum = true
  class Code25 < Barcode1D

    WIDE   = W = true
    NARROW = N = false

    START_ENCODING = [W,W,N]
    STOP_ENCODING  = [W,N,W]

    ENCODINGS = {
      0 => [N,N,W,W,N],
      1 => [W,N,N,N,W],
      2 => [N,W,N,N,W],
      3 => [W,W,N,N,N],
      4 => [N,N,W,N,W],
      5 => [W,N,W,N,N],
      6 => [N,W,W,N,N],
      7 => [N,N,N,W,W],
      8 => [W,N,N,W,N],
      9 => [N,W,N,W,N]
    }

    attr_accessor :include_checksum
    attr_writer :narrow_width, :wide_width, :space_width
    attr_reader :data

    def initialize(data)
      self.data = data
      @narrow_width, @wide_width, @space_width = nil
    end

    def data_encoding
      digit_encodings.join
    end

    def data_encoding_with_checksum
      digit_encodings_with_checksum.join
    end

    def encoding
      start_encoding+(include_checksum? ? data_encoding_with_checksum : data_encoding)+stop_encoding
    end

    def characters
      data.split(//)
    end

    def characters_with_checksum
      characters.push(checksum.to_s)
    end

    def digits
      characters.map{|c| c.to_i }
    end

    def digits_with_checksum
      digits.push(checksum)
    end

    def even_and_odd_digits
      alternater = false
      digits.reverse.partition{ alternater = !alternater }
    end

    def digit_encodings
      raise_invalid unless valid?
      digits.map{|d| encoding_for(d) }
    end
    alias character_encodings digit_encodings

    def digit_encodings_with_checksum
      raise_invalid unless valid?
      digits_with_checksum.map{|d| encoding_for(d) }
    end
    alias character_encodings_with_checksum digit_encodings_with_checksum

    #Returns the encoding for a single digit
    def encoding_for(digit)
      encoding_for_bars(ENCODINGS[digit])
    end

    #Generate encoding for an array of W,N
    def encoding_for_bars(*bars)
      wide, narrow, space = wide_encoding, narrow_encoding, space_encoding
      bars.flatten.inject '' do |enc,bar|
        enc + (bar == WIDE ? wide : narrow) + space
      end
    end

    def encoding_for_bars_without_end_space(*a)
      encoding_for_bars(*a).gsub(/0+$/, '')
    end

    #Mod10
    def checksum
      evens, odds = even_and_odd_digits
      sum = odds.inject(0){|s,d| s + d } + evens.inject(0){|s,d| s + (d*3) }
      sum %= 10
      sum.zero? ? 0 : 10-sum
    end

    def checksum_encoding
      encoding_for(checksum)
    end

    #The width of a narrow bar in xdims
    def narrow_width
      @narrow_width || 1
    end

    #The width of a wide bar in xdims
    #By default three times as wide as a narrow bar
    def wide_width
      @wide_width || narrow_width*3
    end

    #The width of the space between the bars in xdims
    #By default the same width as a narrow bar
    #
    #A space serves only as a separator for the bars,
    #there is no encoded meaning in them
    def space_width
      @space_width || narrow_width
    end

    #2 of 5 doesn't require a checksum, but you can include a Mod10 checksum
    #by setting +include_checksum+ to true
    def include_checksum?
      include_checksum
    end

    def data=(data)
      @data = "#{data}"
    end

    def start_encoding
      encoding_for_bars(START_ENCODING)
    end

    def stop_encoding
      encoding_for_bars_without_end_space(STOP_ENCODING)
    end

    def narrow_encoding
      '1' * narrow_width
    end

    def wide_encoding
      '1' * wide_width
    end

    def space_encoding
      '0' * space_width
    end

    def valid?
      data =~ /^[0-9]*$/
    end

    def to_s
      (include_checksum? ? characters_with_checksum : characters).join
    end

  private

    def raise_invalid
      raise ArgumentError, "data not valid"
    end

  end

end


require 'barby/outputter'
require 'cairo'
require 'stringio'

module Barby

  #Uses Cairo to render a barcode to a number of formats: PNG, PS, EPS, PDF and SVG
  #
  #Registers methods render_to_cairo_context, to_png, to_ps, to_eps, to_pdf and to_svg
  class CairoOutputter < Outputter

    register :render_to_cairo_context
    register :to_png

    if Cairo.const_defined?(:PSSurface)
      register :to_ps
      register :to_eps if Cairo::PSSurface.method_defined?(:eps=)
    end

    register :to_pdf if Cairo.const_defined?(:PDFSurface)
    register :to_svg if Cairo.const_defined?(:SVGSurface)

    attr_writer :x, :y, :xdim, :height, :margin

    def initialize(*)
      super
      @x, @y, @xdim, @height, @margin = nil
    end

    #Render the barcode onto a Cairo context
    def render_to_cairo_context(context, options={})
      if context.respond_to?(:have_current_point?) and
          context.have_current_point?
        current_x, current_y = context.current_point
      else
        current_x = x(options) || margin(options)
        current_y = y(options) || margin(options)
      end

      _xdim = xdim(options)
      _height = height(options)
      original_current_x = current_x
      context.save do
        context.set_source_color(:black)
        context.fill do
          if barcode.two_dimensional?
            boolean_groups.each do |groups|
              groups.each do |bar,amount|
                current_width = _xdim * amount
                if bar
                  context.rectangle(current_x, current_y, current_width, _xdim)
                end
                current_x += current_width
              end
              current_x = original_current_x
              current_y += _xdim
            end
          else
            boolean_groups.each do |bar,amount|
              current_width = _xdim * amount
              if bar
                context.rectangle(current_x, current_y, current_width, _height)
              end
              current_x += current_width
            end
          end
        end
      end

      context
    end

    #Render the barcode to a PNG image
    def to_png(options={})
      output_to_string_io do |io|
        Cairo::ImageSurface.new(options[:format],
                                full_width(options),
                                full_height(options)) do |surface|
          render(surface, options)
          surface.write_to_png(io)
        end
      end
    end

    #Render the barcode to a PS document
    def to_ps(options={})
      output_to_string_io do |io|
        Cairo::PSSurface.new(io,
                             full_width(options),
                             full_height(options)) do |surface|
          surface.eps = options[:eps] if surface.respond_to?(:eps=)
          render(surface, options)
        end
      end
    end

    #Render the barcode to an EPS document
    def to_eps(options={})
      to_ps(options.merge(:eps => true))
    end

    #Render the barcode to a PDF document
    def to_pdf(options={})
      output_to_string_io do |io|
        Cairo::PDFSurface.new(io,
                              full_width(options),
                              full_height(options)) do |surface|
          render(surface, options)
        end
      end
    end

    #Render the barcode to an SVG document
    def to_svg(options={})
      output_to_string_io do |io|
        Cairo::SVGSurface.new(io,
                              full_width(options),
                              full_height(options)) do |surface|
          render(surface, options)
        end
      end
    end

    def x(options={})
      @x || options[:x]
    end

    def y(options={})
      @y || options[:y]
    end

    def width(options={})
      (barcode.two_dimensional? ? encoding.first.length : encoding.length) * xdim(options)
    end

    def height(options={})
      if barcode.two_dimensional?
        encoding.size * xdim(options)
      else
        @height || options[:height] || 50
      end
    end

    def full_width(options={})
      width(options) + (margin(options) * 2)
    end

    def full_height(options={})
      height(options) + (margin(options) * 2)
    end

    def xdim(options={})
      @xdim || options[:xdim] || 1
    end

    def margin(options={})
      @margin || options[:margin] || 10
    end

  private

    def output_to_string_io
      io = StringIO.new
      yield(io)
      io.rewind
      io.read
    end

    def render(surface, options)
      context = Cairo::Context.new(surface)
      yield(context) if block_given?
      context.set_source_color(options[:background] || :white)
      context.paint
      render_to_cairo_context(context, options)
      context
    end

  end

end


require 'barby/barcode/code_25'

module Barby

  #Code 2 of 5 interleaved. Same as standard 2 of 5, but spaces are used
  #for encoding as well as the bars. Each pair of numbers get interleaved,
  #that is, the first is encoded in the bars and the second is encoded
  #in the spaced. This means an interleaved 2/5 barcode must have an even
  #number of digits.
  class Code25Interleaved < Code25

    START_ENCODING = [N,N,N,N]
    STOP_ENCODING =  [W,N,N]

    def digit_pairs(d=nil)
      (d || digits).inject [] do |ary,i|
        ary << [] if !ary.last || ary.last.size == 2
        ary.last << i
        ary
      end
    end

    def digit_pairs_with_checksum
      digit_pairs(digits_with_checksum)
    end

    def digit_encodings
      raise_invalid unless valid?
      digit_pairs.map{|p| encoding_for_pair(p) }
    end

    def digit_encodings_with_checksum
      digit_pairs_with_checksum.map{|p| encoding_for_pair(p) }
    end

    def encoding_for_pair(pair)
      bars, spaces = ENCODINGS[pair.first], ENCODINGS[pair.last]
      encoding_for_interleaved(bars.zip(spaces))
    end

    #Encodes an array of interleaved W or N bars and spaces
    #ex: [W,N,W,W,N,N] => "111011100010"
    def encoding_for_interleaved(*bars_and_spaces)
      bar = false#starts with bar
      bars_and_spaces.flatten.inject '' do |enc,bar_or_space|
        bar = !bar
        enc << (bar ? '1' : '0') * (bar_or_space == WIDE ? wide_width : narrow_width)
      end
    end

    def start_encoding
      encoding_for_interleaved(START_ENCODING)
    end

    def stop_encoding
      encoding_for_interleaved(STOP_ENCODING)
    end

    def valid?
      #                           When checksum is included, it's included when determining "evenness"
      super && digits.size % 2 == (include_checksum? ? 1 : 0)
    end

  end

end


# frozen_string_literal: true

require 'cms_scanner/browser/actions'
require 'cms_scanner/browser/options'

module CMSScanner
  # Singleton used to perform HTTP/HTTPS request to the target
  class Browser
    extend Actions

    # @param [ Hash ] parsed_options
    #
    # @return [ Void ]
    def initialize(parsed_options = {})
      self.throttle = 0

      load_options(parsed_options.dup)
    end

    private_class_method :new

    # @param [ Hash ] parsed_options
    #
    # @return [ Browser ] The instance
    def self.instance(parsed_options = {})
      @@instance ||= new(parsed_options)
    end

    def self.reset
      @@instance = nil
    end

    # @param [ String ] url
    # @param [ Hash ] params
    #
    # @return [ Typhoeus::Request ]
    def forge_request(url, params = {})
      Typhoeus::Request.new(url, request_params(params))
    end

    # @return [ Hash ]
    def typhoeus_to_browser_opts
      { connecttimeout: :connect_timeout, cache_ttl: :cache_ttl,
        proxy: :proxy, timeout: :request_timeout, cookiejar: :cookie_jar,
        cookiefile: :cookie_jar, cookie: :cookie_string }
    end

    # @return [ Hash ]
    def default_request_params
      params = {
        headers: { 'User-Agent' => user_agent }.merge(headers || {}),
        accept_encoding: 'gzip, deflate',
        method: :get
      }

      if disable_tls_checks
        # See http://curl.haxx.se/libcurl/c/CURLOPT_SSL_VERIFYHOST.html
        params[:ssl_verifypeer] = false
        params[:ssl_verifyhost] = 0
      end

      typhoeus_to_browser_opts.each do |typhoeus_opt, browser_opt|
        attr_value = public_send(browser_opt)
        params[typhoeus_opt] = attr_value unless attr_value.nil?
      end

      params[:proxyuserpwd] = "#{proxy_auth[:username]}:#{proxy_auth[:password]}" if proxy_auth
      params[:userpwd] = "#{http_auth[:username]}:#{http_auth[:password]}" if http_auth

      params[:headers]['Host'] = vhost if vhost

      params
    end

    # @param [ Hash ] params
    #
    # @return [ Hash ]
    def request_params(params = {})
      default_request_params.merge(params) do |key, oldval, newval|
        key == :headers ? oldval.merge(newval) : newval
      end
    end
  end
end


# frozen_string_literal: true

require 'cms_scanner/web_site'
require 'cms_scanner/target/platform'
require 'cms_scanner/target/server'
require 'cms_scanner/target/scope'
require 'cms_scanner/target/hashes'

module CMSScanner
  # Target to Scan
  class Target < WebSite
    include Server::Generic

    # @param [ String ] url
    # @param [ Hash ] opts
    # @option opts [ Array<PublicSuffix::Domain, String> ] :scope
    def initialize(url, opts = {})
      super(url, opts)

      scope << uri.host
      [*opts[:scope]].each { |s| scope << s }
    end

    # @param [ Hash ] opts
    #
    # @return [ Findings ]
    def interesting_findings(opts = {})
      @interesting_findings ||= NS::Finders::InterestingFindings::Base.find(self, opts)
    end

    # Weteher or not vulnerabilities have been found.
    # Used to set the exit code of the scanner
    # and it should be overriden in the implementation
    #
    # @return [ Boolean ]
    def vulnerable?
      raise NotImplementedError
    end

    # @return [ Regexp ] The pattern related to the target url, also matches escaped /, such as
    #                    in JSON JS data: http:\/\/t.com\/
    def url_pattern
      @url_pattern ||= Regexp.new(Regexp.escape(url).gsub(/https?/i, 'https?').gsub('/', '\\\\\?/'), Regexp::IGNORECASE)
    end

    # @param [ String ] xpath
    # @param [ Regexp ] pattern
    # @param [ Typhoeus::Response, String ] page
    #
    # @return [ Array<Array<MatchData, Nokogiri::XML::Element>> ]
    # @yield  [ MatchData, Nokogiri::XML::Element ]
    def xpath_pattern_from_page(xpath, pattern, page = nil)
      page    = NS::Browser.get(url(page)) unless page.is_a?(Typhoeus::Response)
      matches = []

      page.html.xpath(xpath).each do |node|
        next unless node.text.strip =~ pattern

        yield Regexp.last_match, node if block_given?

        matches << [Regexp.last_match, node]
      end

      matches
    end

    # @param [ Regexp ] pattern
    # @param [ Typhoeus::Response, String ] page
    #
    # @return [ Array<Array<MatchData, Nokogiri::XML::Comment>> ]
    # @yield  [ MatchData, Nokogiri::XML::Comment ]
    def comments_from_page(pattern, page = nil)
      xpath_pattern_from_page('//comment()', pattern, page) do |match, node|
        yield match, node if block_given?
      end
    end

    # @param [ Regexp ] pattern
    # @param [ Typhoeus::Response, String ] page
    #
    # @return [ Array<Array<MatchData, Nokogiri::XML::Element>> ]
    # @yield  [ MatchData, Nokogiri::XML::Element ]
    def javascripts_from_page(pattern, page = nil)
      xpath_pattern_from_page('//script', pattern, page) do |match, node|
        yield match, node if block_given?
      end
    end

    # @param [ Typhoeus::Response, String ] page
    # @param [ String ] xpath
    #
    # @yield [ Addressable::URI, Nokogiri::XML::Element ] The url and its associated tag
    #
    # @return [ Array<Addressable::URI> ] The absolute URIs detected in the response's body from the HTML tags
    def uris_from_page(page = nil, xpath = '//@href|//@src|//@data-src')
      page    = NS::Browser.get(url(page)) unless page.is_a?(Typhoeus::Response)
      found   = []

      page.html.xpath(xpath).each do |node|
        attr_value = node.text.to_s

        next unless attr_value && !attr_value.empty?

        node_uri = begin
                     uri.join(attr_value.strip)
                   rescue StandardError
                     # Skip potential malformed URLs etc.
                     next
                   end

        next unless node_uri.host

        yield node_uri, node.parent if block_given? && !found.include?(node_uri)

        found << node_uri
      end

      found.uniq
    end
  end
end


# frozen_string_literal: true

module CMSScanner
  # Scope system logic
  class Target < WebSite
    # @return [ Array<PublicSuffix::Domain, String> ]
    def scope
      @scope ||= Scope.new
    end

    # @param [ String, Addressable::URI ] url An absolute URL or URI
    #
    # @return [ Boolean ] true if the url given is in scope
    def in_scope?(url_or_uri)
      url_or_uri = Addressable::URI.parse(url_or_uri.strip) unless url_or_uri.is_a?(Addressable::URI)

      scope.include?(url_or_uri.host)
    rescue StandardError
      false
    end

    # @param [ Typhoeus::Response ] res
    # @param [ String ] xpath
    #
    # @yield [ Addressable::URI, Nokogiri::XML::Element ] The in scope url and its associated tag
    #
    # @return [ Array<Addressable::URI> ] The in scope absolute URIs detected in the response's body
    def in_scope_uris(res, xpath = '//@href|//@src|//@data-src')
      found = []

      uris_from_page(res, xpath) do |uri, tag|
        next unless in_scope?(uri)

        yield uri, tag if block_given?

        found << uri
      end

      found
    end

    # Similar to Target#url_pattern but considering the in scope domains as well
    #
    # @return [ Regexp ] The pattern related to the target url and in scope domains,
    #                    it also matches escaped /, such as in JSON JS data: http:\/\/t.com\/
    # rubocop:disable Metrics/AbcSize
    def scope_url_pattern
      return @scope_url_pattern if @scope_url_pattern

      domains = [uri.host + uri.path]

      domains += if scope.domains.empty?
                   [*scope.invalid_domains[1..-1]]
                 else
                   [*scope.domains[1..-1]].map(&:to_s) + scope.invalid_domains
                 end

      domains.map! { |d| Regexp.escape(d.gsub(%r{/$}, '')).gsub('\*', '.*').gsub('/', '\\\\\?/') }

      domains[0].gsub!(Regexp.escape(uri.host), Regexp.escape(uri.host) + '(?::\\d+)?') if uri.port

      @scope_url_pattern = %r{https?:\\?/\\?/(?:#{domains.join('|')})\\?/?}i
    end
    # rubocop:enable Metrics/AbcSize

    # Scope Implementation
    class Scope
      # @return [ Array<PublicSuffix::Domain> ] The valid domains in scope
      def domains
        @domains ||= []
      end

      # @return [ Array<String> ] The invalid domains in scope (such as IP addresses etc)
      def invalid_domains
        @invalid_domains ||= []
      end

      def <<(element)
        if PublicSuffix.valid?(element, ignore_private: true)
          domains << PublicSuffix.parse(element, ignore_private: true)
        else
          invalid_domains << element
        end
      end

      # @return [ Boolean ] Wether or not the host is in the scope
      def include?(host)
        if PublicSuffix.valid?(host, ignore_private: true)
          domain = PublicSuffix.parse(host, ignore_private: true)

          domains.each { |d| return true if domain.match(d) }
        else
          invalid_domains.each { |d| return true if host == d }
        end

        false
      end
    end
  end
end


# frozen_string_literal: true

module PublicSuffix
  # Monkey Patch to include the match logic
  class Domain
    # For Sanity
    def ==(other)
      name == other.name
    end

    # @return [ Boolean ]
    #
    def match(pattern)
      pattern = PublicSuffix.parse(pattern) unless pattern.is_a?(PublicSuffix::Domain)

      return name == pattern.name unless pattern.trd
      return false unless tld == pattern.tld && sld == pattern.sld

      matching_pattern?(pattern)
    end

    protected

    # @rturn [ Boolean ]
    def matching_pattern?(pattern)
      pattern_trds = pattern.trd.split('.')
      domain_trds  = trd.split('.')

      case pattern_trds.first
      when '*'
        pattern_trds[1..-1] == domain_trds[1..-1]
      when '**'
        pa = pattern_trds[1..-1]
        pa_size = pa.size

        domain_trds[domain_trds.size - pa_size, pa_size] == pa
      else
        name == pattern.name
      end
    end
  end
end


# frozen_string_literal: true

module CMSScanner
  # Controllers Container
  class Controllers < Array
    attr_reader :option_parser, :running

    # @param [ OptParsevalidator::OptParser ] options_parser
    def initialize(option_parser = OptParseValidator::OptParser.new(nil, 40))
      @option_parser = option_parser

      register_options_files
    end

    # Adds the potential option file paths to the option_parser
    def register_options_files
      [Dir.home, Dir.pwd].each do |dir|
        option_parser.options_files.class.supported_extensions.each do |ext|
          @option_parser.options_files << Pathname.new(dir).join(".#{NS.app_name}", "cli_options.#{ext}").to_s
        end
      end
    end

    # @param [ Controller::Base ] controller
    #
    # @retun [ Controllers ] self
    def <<(controller)
      options = controller.cli_options

      unless include?(controller)
        option_parser.add(*options) if options
        super(controller)
      end
      self
    end

    def run
      NS::ParsedCli.options     = option_parser.results
      first.class.option_parser = option_parser # To be able to output the help when -h/--hh

      redirect_output_to_file(NS::ParsedCli.output) if NS::ParsedCli.output

      Timeout.timeout(NS::ParsedCli.max_scan_duration, NS::Error::MaxScanDurationReached) do
        each(&:before_scan)

        @running = true

        each(&:run)
      end
    ensure
      NS::Browser.instance.hydra.abort

      # Reverse is used here as the app/controllers/core#after_scan finishes the output
      # and must be the last one to be executed. It also guarantee that stats will be output
      # even when an error occurs, which could help in debugging.
      # However, the #after_scan methods are only executed if the scan was running, and won't be
      # called when there is a CLI error, or just -h/--hh/--version for example
      reverse_each(&:after_scan) if running
    end
  end
end


# frozen_string_literal: true

module CMSScanner
  # Options available in the Browser
  class Browser
    OPTIONS = %i[
      cache_ttl
      cookie_jar
      cookie_string
      connect_timeout
      disable_tls_checks
      headers
      http_auth
      max_threads
      proxy
      proxy_auth
      random_user_agent
      request_timeout
      throttle
      user_agent
      user_agents_list
      vhost
    ].freeze

    attr_accessor(*OPTIONS)

    # @return [ String ]
    def default_user_agent
      "#{NS} v#{NS::VERSION}"
    end

    # @return [ Typhoeus::Hydra ]
    def hydra
      @hydra ||= Typhoeus::Hydra.new(max_concurrency: max_threads || 1)
    end

    # @param [ Hash ] options
    def load_options(options = {})
      OPTIONS.each do |sym|
        send("#{sym}=", options[sym]) if options.key?(sym)
      end
    end

    # Set the threads attribute and update hydra accordinly
    # If the throttle attribute is > 0, max_threads will be forced to 1
    #
    # @param [ Integer ] number
    def max_threads=(number)
      @max_threads = number.to_i.positive? && throttle.zero? ? number.to_i : 1

      hydra.max_concurrency = @max_threads
    end

    # @return [ String ] The user agent
    def user_agent
      @user_agent ||= random_user_agent ? user_agents.sample : default_user_agent
    end

    # @return [ Array<String> ]
    def user_agents
      return @user_agents if @user_agents

      @user_agents = []

      # The user_agents_list is managed by the CLI options, with the default being
      # APP_DIR/user_agents.txt
      File.open(user_agents_list).each do |line|
        next if line == "\n" || line[0, 1] == '#'

        @user_agents << line.chomp
      end

      @user_agents
    end

    # @param [ value ] The throttle time in milliseconds
    #
    # if value > 0, the max_threads will be set to 1
    def throttle=(value)
      @throttle = value.to_i.abs / 1000.0

      self.max_threads = 1 if @throttle.positive?
    end

    def trottle!
      sleep(throttle) if throttle.positive?
    end
  end
end


# frozen_string_literal: true

module CMSScanner
  # Scan
  class Scan
    attr_reader :run_error

    def initialize
      NS.start_memory = memory_usage

      controllers << NS::Controller::Core.new

      exit_hook

      yield self if block_given?
    end

    # @return [ Controllers ]
    def controllers
      @controllers ||= NS::Controllers.new
    end

    def run
      controllers.run
    rescue OptParseValidator::NoRequiredOption => e
      @run_error = e

      formatter.output('@usage', msg: e.message)
    rescue NoMemoryError, ScriptError, SecurityError, SignalException, StandardError, SystemStackError => e
      @run_error = e

      formatter.output('@scan_aborted',
                       reason: e.is_a?(Interrupt) ? 'Canceled by User' : e.message,
                       trace: e.backtrace,
                       verbose: NS::ParsedCli.verbose ||
                                run_error_exit_code == NS::ExitCode::EXCEPTION)
    ensure
      formatter.beautify
    end

    # Used for convenience
    # @See Formatter
    def formatter
      controllers.first.formatter
    end

    # @return [ Hash ]
    def datastore
      controllers.first.datastore
    end

    # Hook to be able to have an exit code returned
    # depending on the findings / errors
    # :nocov:
    def exit_hook
      # Avoid hooking the exit when rspec is running, otherwise it will always return 0
      # and Travis won't detect failed builds. Couldn't find a better way, even though
      # some people managed to https://github.com/rspec/rspec-core/pull/410
      return if defined?(RSpec)

      at_exit do
        exit(run_error_exit_code) if run_error

        # The parsed_option[:url] must be checked to avoid raising erros when only -h/-v are given
        exit(NS::ExitCode::VULNERABLE) if NS::ParsedCli.url && controllers.first.target.vulnerable?
        exit(NS::ExitCode::OK)
      end
    end
    # :nocov:

    # @return [ Integer ] The exit code related to the run_error
    def run_error_exit_code
      return NS::ExitCode::CLI_OPTION_ERROR if run_error.is_a?(OptParseValidator::Error) ||
                                               run_error.is_a?(OptionParser::ParseError)

      return NS::ExitCode::INTERRUPTED if run_error.is_a?(Interrupt)

      return NS::ExitCode::ERROR if run_error.is_a?(NS::Error::Standard) ||
                                    run_error.is_a?(CMSScanner::Error::Standard)

      NS::ExitCode::EXCEPTION
    end
  end
end


# frozen_string_literal: true

module CMSScanner
  # WebSite Implementation
  class WebSite
    attr_reader :uri, :opts

    # @param [ String ] site_url
    # @param [ Hash ] opts
    def initialize(site_url, opts = {})
      self.url = +site_url
      @opts    = opts
    end

    def url=(site_url)
      # Add a trailing slash to the site url
      # Making also sure the site_url is unfrozen
      +site_url << '/' if site_url[-1, 1] != '/'

      # Use the validator to ensure the site_url has a correct format
      OptParseValidator::OptURL.new([]).validate(site_url)

      @uri = Addressable::URI.parse(site_url).normalize
    end

    # Used for convenience
    #
    # URI.encode is preferered over Addressable::URI.encode as it will encode
    # leading # character:
    # URI.encode('#t#') => %23t%23
    # Addressable::URI.encode('#t#') => #t%23
    #
    # @param [ String ] path Optional path to merge with the uri
    #
    # @return [ String ]
    def url(path = nil)
      return @uri.to_s unless path

      @uri.join(URI.encode(path)).to_s
    end

    attr_writer :homepage_res

    # @return [ Typhoeus::Response ]
    #
    # As webmock does not support redirects mocking, coverage is ignored
    # :nocov:
    def homepage_res
      @homepage_res ||= NS::Browser.get_and_follow_location(url)
    end
    # :nocov:

    # @return [ String ]
    def homepage_url
      @homepage_url ||= homepage_res.effective_url
    end

    # Checks if the remote website is up.
    #
    # @param [ String ] path
    #
    # @return [ Boolean ]
    def online?(path = nil)
      NS::Browser.get(url(path)).code.nonzero? ? true : false
    end

    # @param [ String ] path
    #
    # @return [ Boolean ]
    def http_auth?(path = nil)
      NS::Browser.get(url(path)).code == 401
    end

    # @param [ String ] path
    #
    # @return [ Boolean ]
    def access_forbidden?(path = nil)
      NS::Browser.get(url(path)).code == 403
    end

    # @param [ String ] path
    #
    # @return [ Boolean ]
    def proxy_auth?(path = nil)
      NS::Browser.get(url(path)).code == 407
    end

    # @param [ String ] url
    #
    # @return [ String ] The redirection url or nil
    #
    # As webmock does not support redirects mocking, coverage is ignored
    # :nocov:
    def redirection(url = nil)
      url ||= @uri.to_s

      return unless [301, 302].include?(NS::Browser.get(url).code)

      res = NS::Browser.get(url, followlocation: true)

      res.effective_url == url ? nil : res.effective_url
    end
    # :nocov:

    # @return [ Hash ] The Typhoeus params to use to perform head requests
    def head_or_get_params
      @head_or_get_params ||= if NS::Browser.head(homepage_url).code == 405
                                { method: :get, maxfilesize: 1 }
                              else
                                { method: :head }
                              end
    end

    # Perform a HEAD request to the path provided, then if its response code
    # is in the array of codes given, a GET is done and the response returned. Otherwise the
    # HEAD response is returned.
    #
    # @param [ String ] path
    # @param [ Array<String> ] codes
    # @param [ Hash ] params The requests params
    # @option params [ Hash ] :head Request params for the HEAD
    # @option params [ hash ] :get Request params for the GET
    #
    # @return [ Typhoeus::Response ]
    def head_and_get(path, codes = [200], params = {})
      url_to_get  = url(path)
      head_params = (params[:head] || {}).merge(head_or_get_params)

      head_res = NS::Browser.forge_request(url_to_get, head_params).run

      codes.include?(head_res.code) ? NS::Browser.get(url_to_get, params[:get] || {}) : head_res
    end
  end
end