webloc 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 498d11e48961e5843a3a25fafb71f4738ebc10733037145ea30a17f176abcaa2
4
- data.tar.gz: 78beeadfdafd1a7f414fc6f7b69c6fd24819ed79bb39a9148e72e1bc38089416
3
+ metadata.gz: 246849e44bba8ffbcd81036265a4cc813aa963dccf5cb092cb8551268f83b276
4
+ data.tar.gz: 833a7e93bc666b75ce56cfe58619229884eea236fda992a2a660647315eb093c
5
5
  SHA512:
6
- metadata.gz: 99b273c4af784bdafe2cde8166da2c3672a263d2e412f0f1547e70ef4b8bf8de74185a13b1050e5c16b713e3f7b8c67baabe9f25b1366ee4e86876a95a368740
7
- data.tar.gz: fdbd68bb1239b9b35b1eb8c3d3ef8302d69d8a48eca6830bf636eb693f6e8c9a35e19af02148fda21a2d5a2ce3be1e3b7bef8186d7917dd8c40f73a699557e60
6
+ metadata.gz: 37d9c62d25c198f4e11b56f826c229def950817d1d28733779093ed97e9e0c389db4985abebbc4712b49171f7df9e94c3748a399c0783c5b60249f135356a649
7
+ data.tar.gz: 949d852b749d043290c2462b33aeebe18737d9845a06d588d621d4f2260c3e54dcf02da9aa21502cdacf32ce2fc341fdbaf7cc673862e308056cba9b50228752
data/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
3
+ ## 0.4.0
4
+
5
+ - Rewrite binary plist reading to follow object references and offsets, supporting
6
+ short URLs, additional metadata, and different object orders.
7
+ - Fix binary output for long URLs and support lengths beyond 65,535 characters.
8
+ - Add Unicode URL support, writing UTF-16 strings and reading them as UTF-8.
9
+ - Recover historical long-URL files affected by earlier versions' offset bugs.
10
+ - Reject malformed files with a `WeblocError` subclass instead of returning
11
+ partial URLs. Public methods and error classes remain unchanged.
12
+ - Require Ruby 3.0 or newer.
13
+ - Retain XML support, the original tests and fixtures, and the existing binary
14
+ encoding for short ASCII URLs. Add README example tests, independent plist
15
+ interoperability checks, and CI coverage.
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2011-2024 Peter Cooper
3
+ Copyright (c) 2011-2026 Peter Cooper
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  *webloc* is a Ruby library that can read from and write to <tt>.webloc</tt> files as used on macOS. These are a variant of 'plist' format files, specifically used for storing links to URLs.
4
4
 
5
- It works on Ruby 2.7 and up, including Ruby 3.x, and supports URLs of up to 2048 characters in length (and probably longer, but this is around the de facto limit for URLs in most systems).
5
+ It works on Ruby 3.0 and up (including Ruby 4.0.2 and TruffleRuby). It reads XML and binary plist files and writes binary plist files, supporting Unicode and URLs longer than 65,535 characters.
6
6
 
7
7
  ## Installation
8
8
 
@@ -10,14 +10,108 @@ It works on Ruby 2.7 and up, including Ruby 3.x, and supports URLs of up to 2048
10
10
 
11
11
  ## Usage
12
12
 
13
+ ### Basic Usage
14
+
13
15
  Reading a .webloc file:
14
16
 
15
- Webloc.load(ARGV.first).url
17
+ require 'webloc'
18
+ webloc = Webloc.load('bookmark.webloc')
19
+ puts webloc.url
20
+ # => "https://example.com"
16
21
 
17
22
  Writing to a .webloc file:
18
23
 
24
+ require 'webloc'
19
25
  Webloc.new('https://rubyweekly.com/').save('rubyweekly.webloc')
20
26
 
27
+ ### Advanced Examples
28
+
29
+ #### Error handling
30
+
31
+ ```ruby
32
+ require 'webloc'
33
+
34
+ begin
35
+ webloc = Webloc.load('suspicious.webloc')
36
+ puts webloc.url
37
+ rescue Webloc::FileNotFoundError => e
38
+ puts "File not found: #{e.message}"
39
+ rescue Webloc::CorruptedFileError => e
40
+ puts "File is corrupted: #{e.message}"
41
+ rescue Webloc::InvalidFormatError => e
42
+ puts "Invalid file format: #{e.message}"
43
+ rescue Webloc::WeblocError => e
44
+ puts "General webloc error: #{e.message}"
45
+ end
46
+ ```
47
+
48
+ #### Validating URLs before creating webloc files
49
+
50
+ ```ruby
51
+ require 'webloc'
52
+ require 'uri'
53
+
54
+ def create_webloc_safely(url, filename)
55
+ # Basic URL validation
56
+ uri = URI.parse(url)
57
+ unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
58
+ puts "Invalid URL scheme: #{url}"
59
+ return false
60
+ end
61
+
62
+ # Create the webloc file
63
+ Webloc.new(url).save(filename)
64
+ puts "Created #{filename} for #{url}"
65
+ true
66
+ rescue URI::InvalidURIError
67
+ puts "Invalid URL format: #{url}"
68
+ false
69
+ rescue Webloc::WeblocError => e
70
+ puts "Failed to create webloc: #{e.message}"
71
+ false
72
+ end
73
+
74
+ create_webloc_safely('https://example.com', 'example.webloc')
75
+ create_webloc_safely('invalid-url', 'invalid.webloc')
76
+ ```
77
+
78
+ #### Converting between formats
79
+
80
+ ```ruby
81
+ require 'webloc'
82
+ require 'json'
83
+
84
+ # Convert webloc to JSON
85
+ webloc = Webloc.load('bookmark.webloc')
86
+ json_data = { url: webloc.url, title: File.basename('bookmark.webloc', '.webloc') }
87
+ File.write('bookmark.json', JSON.pretty_generate(json_data))
88
+
89
+ # Convert JSON back to webloc
90
+ json_content = JSON.parse(File.read('bookmark.json'))
91
+ Webloc.new(json_content['url']).save('restored.webloc')
92
+ ```
93
+
94
+ ## Compatibility
95
+
96
+ The public methods and error classes are unchanged. The reader also recovers
97
+ historical long-URL files with the known offset bugs. New writes correct those
98
+ offsets; short ASCII URLs retain their original binary encoding.
99
+ Binary reading supports short URLs, additional metadata, and different object orders.
100
+ URLs are stored as supplied, without escaping or scheme validation. Unicode
101
+ strings use UTF-16 in binary files and are returned as UTF-8 when read.
102
+
103
+ Malformed files without a recoverable historical layout raise a `WeblocError`
104
+ subclass instead of returning a partial URL.
105
+
106
+ ## Development
107
+
108
+ Run `bundle install` followed by `bundle exec rake test`.
109
+
110
+ The suite retains the original tests and fixtures and exercises the Ruby examples
111
+ in this README. Additional fixtures cover other binary layouts and files written
112
+ by older releases. Interoperability tests use Python 3's `plistlib` when available
113
+ and Apple's `plutil` on macOS; neither is a runtime dependency of the gem.
114
+
21
115
  ## Thanks
22
116
 
23
117
  Thanks is due to Christos Karaiskos for [this article](https://medium.com/@karaiskc/understanding-apples-binary-property-list-format-281e6da00dbd
@@ -25,6 +119,6 @@ Thanks is due to Christos Karaiskos for [this article](https://medium.com/@karai
25
119
 
26
120
  ## License
27
121
 
28
- Copyright (C) 2011-2024 Peter Cooper
122
+ Copyright (C) 2011-2026 Peter Cooper
29
123
 
30
- webloc is licensed under the terms of the MIT License
124
+ webloc is licensed under the terms of the MIT License
@@ -0,0 +1,159 @@
1
+ class Webloc
2
+ # Read the root dictionary's URL without recursively decoding unrelated metadata.
3
+ class BinaryPlist
4
+ HEADER = 'bplist00'.b.freeze
5
+ PREFIX = "bplist00\xD1\x01\x02SURL".b.freeze
6
+ INTEGER_FORMATS = { 1 => 'C', 2 => 'n', 4 => 'N', 8 => 'Q>' }.freeze
7
+
8
+ def self.integer_width(value)
9
+ INTEGER_FORMATS.each_key.find { |width| value < (1 << (8 * width)) } ||
10
+ raise(ArgumentError, 'URL is too long to encode')
11
+ end
12
+
13
+ def self.dump(url)
14
+ if url.ascii_only?
15
+ payload = url.b
16
+ length = payload.bytesize
17
+ marker = 0x5f
18
+ else
19
+ payload = url.encode(Encoding::UTF_16BE).b
20
+ length = payload.bytesize / 2 # UTF-16 code units, including surrogate pairs.
21
+ marker = 0x6f
22
+ end
23
+
24
+ # Keep the historical object order and extended string length encoding,
25
+ # so existing valid ASCII output (including the original fixture) is stable.
26
+ width = integer_width(length)
27
+ output = PREFIX + [marker, 0x10 + width.bit_length - 1].pack('CC')
28
+ output << [length].pack(INTEGER_FORMATS.fetch(width)) << payload
29
+ table_offset = output.bytesize
30
+ offset_width = integer_width(table_offset)
31
+ output << [8, 11, 15].pack("#{INTEGER_FORMATS.fetch(offset_width)}*")
32
+ output << [offset_width, 1, 3, 0, table_offset].pack('x6CCQ>Q>Q>')
33
+ end
34
+
35
+ def initialize(data, filename)
36
+ @data = data
37
+ @filename = filename
38
+ end
39
+
40
+ def url
41
+ unless @data.start_with?(HEADER)
42
+ detail = @data.include?('SURL_') ? 'missing bplist00 header' : 'missing SURL marker'
43
+ raise InvalidFormatError, "Invalid binary webloc format - #{detail} in file: #{@filename}"
44
+ end
45
+
46
+ # Recognize the complete historical layout, never just a URL marker.
47
+ historical_url = legacy_url
48
+ return historical_url if historical_url
49
+
50
+ read_table
51
+ type, count, cursor, limit = object(@root)
52
+ corrupt('Root object is not a dictionary') unless type == 0xd
53
+ read(cursor, count * 2 * @reference_width, limit)
54
+
55
+ count.times do |index|
56
+ key_ref = integer(cursor + index * @reference_width, @reference_width, limit)
57
+ next unless string(key_ref) == 'URL'
58
+
59
+ value_ref = integer(cursor + (count + index) * @reference_width, @reference_width, limit)
60
+ return string(value_ref)
61
+ end
62
+ corrupt("No 'URL' key found")
63
+ end
64
+
65
+ private
66
+
67
+ def corrupt(message)
68
+ raise CorruptedFileError, "#{message} in binary webloc file: #{@filename}"
69
+ end
70
+
71
+ def read(offset, length, limit)
72
+ corrupt('Truncated or invalid object') if offset < 8 || length < 0 || offset > limit || length > limit - offset
73
+ @data.byteslice(offset, length)
74
+ end
75
+
76
+ def integer(offset, width, limit)
77
+ read(offset, width, limit).each_byte.reduce(0) { |value, byte| (value << 8) | byte }
78
+ end
79
+
80
+ def read_table
81
+ corrupt('Truncated plist trailer') if @data.bytesize < 40
82
+ offset_width, @reference_width, count, @root, table_offset =
83
+ @data.byteslice(-32, 32).unpack('x6CCQ>Q>Q>')
84
+ unless (1..8).cover?(offset_width) && (1..8).cover?(@reference_width)
85
+ corrupt('Invalid offset or reference width')
86
+ end
87
+ trailer_offset = @data.bytesize - 32
88
+ unless count > 0 && @root < count && table_offset >= 8 &&
89
+ table_offset + count * offset_width == trailer_offset
90
+ corrupt('Invalid offset table')
91
+ end
92
+
93
+ @offsets = Array.new(count) do |index|
94
+ offset = integer(table_offset + index * offset_width, offset_width, trailer_offset)
95
+ corrupt('Object offset is outside the object table') unless offset >= 8 && offset < table_offset
96
+ offset
97
+ end
98
+ # Objects need not be stored in reference order. Bound each read by the
99
+ # following physical object, so a truncated URL cannot consume the table.
100
+ boundaries = @offsets.uniq.sort + [table_offset]
101
+ @limits = boundaries.each_cons(2).to_h
102
+ end
103
+
104
+ def object(reference)
105
+ corrupt('Invalid object reference') unless reference < @offsets.length
106
+ offset = @offsets.fetch(reference)
107
+ limit = @limits.fetch(offset)
108
+ marker = integer(offset, 1, limit)
109
+ cursor = offset + 1
110
+ length = marker & 0xf
111
+ if length == 0xf
112
+ size_marker = integer(cursor, 1, limit)
113
+ unless (0x10..0x13).cover?(size_marker)
114
+ raise InvalidFormatError, "Unsupported length encoding in binary webloc file: #{@filename}"
115
+ end
116
+ width = 1 << (size_marker & 0xf)
117
+ length = integer(cursor + 1, width, limit)
118
+ cursor += width + 1
119
+ end
120
+ [marker >> 4, length, cursor, limit]
121
+ end
122
+
123
+ def string(reference)
124
+ type, length, cursor, limit = object(reference)
125
+ case type
126
+ when 0x5
127
+ value = read(cursor, length, limit)
128
+ corrupt('Invalid ASCII string') unless value.ascii_only?
129
+ value
130
+ when 0x6
131
+ read(cursor, length * 2, limit).force_encoding(Encoding::UTF_16BE).encode(Encoding::UTF_8)
132
+ else
133
+ corrupt('Expected a string')
134
+ end
135
+ rescue EncodingError
136
+ corrupt('Invalid Unicode string')
137
+ end
138
+
139
+ def legacy_url
140
+ return unless @data.start_with?(PREFIX + "\x5f".b)
141
+ width = { 0x10 => 1, 0x11 => 2 }[@data.getbyte(16)]
142
+ return unless width && @data.bytesize >= 18 + width + 35
143
+
144
+ length = @data.byteslice(17, width).unpack1(INTEGER_FORMATS.fetch(width))
145
+ return unless length >= 238 && width == (length > 255 ? 2 : 1)
146
+ table_offset = 17 + width + length
147
+ return unless @data.bytesize == table_offset + 35
148
+
149
+ # Match even the two-byte overflow in the old trailer, but require every
150
+ # other byte of its structure and the full declared URL to be present.
151
+ trailer = [1, 1, 3, 0, (length + 18) & 0xffff].pack('x6CCQ>Q>Q>')
152
+ return unless @data.byteslice(table_offset, 35) == "\x08\x0b\x0f".b + trailer
153
+ value = @data.byteslice(17 + width, length)
154
+ value if value.ascii_only?
155
+ end
156
+ end
157
+
158
+ private_constant :BinaryPlist
159
+ end
@@ -1,3 +1,3 @@
1
1
  class Webloc
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
data/lib/webloc.rb CHANGED
@@ -1,78 +1,83 @@
1
1
  require 'plist'
2
+ require_relative 'webloc/binary_plist'
2
3
 
3
4
  class Webloc
5
+ class WeblocError < StandardError; end
6
+ class FileNotFoundError < WeblocError; end
7
+ class CorruptedFileError < WeblocError; end
8
+ class InvalidFormatError < WeblocError; end
9
+ class EmptyFileError < WeblocError; end
10
+
4
11
  attr_accessor :url
5
12
 
6
13
  def initialize(url)
14
+ raise ArgumentError, "URL cannot be nil or empty" if url.nil? || url.empty?
7
15
  @url = url
8
16
  end
9
17
 
10
18
  def self.load(filename)
11
- data = File.read(filename)
12
- data = data.force_encoding('binary') rescue data
19
+ raise FileNotFoundError, "File not found: #{filename}" unless File.exist?(filename)
13
20
 
14
- if data !~ /\<plist/
15
- offset = (data =~ /SURL_/)
16
- length_offset = 7
17
- if data[offset + 5] == "\x10"
18
- length = data[offset + 6]
19
- length = length.unpack('C')[0]
20
- elsif data[offset + 5] == "\x11"
21
- length_offset = 8
22
- length = data[offset + 6] + data[offset + 7]
23
- length = length.unpack('S>')[0]
24
- end
25
- url = data[offset + length_offset,length]
21
+ begin
22
+ data = File.binread(filename)
23
+ rescue => e
24
+ raise FileNotFoundError, "Unable to read file '#{filename}': #{e.message}"
25
+ end
26
+
27
+ raise EmptyFileError, "File is empty: #{filename}" if data.empty?
28
+
29
+ url = nil
30
+
31
+ if data.start_with?('bplist00') || !data.include?('<plist')
32
+ # Handle binary plist format
33
+ url = parse_binary_format(data, filename)
26
34
  else
27
- url = Plist::parse_xml(filename)['URL'] rescue nil
35
+ # Handle XML plist format
36
+ url = parse_xml_format(filename)
28
37
  end
29
38
 
30
- raise ArgumentError unless url
39
+ raise CorruptedFileError, "No URL found in webloc file: #{filename}" if url.nil? || (url.is_a?(String) && url.empty?)
40
+ raise CorruptedFileError, "URL must be a string in webloc file: #{filename}" unless url.is_a?(String)
31
41
  new(url)
32
42
  end
33
43
 
34
- def data
35
- # PLIST HEADER
36
- @data = "bplist\x30\x30".bytes
44
+ private
37
45
 
38
- # PLIST OBJECT TABLE
39
- @data += "\xD1\x01\x02".bytes # object 1 is a dictionary
40
- @data += "SURL".bytes # object 2
46
+ def self.parse_binary_format(data, filename)
47
+ BinaryPlist.new(data.b, filename).url
48
+ end
41
49
 
42
- length_suffix = @url.length > 255 ? "\x11" : "\x10"
43
- @data += ("\x5f" + length_suffix).bytes # object 3 is an ASCII string with a variable length length encoding (I know..)
44
- # .. the '0' in \x10 denotes the length can be encoded within 2**0 bytes (i.e. 1)
45
- # .. the '1' in \x11 denotes the length can be encoded within 2**1 bytes (i.e. 2)
46
-
47
- if @url.length > 255
48
- @data += [@url.length].pack('S>').bytes
49
- else
50
- @data += [@url.length].pack('C').bytes
50
+ def self.parse_xml_format(filename)
51
+ begin
52
+ plist_data = Plist::parse_xml(filename)
53
+ raise InvalidFormatError, "Invalid XML plist format - could not parse file: #{filename}" unless plist_data.is_a?(Hash)
54
+
55
+ url = plist_data['URL']
56
+ raise CorruptedFileError, "No 'URL' key found in plist file: #{filename}" unless url
57
+
58
+ url
59
+ rescue => e
60
+ if e.message.include?('parse') || e.message.include?('XML') || e.message.include?('plist')
61
+ raise InvalidFormatError, "Invalid XML plist format in file '#{filename}': #{e.message}"
62
+ else
63
+ raise CorruptedFileError, "Failed to parse XML webloc format in file '#{filename}': #{e.message}"
64
+ end
51
65
  end
52
- @data += @url.bytes # and finally the URL itself
53
-
54
- # This is the offset table
55
- @data += "\x08\x0B\x0F".bytes # so objects at 0x08, 0x0b and 0x0f
66
+ end
56
67
 
57
- # PLIST TRAILER
58
- # Bytes 0-4 are unused
59
- @data += "\x00\x00\x00\x00\x00".bytes
60
- # Byte 5 is the sort version
61
- @data += "\x00".bytes
62
- # Byte 6 is how many bytes are needed for each offset table offset
63
- @data += "\x01".bytes
64
- @data += "\x01".bytes
65
- # Bytes 8-15 are how many objects are contained in the plist
66
- @data += "\x00\x00\x00\x00\x00\x00\x00\x03".bytes
67
- # Bytes 16-23 are for an offset from the offset table
68
- @data += "\x00\x00\x00\x00\x00\x00\x00\x00".bytes
69
- # Bytes 24-31 denote the position of the offset table from the start of the file
70
- @data += "\x00\x00\x00\x00\x00\x00".bytes + [@url.length + 18].pack('S>').bytes
68
+ public
71
69
 
72
- @data = @data.pack('C*')
70
+ def data
71
+ BinaryPlist.dump(@url)
73
72
  end
74
-
73
+
75
74
  def save(filename)
76
- File.open(filename, 'wb') { |f| f.write data }
75
+ raise ArgumentError, "Filename cannot be nil or empty" if filename.nil? || filename.empty?
76
+
77
+ begin
78
+ File.open(filename, 'wb') { |f| f.write data }
79
+ rescue => e
80
+ raise WeblocError, "Failed to save webloc file '#{filename}': #{e.message}"
81
+ end
77
82
  end
78
- end
83
+ end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: webloc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Cooper
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-05-01 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: plist
@@ -31,21 +30,16 @@ executables: []
31
30
  extensions: []
32
31
  extra_rdoc_files: []
33
32
  files:
34
- - ".gitignore"
35
- - Gemfile
33
+ - CHANGELOG.md
36
34
  - LICENSE
37
35
  - README.md
38
- - Rakefile
39
36
  - lib/webloc.rb
37
+ - lib/webloc/binary_plist.rb
40
38
  - lib/webloc/version.rb
41
- - test/oldstyle.webloc
42
- - test/pliststyle.webloc
43
- - test/webloc_test.rb
44
- - webloc.gemspec
45
39
  homepage: https://github.com/peterc/webloc
46
- licenses: []
40
+ licenses:
41
+ - MIT
47
42
  metadata: {}
48
- post_install_message:
49
43
  rdoc_options: []
50
44
  require_paths:
51
45
  - lib
@@ -53,18 +47,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
53
47
  requirements:
54
48
  - - ">="
55
49
  - !ruby/object:Gem::Version
56
- version: '0'
50
+ version: '3.0'
57
51
  required_rubygems_version: !ruby/object:Gem::Requirement
58
52
  requirements:
59
53
  - - ">="
60
54
  - !ruby/object:Gem::Version
61
55
  version: '0'
62
56
  requirements: []
63
- rubygems_version: 3.5.3
64
- signing_key:
57
+ rubygems_version: 4.0.6
65
58
  specification_version: 4
66
59
  summary: Reads and writes .webloc files on macOS
67
- test_files:
68
- - test/oldstyle.webloc
69
- - test/pliststyle.webloc
70
- - test/webloc_test.rb
60
+ test_files: []
data/.gitignore DELETED
@@ -1,4 +0,0 @@
1
- pkg/*
2
- *.gem
3
- .bundle
4
- .DS_Store
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- # Specify your gem's dependencies in webloc.gemspec
4
- gemspec
data/Rakefile DELETED
@@ -1,11 +0,0 @@
1
- require 'bundler'
2
- Bundler::GemHelper.install_tasks
3
-
4
- require 'rake/testtask'
5
- Rake::TestTask.new(:test) do |test|
6
- test.libs << 'lib' << 'test'
7
- test.pattern = 'test/**/*_test.rb'
8
- test.verbose = true
9
- end
10
-
11
- task :default => :test
data/test/oldstyle.webloc DELETED
Binary file
@@ -1,8 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
- <plist version="1.0">
4
- <dict>
5
- <key>URL</key>
6
- <string>https://github.com/peterc/webloc</string>
7
- </dict>
8
- </plist>
data/test/webloc_test.rb DELETED
@@ -1,50 +0,0 @@
1
- require 'test/unit'
2
- require 'webloc'
3
- require 'tempfile'
4
-
5
- class WeblocTest < Test::Unit::TestCase
6
- def test_webloc_object_requires_url
7
- assert_raise(ArgumentError) { Webloc.new }
8
- end
9
-
10
- def test_webloc_object_created_with_url
11
- assert_equal 'http://example.com', Webloc.new('http://example.com').url
12
- end
13
-
14
- def test_webloc_object_loaded_from_old_style_file
15
- assert_equal 'https://github.com/peterc/webloc', Webloc.load(File.dirname(__FILE__) + '/oldstyle.webloc').url
16
- end
17
-
18
- def test_webloc_object_loaded_from_plist_file
19
- assert_equal 'https://github.com/peterc/webloc', Webloc.load(File.dirname(__FILE__) + '/pliststyle.webloc').url
20
- end
21
-
22
- def test_webloc_generates_valid_data
23
- data = File.read(File.dirname(__FILE__) + '/oldstyle.webloc').b
24
- assert_equal data, Webloc.new('https://github.com/peterc/webloc').data
25
- end
26
-
27
- def test_webloc_can_handle_long_urls
28
- url = "http://example.com/this-is-a-very-long-url-abcde" + ('a' * 2000)
29
- assert_nothing_raised { Webloc.new(url).data }
30
- file = Tempfile.new('test-long-webloc')
31
- begin
32
- Webloc.new(url).save(file.path)
33
- assert_equal url, Webloc.load(file.path).url
34
- ensure
35
- file.close
36
- file.unlink
37
- end
38
- end
39
-
40
- def test_webloc_can_write_file
41
- file = Tempfile.new('test-webloc')
42
- begin
43
- Webloc.new('https://github.com/peterc/webloc').save(file.path)
44
- assert_equal Webloc.new('https://github.com/peterc/webloc').data, File.read(file.path).b
45
- ensure
46
- file.close
47
- file.unlink
48
- end
49
- end
50
- end
data/webloc.gemspec DELETED
@@ -1,23 +0,0 @@
1
- # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
3
- require "webloc/version"
4
-
5
- Gem::Specification.new do |s|
6
- s.name = "webloc"
7
- s.version = Webloc::VERSION
8
- s.platform = Gem::Platform::RUBY
9
- s.authors = ["Peter Cooper"]
10
- s.email = ["git@peterc.org"]
11
- s.homepage = "https://github.com/peterc/webloc"
12
- s.summary = %q{Reads and writes .webloc files on macOS}
13
- s.description = %q{Webloc reads and writes .webloc files on macOS}
14
-
15
- s.rubyforge_project = "webloc"
16
-
17
- s.files = `git ls-files`.split("\n")
18
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
- s.require_paths = ["lib"]
21
-
22
- s.add_dependency 'plist'
23
- end