#!/usr/bin/env ruby
#
# Cloak/TurboPasswords Importer for 1Passwd
# Author: John Dawson <jdawson@io.com>
# Copyright: None; this work is in the PUBLIC DOMAIN
#
# Cloak and TurboPasswords are from Chapura Software.  They manage passwords
# and notes, using encryption to protect the data.  They run on Windows
# and Palm computers, with syncing between them.  Unfortunately they
# don't support Macs.
#
# Features:
#
# - Imports Secure Notes in a format that displays like they do in
#   Cloak/TurboPasswords.
#
# - Imports Web Forms for the records using the 'Website' template (which
#   have 'User Name', 'Password', and 'Website' values).
#
# - Preserves categories, with categories prefixed by 'TurboPasswords' or 'Cloak'
#   so data doesn't get accidentally mixed in with other 1Passwd accounts.
#
# - Records are tagged with 'cloak_export' or 'turbopasswords_export' so
#   they can be searched for.  (Note:  as of this writing, 2007-01-28, 1Passwd
#   version 2.3.1 doesn't support searching in the Notes field for Web Forms,
#   so this only works for Secure Notes.  Hopefully they will fix this.)
#
# - Autodetects file format (Cloak or TurboPasswords CSV export)
#
# Compatibility:
# - Tested with Cloak 2.0
# - Tested with TurboPasswords 4.0
# 

require File.dirname(__FILE__) + '/lib/base'
require File.dirname(__FILE__) + '/lib/csv'

class CloakImporter < Importer
  def initialize
    super()
    @state = 'checking_type'
    @format = '???'
    @hdr_labels = []
  end

  def process_contents(contents)
    CSV.parse(contents, nil, nil) { |row| process_row(row) }
  end

  def process_row(row)
    if (@state == "checking_type") then
      if (row[0] =~ /Cloak CSV Export/) then
        @format = 'Cloak'
        @state = "reading_header"
      elsif (row[0] =~ /TurboPasswords CSV Export/) then
        @format = 'TurboPasswords'
        @state = "reading_header"
      else
        throw "Invalid file: Not a Cloak Export"
      end
    elsif (@state == "reading_header") then
      @hdr_labels = row
      @state = "reading_data"
    elsif (@state == 'reading_data') then
      if (@format == 'Cloak') then
        cloak_csv(row)
      elsif (@format == 'TurboPasswords') then
        tp_csv(row)
      else
        throw "Invalid @format: #{@format}"
      end
    else
      throw "Invalid @state: #{@state}"
    end
  end

  def has_value(x)
    return x =~ /\S/
  end

  def make_record(header_row, data_row)
    rec = {}
    header_row.zip(data_row).each { |header,value|
      rec[header] = value if (has_value(header) and has_value(value))
    }
    return rec
  end

  def make_notes(rec, layout)
    notes = ''
    dict_rec = {}
    layout.each { |pair|
      label = pair[0]
      value = pair[1]
      if (has_value(label) and has_value(value)) then
        dict_rec[label] = value
        value.gsub! /\r?\n/, "\n"
        notes << "\n\n" if notes.length > 0
        notes << "#{label}:\n#{value}"
      end
    }
    return notes, dict_rec
  end

  # Header lines for a Cloak export file:
  # Cloak CSV Export File,,,,,,,,,,,,,,
  # Category,Type,Label 0,Data 0,Label 1,Data 1,Label 2,Data 2,Label 3,Data 3,Label 4,Data 4,Label 5,Data 5,Note
  def cloak_csv(row)
    rec = make_record(@hdr_labels, row);
    cloak_layout = [
      #[ 'Category',     rec['Category'] ],
      [ 'Type',         rec['Type'] ],
      [ rec['Label 0'], rec['Data 0'] ],
      [ rec['Label 1'], rec['Data 1'] ],
      [ rec['Label 2'], rec['Data 2'] ],
      [ rec['Label 3'], rec['Data 3'] ],
      [ rec['Label 4'], rec['Data 4'] ],
      [ rec['Label 5'], rec['Data 5'] ],
      [ 'Note',         rec['Note']   ],
      [ 'Origin',       'cloak_export' ],
    ]
    name = rec['Data 0'] || '(No Title)'
    notes, dict_rec = make_notes(rec, cloak_layout)

    title = name
    title.gsub! /\\/, '/'
    category = rec['Category'] || 'Unfiled'
    title = "Cloak - #{category}\\#{title}"

    note = SecureNote.new
    note.name = title
    note.notes = notes
    add_item note

    if dict_rec['Password'] && dict_rec['User Name'] && dict_rec['Website'] then
      form = WebForm.new
      form.name = title
      form.add_username(dict_rec['User Name'])
      form.add_password(dict_rec['Password'])
      form.url = dict_rec['Website']
      form.notes = "Origin: cloak_export"
      add_item form
    end
  end

  # Header lines for a TurboPasswords export file:
  # TurboPasswords CSV Export File,,,,,,,,,,,,,,
  # Category,Type,Label 1,Data 1,Label 2,Data 2,Label 3,Data 3,Label 4,Data 4,Label 5,Data 5,Label 6,Data 6,Note,URL,Top URL,Form Number
  def tp_csv(row)
    rec = make_record(@hdr_labels, row);
    tp_layout = [
      #[ 'Category',     rec['Category'] ],
      [ 'Type',         rec['Type'] ],
      [ rec['Label 1'], rec['Data 1'] ],
      [ rec['Label 2'], rec['Data 2'] ],
      [ rec['Label 3'], rec['Data 3'] ],
      [ rec['Label 4'], rec['Data 4'] ],
      [ rec['Label 5'], rec['Data 5'] ],
      [ rec['Label 6'], rec['Data 6'] ],
      [ 'Note',         rec['Note']   ],
      [ 'URL',          rec['URL']   ],
      [ 'Top URL',      rec['Top URL']   ],
      [ 'Form Number',  rec['Form Number']   ],
      [ 'Origin',       'turbopasswords_export' ],
    ]
    name = rec['Data 1'] || '(No Title)'
    notes, dict_rec = make_notes(rec, tp_layout)

    title = name
    title.gsub! /\\/, '/'
    category = rec['Category'] || 'Unfiled'
    title = "TurboPasswords - #{category}\\#{title}"

    note = SecureNote.new
    note.name = title
    note.notes = notes
    add_item note

    if dict_rec['Password'] && dict_rec['User Name'] && dict_rec['Website'] then
      form = WebForm.new
      form.name = title
      form.add_username(dict_rec['User Name'])
      form.add_password(dict_rec['Password'])
      form.url = dict_rec['Website']
      form.notes = "Origin: turbopasswords_export"
      add_item form
    end
  end
end

CloakImporter.new.run

# vim:autoindent:shiftwidth=2:tabstop=8:expandtab
