diff options
author | adamedx <adamed@opscode.com> | 2013-08-02 10:01:15 -0700 |
---|---|---|
committer | adamedx <adamed@opscode.com> | 2013-08-02 10:01:15 -0700 |
commit | 491ddbbbe8e5037ec3321f26a0872e36b47bb665 (patch) | |
tree | 073048eedb7ed1e0d40744203b9d0752f72c2cca | |
parent | 324caf8614b356dbe6a0e3a6028c1e2cd3af33d7 (diff) | |
download | chef-491ddbbbe8e5037ec3321f26a0872e36b47bb665.tar.gz |
CHEF-4422 Truncate cache paths for remote files to stay within file system path length limits
3 files changed, 131 insertions, 1 deletions
diff --git a/lib/chef/provider/remote_file/cache_control_data.rb b/lib/chef/provider/remote_file/cache_control_data.rb index d21ad2da12..27c724620b 100644 --- a/lib/chef/provider/remote_file/cache_control_data.rb +++ b/lib/chef/provider/remote_file/cache_control_data.rb @@ -149,7 +149,10 @@ class Chef end def sanitized_cache_file_basename - scrubbed_uri = uri.gsub(/\W/, '_') + # Scrub and truncate in accordance with the goals of keeping the name + # human-readable but within the bounds of local file system + # path length limits + scrubbed_uri = uri.gsub(/\W/, '_')[0..63] uri_md5 = Chef::Digester.instance.generate_md5_checksum(StringIO.new(uri)) "#{scrubbed_uri}-#{uri_md5}.json" end diff --git a/spec/functional/provider/remote_file/cache_control_data_spec.rb b/spec/functional/provider/remote_file/cache_control_data_spec.rb new file mode 100755 index 0000000000..20e0172252 --- /dev/null +++ b/spec/functional/provider/remote_file/cache_control_data_spec.rb @@ -0,0 +1,91 @@ +# +# Author:: Adam Edwards (<adamed@opscode.com>) +# Copyright:: Copyright (c) 2013 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'spec_helper' +require 'uri' + +describe Chef::Provider::RemoteFile::CacheControlData do + + before(:each) do + Chef::Config[:file_cache_path] = Dir.mktmpdir + end + + after(:each) do + FileUtils.rm_rf(Chef::Config[:file_cache_path]) + end + + let(:uri) { URI.parse("http://www.bing.com/robots.txt") } + + describe "when the cache control data save method is invoked" do + + subject(:cache_control_data) do + Chef::Provider::RemoteFile::CacheControlData.load_and_validate(uri, file_checksum) + end + + # the checksum of the file last we fetched it. + let(:file_checksum) { "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" } + + let(:etag) { "\"a-strong-identifier\"" } + let(:mtime) { "Thu, 01 Aug 2013 08:16:32 GMT" } + + before do + cache_control_data.etag = etag + cache_control_data.mtime = mtime + cache_control_data.checksum = file_checksum + end + + it "writes data to the cache" do + cache_control_data.save + end + + it "writes the data to the cache and the same data can be read back" do + cache_control_data.save + saved_cache_control_data = Chef::Provider::RemoteFile::CacheControlData.load_and_validate(uri, file_checksum) + saved_cache_control_data.etag.should == cache_control_data.etag + saved_cache_control_data.mtime.should == cache_control_data.mtime + saved_cache_control_data.checksum.should == cache_control_data.checksum + end + + # Cover the very long remote file path case -- see CHEF-4422 where + # local cache file names generated from the long uri exceeded + # local file system path limits resulting in exceptions from + # file system API's on both Windows and Unix systems. + context "when the length of the uri exceeds the path length limits for the local file system" do + let(:uri_exceeds_file_system_limit) do + URI.parse("http://www.bing.com/" + ('0' * 1024)) + end + + let(:uri) { uri_exceeds_file_system_limit } + + it "writes data to the cache" do + cache_control_data.save + end + + it "writes the data to the cache and the same data can be read back" do + cache_control_data.save + saved_cache_control_data = Chef::Provider::RemoteFile::CacheControlData.load_and_validate(uri, file_checksum) + saved_cache_control_data.etag.should == cache_control_data.etag + saved_cache_control_data.mtime.should == cache_control_data.mtime + saved_cache_control_data.checksum.should == cache_control_data.checksum + end + + end + end + +end + diff --git a/spec/unit/provider/remote_file/cache_control_data_spec.rb b/spec/unit/provider/remote_file/cache_control_data_spec.rb index ee29e21890..2baf86bcf6 100644 --- a/spec/unit/provider/remote_file/cache_control_data_spec.rb +++ b/spec/unit/provider/remote_file/cache_control_data_spec.rb @@ -164,6 +164,42 @@ describe Chef::Provider::RemoteFile::CacheControlData do cache_control_data.save end end + + # Cover the very long remote file path case -- see CHEF-4422 where + # local cache file names generated from the long uri exceeded + # local file system path limits resulting in exceptions from + # file system API's on both Windows and Unix systems. + context "and the URI results in a file cache path that exceeds 102 characters in length" do + let(:truncated_friendly_file_name_length) { 64 } + let(:md5_hex_length) { 32 } + let(:json_file_extension_length) { 5 } + let(:cache_file_path_limit) { truncated_friendly_file_name_length + 1 + md5_hex_length + json_file_extension_length } # {friendly}-{md5hex}.json == 102 + let(:long_remote_path) { "http://www.bing.com/" + ('0' * (truncated_friendly_file_name_length * 2 )) } + let(:uri) { URI.parse(long_remote_path) } + let(:truncated_remote_uri) { URI.parse(long_remote_path[0...truncated_friendly_file_name_length]) } + let(:truncated_file_cache_path) do + cache_control_data_truncated = Chef::Provider::RemoteFile::CacheControlData.load_and_validate(truncated_remote_uri, current_file_checksum) + cache_control_data_truncated.send('sanitized_cache_file_basename')[0...truncated_friendly_file_name_length] + end + + it "truncates the file cache path to 102 characters" do + normalized_cache_path = cache_control_data.send('sanitized_cache_file_basename') + + Chef::FileCache.should_receive(:store).with("remote_file/" + normalized_cache_path, cache_control_data.json_data) + + cache_control_data.save + + normalized_cache_path.length.should == cache_file_path_limit + end + + it "uses a file cache path that starts with the first 64 characters of the URI" do + normalized_cache_path = cache_control_data.send('sanitized_cache_file_basename') + + truncated_file_cache_path.length.should == truncated_friendly_file_name_length + normalized_cache_path.start_with?(truncated_file_cache_path).should == true + end + end + end end |