Permalink
Browse files

Initial commit

  • Loading branch information...
1 parent 2cdd6a4 commit 73385717b8a1246a33ecde64b20351ebafb61159 @weppos committed Nov 2, 2009
View
6 Manifest
@@ -0,0 +1,6 @@
+README
+Rakefile
+lib/domain_name.rb
+lib/domain_name/version.rb
+test/test_helper.rb
+Manifest
View
56 Rakefile
@@ -0,0 +1,56 @@
+$:.unshift(File.dirname(__FILE__) + "/lib")
+
+require 'rubygems'
+require 'rake'
+require 'echoe'
+require 'domain_name'
+
+
+# Common package properties
+PKG_NAME = ENV['PKG_NAME'] || DomainName::GEM
+PKG_VERSION = ENV['PKG_VERSION'] || DomainName::VERSION
+RUBYFORGE_PROJECT = 'domain_name'
+
+if ENV['SNAPSHOT'].to_i == 1
+ PKG_VERSION << "." << Time.now.utc.strftime("%Y%m%d%H%M%S")
+end
+
+
+Echoe.new(PKG_NAME, PKG_VERSION) do |p|
+ p.author = "Simone Carletti"
+ p.email = "weppos@weppos.net"
+ p.summary = ""
+ p.url = "http://code.simonecarletti.com/domain-name"
+ p.project = RUBYFORGE_PROJECT
+ p.description = <<-EOD
+ EOD
+
+ p.need_zip = true
+
+ p.development_dependencies += ["rake ~>0.8",
+ "echoe ~>3.2",
+ "mocha ~>0.9"]
+
+ p.rcov_options = ["-Itest -x mocha,rcov,Rakefile"]
+end
+
+
+desc "Open an irb session preloaded with this library"
+task :console do
+ sh "irb -rubygems -I lib -r domain_name.rb"
+end
+
+begin
+ require 'code_statistics'
+ desc "Show library's code statistics"
+ task :stats do
+ CodeStatistics.new(["DomainName", "lib"],
+ ["Tests", "test"]).to_s
+ end
+rescue LoadError
+ puts "CodeStatistics (Rails) is not available"
+end
+
+Dir["tasks/**/*.rake"].each do |file|
+ load(file)
+end
View
94 lib/domain_name.rb
@@ -0,0 +1,94 @@
+#
+# = DomainName
+#
+# MissingDescription
+#
+#
+# Category:: Net
+# Package:: DomainName
+# Author:: Simone Carletti <weppos@weppos.net>
+# License:: MIT License
+#
+#--
+#
+#++
+
+
+require 'domain_name/version'
+require 'domain_name/rule'
+require 'domain_name/rule_list'
+
+
+class DomainName
+
+ NAME = 'DomainName'
+ GEM = 'domain_name'
+ AUTHORS = ['Simone Carletti <weppos@weppos.net>']
+
+
+ attr_reader :name, :tld, :sld, :trd, :rule
+
+
+ def initialize(name)
+ @name = name
+ end
+
+ def labels
+ to_s.split(".").reverse
+ end
+
+ def to_s
+ name.to_s
+ end
+
+
+ def rule
+ @rule ||= RuleList.default.find(self)
+ end
+
+ def rule!
+ rule || raise(Error, "The domain cannot be found in the TLD definition file")
+ end
+
+
+ protected
+
+ def parse
+ case rule!.type
+
+ # "foo.google.com"
+ when :normal
+ match = /^(.*)\.(#{Regexp.escape(rule.value)})$/.match(name.to_s)
+ ignore, full, tld = match.to_a
+
+ # "photos.verybritish.co.uk"
+ when :wildcard
+ match = /^(.*)\.(.*?\.#{Regexp.escape(rule.value)})$/.match(name.to_s)
+ ignore, full, tld = match.to_a
+
+ # "photos.wishlist.parliament.uk"
+ when :exception
+ match = /^(.*)\.(#{Regexp.escape(rule.value.split('.', 2).last)})$/.match(name.to_s)
+ ignore, full, tld = match.to_a
+
+ else
+ raise Error, "WTF?!?"
+ end
+
+ # If we have 0 parts left, there is just a tld and no domain or subdomain
+ # If we have 1 part, it's the domain, and there is no subdomain
+ # If we have 2+ parts, the last part is the domain, the other parts (combined) are the subdomain
+ parts = full.split(".")
+ @tld = tld
+ @sld = parts.empty? ? nil : parts.pop
+ @trd = parts.empty? ? nil : parts.join(".")
+
+ self
+ end
+
+
+ def self.valid?(domain)
+ !Domain.new(domain).rule.nil?
+ end
+
+end
View
4,362 lib/domain_name/definitions.dat
4,362 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
View
22 lib/domain_name/errors.rb
@@ -0,0 +1,22 @@
+#
+# = DomainName
+#
+# MissingDescription
+#
+#
+# Category:: Net
+# Package:: DomainName
+# Author:: Simone Carletti <weppos@weppos.net>
+# License:: MIT License
+#
+#--
+#
+#++
+
+
+class DomainName
+
+ class Error < StandardError
+ end
+
+end
View
86 lib/domain_name/rule.rb
@@ -0,0 +1,86 @@
+#
+# = DomainName
+#
+# MissingDescription
+#
+#
+# Category:: Net
+# Package:: DomainName
+# Author:: Simone Carletti <weppos@weppos.net>
+# License:: MIT License
+#
+#--
+#
+#++
+
+
+class DomainName
+
+ class Rule
+
+ attr_reader :name, :value, :type, :labels
+
+ def initialize(name)
+ @name = name.to_s
+
+ case self.name[0..0]
+ when "*" then
+ @type = :wildcard
+ @value = self.name[2..-1]
+ when "!" then
+ @type = :exception
+ @value = self.name[1..-1]
+ else
+ @type = :normal
+ @value = self.name
+ end
+
+ @labels = self.value.split(".").reverse
+ end
+
+
+ # Returns true if two rules are equal.
+ def ==(other)
+ return false unless other.is_a?(Rule)
+ self.equal?(other) ||
+ self.name == other.name
+ end
+ alias :eql? :==
+
+
+ def match?(domain_name)
+ l1 = labels
+ l2 = domain_name.labels
+ odiff(l1, l2).empty?
+ end
+
+ def length
+ if wildcard?
+ labels.length + 1
+ elsif exception?
+ labels.length - 1
+ else
+ labels.length
+ end
+ end
+
+ %w(normal wildcard exception).each do |method|
+ define_method "#{method}?" do
+ type.to_s == method
+ end
+ end
+
+
+ protected
+
+ def odiff(one, two)
+ ii = 0
+ while(ii < one.size && one[ii] == two[ii])
+ ii += 1
+ end
+ one[ii..one.count]
+ end
+
+ end
+
+end
View
142 lib/domain_name/rule_list.rb
@@ -0,0 +1,142 @@
+#
+# = DomainName
+#
+# MissingDescription
+#
+#
+# Category:: Net
+# Package:: DomainName
+# Author:: Simone Carletti <weppos@weppos.net>
+# License:: MIT License
+#
+#--
+#
+#++
+
+
+class DomainName
+
+ class RuleList
+ include Enumerable
+
+ attr_reader :list
+
+
+ def initialize(&block)
+ @list = []
+ yield(self) if block_given?
+ end
+
+
+ # Returns true if two rules are equal.
+ def ==(other)
+ return false unless other.is_a?(RuleList)
+ self.equal?(other) ||
+ self.list == other.list
+ end
+ alias :eql? :==
+
+ def each(*args, &block)
+ @list.each(*args, &block)
+ end
+
+ def to_a
+ @list
+ end
+
+ # Adds the given object to the set and returns self.
+ def add(rule)
+ @list << rule
+ self
+ end
+ alias << add
+
+ # Returns the number of elements.
+ def size
+ @list.size
+ end
+ alias length size
+
+ # Returns true if the set contains no elements.
+ def empty?
+ @list.empty?
+ end
+
+ # Removes all elements and returns self.
+ def clear
+ @list.clear
+ self
+ end
+
+
+ def find(domain_name)
+ rules = select(domain_name)
+ rules.select { |r| r.type == :exception }.first ||
+ rules.inject { |t,r| t.length > r.length ? t : r }
+ end
+
+ def select(domain_name)
+ @list.select { |rule| rule.match?(domain_name) }
+ end
+
+
+ @@default = nil
+
+ class << self
+
+ # Returns the default <tt>RuleList</tt>.
+ # Initializes a new <tt>RuleList</tt> parsing the content of <tt>default_definition</tt> if necessary.
+ def default
+ @@default ||= parse(default_definition)
+ end
+
+ # Sets the default <tt>RuleList</tt> to <tt>value</tt>.
+ def default=(value)
+ @@default = value
+ end
+
+ # Sets the default <tt>RuleList</tt> to <tt>nil</tt>.
+ def clear
+ self.default = nil
+ self
+ end
+
+ # Resets the default <tt>RuleList</tt> and reinitialize it
+ # parsing the content of <tt>default_definition</tt>.
+ def reload
+ self.clear.default
+ end
+
+ # Returns the default definition list.
+ # Can be any IOStream including a <tt>String</tt> or a simple <tt>String</tt>.
+ def default_definition
+ File.new(File.join(File.dirname(__FILE__), "definitions.dat"))
+ end
+
+
+ # Parse given <tt>input</tt> treating the content as Public Suffic List.
+ # See http://publicsuffix.org/format/ for more details about input format.
+ def parse(input)
+ new do |list|
+ input.each_line do |line|
+ line.strip!
+
+ # strip blank lines
+ if line.empty?
+ next
+ # strip comments
+ elsif line =~ %r{^//}
+ next
+ # append rule
+ else
+ list << Rule.new(line)
+ end
+ end
+ end
+ end
+
+ end
+
+ end
+
+end
View
31 lib/domain_name/version.rb
@@ -0,0 +1,31 @@
+#
+# = DomainName
+#
+# MissingDescription
+#
+#
+# Category:: Net
+# Package:: DomainName
+# Author:: Simone Carletti <weppos@weppos.net>
+# License:: MIT License
+#
+#--
+#
+#++
+
+
+class DomainName
+
+ module Version
+ MAJOR = 0
+ MINOR = 0
+ TINY = 0
+
+ STRING = [MAJOR, MINOR, TINY].join('.')
+ end
+
+ VERSION = Version::STRING
+ STATUS = 'dev'
+ BUILD = nil
+
+end
View
14 tasks/domain_name.rake
@@ -0,0 +1,14 @@
+namespace :domain_name do
+
+ task :download_definitions do
+ require "net/http"
+
+ DEFINITION_URL = "http://mxr.mozilla.org/mozilla-central/source/netwerk/dns/src/effective_tld_names.dat?raw=1"
+
+ File.open("lib/domain_name/definitions.dat", "w+") do |f|
+ response = Net::HTTP.get_response(URI.parse(DEFINITION_URL))
+ f.write(response.body)
+ end
+ end
+
+end
View
10 test/domain_name_test.rb
@@ -0,0 +1,10 @@
+require 'test_helper'
+
+class DomainNameTest < Test::Unit::TestCase
+
+ def test_labels
+ assert_equal %w(uk co google), domain_name("google.co.uk").labels
+ assert_equal %w(uk google), domain_name("google.uk").labels
+ end
+
+end
View
178 test/rule_list_test.rb
@@ -0,0 +1,178 @@
+require 'test_helper'
+
+class RuleListTest < Test::Unit::TestCase
+
+ def setup
+ @list = DomainName::RuleList.new
+ end
+
+
+ def test_initialize
+ assert_instance_of DomainName::RuleList, @list
+ assert_equal 0, @list.length
+ end
+
+
+ def test_equality_with_self
+ list = DomainName::RuleList.new
+ assert_equal list, list
+ end
+
+ def test_equality_with_internals
+ rule = DomainName::Rule.new("com")
+ assert_equal DomainName::RuleList.new.add(rule), DomainName::RuleList.new.add(rule)
+ end
+
+
+ def test_add
+ assert_equal @list, @list.add(DomainName::Rule.new(""))
+ assert_equal @list, @list << DomainName::Rule.new("")
+ assert_equal 2, @list.length
+ end
+
+ def test_empty?
+ assert @list.empty?
+ @list.add(DomainName::Rule.new(""))
+ assert !@list.empty?
+ end
+
+ def test_size
+ assert_equal 0, @list.length
+ assert_equal @list, @list.add(DomainName::Rule.new(""))
+ assert_equal 1, @list.length
+ end
+
+ def test_clear
+ assert_equal 0, @list.length
+ assert_equal @list, @list.add(DomainName::Rule.new(""))
+ assert_equal 1, @list.length
+ assert_equal @list, @list.clear
+ assert_equal 0, @list.length
+ end
+
+
+ def test_find
+ @list = DomainName::RuleList.parse(<<EOS)
+// com : http://en.wikipedia.org/wiki/.com
+com
+
+// uk : http://en.wikipedia.org/wiki/.uk
+*.uk
+*.sch.uk
+!bl.uk
+!british-library.uk
+EOS
+ assert_equal DomainName::Rule.new("com"), @list.find(domain_name("google.com"))
+ assert_equal DomainName::Rule.new("com"), @list.find(domain_name("foo.google.com"))
+ assert_equal DomainName::Rule.new("*.uk"), @list.find(domain_name("google.uk"))
+ assert_equal DomainName::Rule.new("*.uk"), @list.find(domain_name("google.co.uk"))
+ assert_equal DomainName::Rule.new("*.uk"), @list.find(domain_name("foo.google.co.uk"))
+ assert_equal DomainName::Rule.new("!british-library.uk"), @list.find(domain_name("british-library.uk"))
+ assert_equal DomainName::Rule.new("!british-library.uk"), @list.find(domain_name("foo.british-library.uk"))
+ end
+
+ def test_select
+ @list = DomainName::RuleList.parse(<<EOS)
+// com : http://en.wikipedia.org/wiki/.com
+com
+
+// uk : http://en.wikipedia.org/wiki/.uk
+*.uk
+*.sch.uk
+!bl.uk
+!british-library.uk
+EOS
+ assert_equal 2, @list.select(domain_name("british-library.uk")).size
+ end
+
+
+ def test_self_default_getter
+ assert_equal nil, DomainName::RuleList.send(:class_variable_get, :"@@default")
+ DomainName::RuleList.default
+ assert_not_equal nil, DomainName::RuleList.send(:class_variable_get, :"@@default")
+ end
+
+ def test_self_default_setter
+ DomainName::RuleList.default
+ assert_not_equal nil, DomainName::RuleList.send(:class_variable_get, :"@@default")
+ DomainName::RuleList.default = nil
+ assert_equal nil, DomainName::RuleList.send(:class_variable_get, :"@@default")
+ end
+
+ def test_self_clear
+ DomainName::RuleList.default
+ assert_not_equal nil, DomainName::RuleList.send(:class_variable_get, :"@@default")
+ DomainName::RuleList.clear
+ assert_equal nil, DomainName::RuleList.send(:class_variable_get, :"@@default")
+ end
+
+ def test_self_reload
+ DomainName::RuleList.default
+ DomainName::RuleList.expects(:default_definition).returns("")
+ DomainName::RuleList.reload
+ assert_equal DomainName::RuleList.new, DomainName::RuleList.default
+ end
+
+ def test_self_parse
+ input = <<EOS
+// ***** BEGIN LICENSE BLOCK *****
+// Version: MPL 1.1/GPL 2.0/LGPL 2.1
+//
+// The contents of this file are subject to the Mozilla Public License Version
+// 1.1 (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+// http://www.mozilla.org/MPL/
+//
+// Software distributed under the License is distributed on an "AS IS" basis,
+// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+// for the specific language governing rights and limitations under the
+// License.
+//
+// The Original Code is the Public Suffix List.
+//
+// The Initial Developer of the Original Code is
+// Jo Hermans <jo.hermans@gmail.com>.
+// Portions created by the Initial Developer are Copyright (C) 2007
+// the Initial Developer. All Rights Reserved.
+//
+// Contributor(s):
+// Ruben Arakelyan <ruben@wackomenace.co.uk>
+// Gervase Markham <gerv@gerv.net>
+// Pamela Greene <pamg.bugs@gmail.com>
+// David Triendl <david@triendl.name>
+// The kind representatives of many TLD registries
+//
+// Alternatively, the contents of this file may be used under the terms of
+// either the GNU General Public License Version 2 or later (the "GPL"), or
+// the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+// in which case the provisions of the GPL or the LGPL are applicable instead
+// of those above. If you wish to allow use of your version of this file only
+// under the terms of either the GPL or the LGPL, and not to allow others to
+// use your version of this file under the terms of the MPL, indicate your
+// decision by deleting the provisions above and replace them with the notice
+// and other provisions required by the GPL or the LGPL. If you do not delete
+// the provisions above, a recipient may use your version of this file under
+// the terms of any one of the MPL, the GPL or the LGPL.
+//
+// ***** END LICENSE BLOCK *****
+
+// ac : http://en.wikipedia.org/wiki/.ac
+ac
+com.ac
+
+// ad : http://en.wikipedia.org/wiki/.ad
+ad
+
+// ar : http://en.wikipedia.org/wiki/.ar
+*.ar
+!congresodelalengua3.ar
+EOS
+ expected = []
+ list = DomainName::RuleList.parse(input)
+
+ assert_instance_of DomainName::RuleList, list
+ assert_equal 5, list.length
+ assert_equal %w(ac com.ac ad *.ar !congresodelalengua3.ar).map { |name| DomainName::Rule.new(name) }, list.to_a
+ end
+
+end
View
73 test/rule_test.rb
@@ -0,0 +1,73 @@
+require 'test_helper'
+
+class RuleTest < Test::Unit::TestCase
+
+ def test_initialize_rule_normal
+ rule = DomainName::Rule.new("verona.it")
+ assert_instance_of DomainName::Rule, rule
+ assert_equal :normal, rule.type
+ assert_equal "verona.it", rule.name
+ assert_equal "verona.it", rule.value
+ assert_equal %w(verona it).reverse, rule.labels
+ end
+
+ def test_initialize_rule_wildcard
+ rule = DomainName::Rule.new("*.aichi.jp")
+ assert_instance_of DomainName::Rule, rule
+ assert_equal :wildcard, rule.type
+ assert_equal "*.aichi.jp", rule.name
+ assert_equal "aichi.jp", rule.value
+ assert_equal %w(aichi jp).reverse, rule.labels
+ end
+
+ def test_initialize_rule_exception
+ rule = DomainName::Rule.new("!british-library.uk")
+ assert_instance_of DomainName::Rule, rule
+ assert_equal :exception, rule.type
+ assert_equal "!british-library.uk", rule.name
+ assert_equal "british-library.uk", rule.value
+ assert_equal %w(british-library uk).reverse, rule.labels
+ end
+
+
+ def test_equality_with_self
+ rule = DomainName::Rule.new("foo")
+ assert_equal rule, rule
+ end
+
+ def test_equality_with_internals
+ assert_equal DomainName::Rule.new("foo"), DomainName::Rule.new("foo")
+ assert_not_equal DomainName::Rule.new("foo"), DomainName::Rule.new("bar")
+ assert_not_equal DomainName::Rule.new("foo"), Class.new { def name; foo; end }.new
+ end
+
+
+ def test_match
+ assert DomainName::Rule.new("uk").match?(domain_name("google.uk"))
+ assert !DomainName::Rule.new("gk").match?(domain_name("google.uk"))
+ assert !DomainName::Rule.new("google").match?(domain_name("google.uk"))
+ assert DomainName::Rule.new("uk").match?(domain_name("google.co.uk"))
+ assert !DomainName::Rule.new("gk").match?(domain_name("google.co.uk"))
+ assert !DomainName::Rule.new("co").match?(domain_name("google.co.uk"))
+ assert DomainName::Rule.new("co.uk").match?(domain_name("google.co.uk"))
+ assert !DomainName::Rule.new("uk.co").match?(domain_name("google.co.uk"))
+ assert !DomainName::Rule.new("go.uk").match?(domain_name("google.co.uk"))
+ end
+
+ def test_match_with_wildcard
+ assert DomainName::Rule.new("*.uk").match?(domain_name("google.uk"))
+ assert DomainName::Rule.new("*.uk").match?(domain_name("google.co.uk"))
+ assert DomainName::Rule.new("*.co.uk").match?(domain_name("google.co.uk"))
+ assert !DomainName::Rule.new("*.go.uk").match?(domain_name("google.co.uk"))
+ end
+
+ def test_match_with_exception
+ assert DomainName::Rule.new("!uk").match?(domain_name("google.co.uk"))
+ assert !DomainName::Rule.new("!gk").match?(domain_name("google.co.uk"))
+ assert DomainName::Rule.new("!co.uk").match?(domain_name("google.co.uk"))
+ assert !DomainName::Rule.new("!go.uk").match?(domain_name("google.co.uk"))
+ assert DomainName::Rule.new("!british-library.uk").match?(domain_name("british-library.uk"))
+ assert !DomainName::Rule.new("!british-library.uk").match?(domain_name("google.co.uk"))
+ end
+
+end
View
22 test/test_helper.rb
@@ -0,0 +1,22 @@
+$:.unshift(File.dirname(__FILE__) + '/../lib')
+
+require 'rubygems'
+require 'test/unit'
+require 'mocha'
+require 'domain_name'
+
+class DomainName
+ module TestCase
+
+ private
+
+ def domain_name(name)
+ DomainName.new(name)
+ end
+
+ end
+end
+
+class Test::Unit::TestCase
+ include DomainName::TestCase
+end

0 comments on commit 7338571

Please sign in to comment.