Merge pull request #2882 from jekyll/security/centralize-path-sanitation

This commit is contained in:
Parker Moore 2014-11-05 10:21:57 -08:00
commit 2ec1dc1831
26 changed files with 240 additions and 176 deletions

View File

@ -68,11 +68,12 @@ module Jekyll
require 'jekyll/command' require 'jekyll/command'
require 'jekyll/liquid_extensions' require 'jekyll/liquid_extensions'
class << self
# Public: Tells you which Jekyll environment you are building in so you can skip tasks # Public: Tells you which Jekyll environment you are building in so you can skip tasks
# if you need to. This is useful when doing expensive compression tasks on css and # if you need to. This is useful when doing expensive compression tasks on css and
# images and allows you to skip that when working in development. # images and allows you to skip that when working in development.
def self.env def env
ENV["JEKYLL_ENV"] || "development" ENV["JEKYLL_ENV"] || "development"
end end
@ -84,10 +85,12 @@ module Jekyll
# list of option names and their defaults. # list of option names and their defaults.
# #
# Returns the final configuration Hash. # Returns the final configuration Hash.
def self.configuration(override) def configuration(override = Hash.new)
config = Configuration[Configuration::DEFAULTS] config = Configuration[Configuration::DEFAULTS]
override = Configuration[override].stringify_keys override = Configuration[override].stringify_keys
unless override.delete('skip_config_files')
config = config.read_config_files(config.config_files(override)) config = config.read_config_files(config.config_files(override))
end
# Merge DEFAULTS < _config.yml < override # Merge DEFAULTS < _config.yml < override
config = Utils.deep_merge_hashes(config, override).stringify_keys config = Utils.deep_merge_hashes(config, override).stringify_keys
@ -96,32 +99,49 @@ module Jekyll
config config
end end
# Static: Set the TZ environment variable to use the timezone specified # Public: Set the TZ environment variable to use the timezone specified
# #
# timezone - the IANA Time Zone # timezone - the IANA Time Zone
# #
# Returns nothing # Returns nothing
def self.set_timezone(timezone) def set_timezone(timezone)
ENV['TZ'] = timezone ENV['TZ'] = timezone
end end
def self.logger # Public: Fetch the logger instance for this Jekyll process.
#
# Returns the LogAdapter instance.
def logger
@logger ||= LogAdapter.new(Stevenson.new) @logger ||= LogAdapter.new(Stevenson.new)
end end
def self.logger=(writer) # Public: Set the log writer.
# New log writer must respond to the same methods
# as Ruby's interal Logger.
#
# writer - the new Logger-compatible log transport
#
# Returns the new logger.
def logger=(writer)
@logger = LogAdapter.new(writer) @logger = LogAdapter.new(writer)
end end
# Public: File system root # Public: An array of sites
# #
# Returns the root of the filesystem as a Pathname # Returns the Jekyll sites created.
def self.fs_root def sites
@fs_root ||= "/" @sites ||= []
end end
def self.sanitized_path(base_directory, questionable_path) # Public: Ensures the questionable path is prefixed with the base directory
clean_path = File.expand_path(questionable_path, fs_root) # and prepends the questionable path with the base directory if false.
#
# base_directory - the directory with which to prefix the questionable path
# questionable_path - the path we're unsure about, and want prefixed
#
# Returns the sanitized path.
def sanitized_path(base_directory, questionable_path)
clean_path = File.expand_path(questionable_path, "/")
clean_path.gsub!(/\A\w\:\//, '/') clean_path.gsub!(/\A\w\:\//, '/')
unless clean_path.start_with?(base_directory) unless clean_path.start_with?(base_directory)
File.join(base_directory, clean_path) File.join(base_directory, clean_path)
@ -129,6 +149,8 @@ module Jekyll
clean_path clean_path
end end
end end
end
end end
require_all 'jekyll/commands' require_all 'jekyll/commands'

View File

@ -29,7 +29,7 @@ module Jekyll
# Returns a Set with the file paths # Returns a Set with the file paths
def existing_files def existing_files
files = Set.new files = Set.new
Dir.glob(File.join(site.dest, "**", "*"), File::FNM_DOTMATCH) do |file| Dir.glob(site.in_dest_dir("**", "*"), File::FNM_DOTMATCH) do |file|
files << file unless file =~ /\/\.{1,2}$/ || file =~ keep_file_regex || keep_dirs.include?(file) files << file unless file =~ /\/\.{1,2}$/ || file =~ keep_file_regex || keep_dirs.include?(file)
end end
files files
@ -76,7 +76,7 @@ module Jekyll
# #
# Returns a Set with the directory paths # Returns a Set with the directory paths
def keep_dirs def keep_dirs
site.keep_files.map { |file| parent_dirs(File.join(site.dest, file)) }.flatten.to_set site.keep_files.map { |file| parent_dirs(site.in_dest_dir(file)) }.flatten.to_set
end end
# Private: Creates a regular expression from the config's keep_files array # Private: Creates a regular expression from the config's keep_files array

View File

@ -35,7 +35,8 @@ module Jekyll
# Returns the sorted array of docs. # Returns the sorted array of docs.
def read def read
filtered_entries.each do |file_path| filtered_entries.each do |file_path|
full_path = Jekyll.sanitized_path(directory, file_path) full_path = collection_dir(file_path)
next if File.directory?(full_path)
if Utils.has_yaml_header? full_path if Utils.has_yaml_header? full_path
doc = Jekyll::Document.new(full_path, { site: site, collection: self }) doc = Jekyll::Document.new(full_path, { site: site, collection: self })
doc.read doc.read
@ -54,8 +55,9 @@ module Jekyll
# relative to the collection's directory # relative to the collection's directory
def entries def entries
return Array.new unless exists? return Array.new unless exists?
Dir.glob(File.join(directory, "**", "*.*")).map do |entry| @entries ||=
entry[File.join(directory, "")] = ''; entry Dir.glob(collection_dir("**", "*.*")).map do |entry|
entry["#{collection_dir}/"] = ''; entry
end end
end end
@ -65,8 +67,12 @@ module Jekyll
# Returns a list of filtered entry paths. # Returns a list of filtered entry paths.
def filtered_entries def filtered_entries
return Array.new unless exists? return Array.new unless exists?
@filtered_entries ||=
Dir.chdir(directory) do Dir.chdir(directory) do
entry_filter.filter(entries).reject { |f| File.directory?(f) } entry_filter.filter(entries).reject do |f|
path = collection_dir(f)
File.directory?(path) || (File.symlink?(f) && site.safe)
end
end end
end end
@ -78,12 +84,25 @@ module Jekyll
@relative_directory ||= "_#{label}" @relative_directory ||= "_#{label}"
end end
# The full path to the directory containing the # The full path to the directory containing the collection.
# #
# Returns a String containing th directory name where the collection # Returns a String containing th directory name where the collection
# is stored on the filesystem. # is stored on the filesystem.
def directory def directory
@directory ||= Jekyll.sanitized_path(site.source, relative_directory) @directory ||= site.in_source_dir(relative_directory)
end
# The full path to the directory containing the collection, with
# optional subpaths.
#
# *files - (optional) any other path pieces relative to the
# directory to append to the path
#
# Returns a String containing th directory name where the collection
# is stored on the filesystem.
def collection_dir(*files)
return directory if files.empty?
site.in_source_dir(relative_directory, *files)
end end
# Checks whether the directory "exists" for this collection. # Checks whether the directory "exists" for this collection.

View File

@ -43,7 +43,7 @@ module Jekyll
# Returns nothing. # Returns nothing.
def read_yaml(base, name, opts = {}) def read_yaml(base, name, opts = {})
begin begin
self.content = File.read(Jekyll.sanitized_path(base, name), self.content = File.read(site.in_source_dir(base, name),
merged_file_read_opts(opts)) merged_file_read_opts(opts))
if content =~ /\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)/m if content =~ /\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)/m
self.content = $POSTMATCH self.content = $POSTMATCH

View File

@ -159,7 +159,8 @@ module Jekyll
# #
# Returns the full path to the output file of this document. # Returns the full path to the output file of this document.
def destination(base_directory) def destination(base_directory)
path = Jekyll.sanitized_path(base_directory, url) dest = site.in_dest_dir(base_directory)
path = site.in_dest_dir(dest, url)
path = File.join(path, "index.html") if url =~ /\/$/ path = File.join(path, "index.html") if url =~ /\/$/
path path
end end

View File

@ -14,8 +14,8 @@ module Jekyll
end end
# Get the full path to the directory containing the draft files # Get the full path to the directory containing the draft files
def containing_dir(source, dir) def containing_dir(dir)
File.join(source, dir, '_drafts') site.in_source_dir(dir, '_drafts')
end end
# The path to the draft source file, relative to the site source # The path to the draft source file, relative to the site source

View File

@ -106,7 +106,7 @@ module Jekyll
# Returns excerpt String # Returns excerpt String
def extract_excerpt(post_content) def extract_excerpt(post_content)
separator = site.config['excerpt_separator'] separator = site.config['excerpt_separator']
head, _, tail = post_content.partition(separator) head, _, tail = post_content.to_s.partition(separator)
"" << head << "\n\n" << tail.scan(/^\[[^\]]+\]:.+$/).join("\n") "" << head << "\n\n" << tail.scan(/^\[[^\]]+\]:.+$/).join("\n")
end end

View File

@ -38,12 +38,12 @@ module Jekyll
end end
def layout_directory_inside_source def layout_directory_inside_source
Jekyll.sanitized_path(site.source, site.config['layouts']) site.in_source_dir(site.config['layouts'])
end end
def layout_directory_in_cwd def layout_directory_in_cwd
dir = Jekyll.sanitized_path(Dir.pwd, site.config['layouts']) dir = Jekyll.sanitized_path(Dir.pwd, site.config['layouts'])
if File.directory?(dir) if File.directory?(dir) && !site.safe
dir dir
else else
nil nil

View File

@ -140,7 +140,7 @@ module Jekyll
# #
# Returns the destination file path String. # Returns the destination file path String.
def destination(dest) def destination(dest)
path = Jekyll.sanitized_path(dest, URL.unescape_path(url)) path = site.in_dest_dir(dest, URL.unescape_path(url))
path = File.join(path, "index.html") if url =~ /\/$/ path = File.join(path, "index.html") if url =~ /\/$/
path path
end end

View File

@ -66,7 +66,7 @@ module Jekyll
# Returns an Array of plugin search paths # Returns an Array of plugin search paths
def plugins_path def plugins_path
if (site.config['plugins'] == Jekyll::Configuration::DEFAULTS['plugins']) if (site.config['plugins'] == Jekyll::Configuration::DEFAULTS['plugins'])
[Jekyll.sanitized_path(site.source, site.config['plugins'])] [site.in_source_dir(site.config['plugins'])]
else else
Array(site.config['plugins']).map { |d| File.expand_path(d) } Array(site.config['plugins']).map { |d| File.expand_path(d) }
end end

View File

@ -49,7 +49,7 @@ module Jekyll
def initialize(site, source, dir, name) def initialize(site, source, dir, name)
@site = site @site = site
@dir = dir @dir = dir
@base = containing_dir(source, dir) @base = containing_dir(dir)
@name = name @name = name
self.categories = dir.downcase.split('/').reject { |x| x.empty? } self.categories = dir.downcase.split('/').reject { |x| x.empty? }
@ -88,8 +88,8 @@ module Jekyll
end end
# Get the full path to the directory containing the post files # Get the full path to the directory containing the post files
def containing_dir(source, dir) def containing_dir(dir)
return File.join(source, dir, '_posts') site.in_source_dir(dir, '_posts')
end end
# Read the YAML frontmatter. # Read the YAML frontmatter.
@ -268,7 +268,7 @@ module Jekyll
# Returns destination file path String. # Returns destination file path String.
def destination(dest) def destination(dest)
# The url needs to be unescaped in order to preserve the correct filename # The url needs to be unescaped in order to preserve the correct filename
path = Jekyll.sanitized_path(dest, URL.unescape_path(url)) path = site.in_dest_dir(dest, URL.unescape_path(url))
path = File.join(path, "index.html") if path[/\.html?$/].nil? path = File.join(path, "index.html") if path[/\.html?$/].nil?
path path
end end

View File

@ -46,8 +46,7 @@ module Jekyll
end end
def most_recent_posts def most_recent_posts
recent_posts = site.posts.reverse - [post] @most_recent_posts ||= (site.posts.reverse - [post]).first(10)
recent_posts.first(10)
end end
def display(output) def display(output)

View File

@ -3,11 +3,12 @@ require 'csv'
module Jekyll module Jekyll
class Site class Site
attr_accessor :config, :layouts, :posts, :pages, :static_files, attr_reader :source, :dest, :config
:exclude, :include, :source, :dest, :lsi, :highlighter, attr_accessor :layouts, :posts, :pages, :static_files,
:permalink_style, :time, :future, :unpublished, :safe, :plugins, :limit_posts, :exclude, :include, :lsi, :highlighter, :permalink_style,
:show_drafts, :keep_files, :baseurl, :data, :file_read_opts, :gems, :time, :future, :unpublished, :safe, :plugins, :limit_posts,
:plugin_manager :show_drafts, :keep_files, :baseurl, :data, :file_read_opts,
:gems, :plugin_manager
attr_accessor :converters, :generators attr_accessor :converters, :generators
@ -15,16 +16,16 @@ module Jekyll
# #
# config - A Hash containing site configuration details. # config - A Hash containing site configuration details.
def initialize(config) def initialize(config)
self.config = config.clone @config = config.clone
%w[safe lsi highlighter baseurl exclude include future unpublished %w[safe lsi highlighter baseurl exclude include future unpublished
show_drafts limit_posts keep_files gems].each do |opt| show_drafts limit_posts keep_files gems].each do |opt|
self.send("#{opt}=", config[opt]) self.send("#{opt}=", config[opt])
end end
self.source = File.expand_path(config['source']) # Source and destination may not be changed after the site has been created.
self.dest = File.expand_path(config['destination']) @source = File.expand_path(config['source']).freeze
self.permalink_style = config['permalink'].to_sym @dest = File.expand_path(config['destination']).freeze
self.plugin_manager = Jekyll::PluginManager.new(self) self.plugin_manager = Jekyll::PluginManager.new(self)
self.plugins = plugin_manager.plugins_path self.plugins = plugin_manager.plugins_path
@ -32,6 +33,10 @@ module Jekyll
self.file_read_opts = {} self.file_read_opts = {}
self.file_read_opts[:encoding] = config['encoding'] if config['encoding'] self.file_read_opts[:encoding] = config['encoding'] if config['encoding']
self.permalink_style = config['permalink'].to_sym
Jekyll.sites << self
reset reset
setup setup
end end
@ -88,6 +93,30 @@ module Jekyll
end end
end end
# Public: Prefix a given path with the source directory.
#
# paths - (optional) path elements to a file or directory within the
# source directory
#
# Returns a path which is prefixed with the source directory.
def in_source_dir(*paths)
paths.reduce(source) do |base, path|
Jekyll.sanitized_path(base, path)
end
end
# Public: Prefix a given path with the destination directory.
#
# paths - (optional) path elements to a file or directory within the
# destination directory
#
# Returns a path which is prefixed with the destination directory.
def in_dest_dir(*paths)
paths.reduce(dest) do |base, path|
Jekyll.sanitized_path(base, path)
end
end
# The list of collections and their corresponding Jekyll::Collection instances. # The list of collections and their corresponding Jekyll::Collection instances.
# If config['collections'] is set, a new instance is created for each item in the collection. # If config['collections'] is set, a new instance is created for each item in the collection.
# If config['collections'] is not set, a new hash is returned. # If config['collections'] is not set, a new hash is returned.
@ -132,7 +161,7 @@ module Jekyll
# #
# Returns nothing. # Returns nothing.
def read_directories(dir = '') def read_directories(dir = '')
base = File.join(source, dir) base = in_source_dir(dir)
entries = Dir.chdir(base) { filter_entries(Dir.entries('.'), base) } entries = Dir.chdir(base) { filter_entries(Dir.entries('.'), base) }
read_posts(dir) read_posts(dir)
@ -141,7 +170,7 @@ module Jekyll
limit_posts! if limit_posts > 0 # limit the posts if :limit_posts option is set limit_posts! if limit_posts > 0 # limit the posts if :limit_posts option is set
entries.each do |f| entries.each do |f|
f_abs = File.join(base, f) f_abs = in_source_dir(base, f)
if File.directory?(f_abs) if File.directory?(f_abs)
f_rel = File.join(dir, f) f_rel = File.join(dir, f)
read_directories(f_rel) unless dest.sub(/\/$/, '') == f_abs read_directories(f_rel) unless dest.sub(/\/$/, '') == f_abs
@ -198,7 +227,7 @@ module Jekyll
# #
# Returns nothing # Returns nothing
def read_data(dir) def read_data(dir)
base = Jekyll.sanitized_path(source, dir) base = in_source_dir(dir)
read_data_to(base, self.data) read_data_to(base, self.data)
end end
@ -217,7 +246,7 @@ module Jekyll
end end
entries.each do |entry| entries.each do |entry|
path = Jekyll.sanitized_path(dir, entry) path = in_source_dir(dir, entry)
next if File.symlink?(path) && safe next if File.symlink?(path) && safe
key = sanitize_filename(File.basename(entry, '.*')) key = sanitize_filename(File.basename(entry, '.*'))
@ -407,10 +436,10 @@ module Jekyll
# #
# Returns the list of entries to process # Returns the list of entries to process
def get_entries(dir, subfolder) def get_entries(dir, subfolder)
base = File.join(source, dir, subfolder) base = in_source_dir(dir, subfolder)
return [] unless File.exist?(base) return [] unless File.exist?(base)
entries = Dir.chdir(base) { filter_entries(Dir['**/*'], base) } entries = Dir.chdir(base) { filter_entries(Dir['**/*'], base) }
entries.delete_if { |e| File.directory?(File.join(base, e)) } entries.delete_if { |e| File.directory?(in_source_dir(base, e)) }
end end
# Aggregate post information # Aggregate post information

View File

@ -37,7 +37,7 @@ module Jekyll
# #
# Returns destination file path. # Returns destination file path.
def destination(dest) def destination(dest)
File.join(*[dest, destination_rel_dir, @name].compact) @site.in_dest_dir(*[dest, destination_rel_dir, @name].compact)
end end
def destination_rel_dir def destination_rel_dir

View File

@ -161,7 +161,7 @@ eos
end end
def resolved_includes_dir(context) def resolved_includes_dir(context)
Jekyll.sanitized_path(context.registers[:site].source, page_path(context)) context.registers[:site].in_source_dir(page_path(context))
end end
end end
end end

View File

@ -30,10 +30,10 @@ class Test::Unit::TestCase
end end
def site_configuration(overrides = {}) def site_configuration(overrides = {})
full_overrides = build_configs(overrides, build_configs({"destination" => dest_dir}))
build_configs({ build_configs({
"source" => source_dir, "source" => source_dir,
"destination" => dest_dir }, full_overrides)
}, build_configs(overrides))
end end
def dest_dir(*subdirs) def dest_dir(*subdirs)

View File

@ -4,15 +4,16 @@ class TestCleaner < Test::Unit::TestCase
context "directory in keep_files" do context "directory in keep_files" do
setup do setup do
clear_dest clear_dest
stub(Jekyll).configuration do
Jekyll::Configuration::DEFAULTS.merge({'source' => source_dir, 'destination' => dest_dir})
end
FileUtils.mkdir_p(dest_dir('to_keep/child_dir')) FileUtils.mkdir_p(dest_dir('to_keep/child_dir'))
FileUtils.touch(File.join(dest_dir('to_keep'), 'index.html')) FileUtils.touch(File.join(dest_dir('to_keep'), 'index.html'))
FileUtils.touch(File.join(dest_dir('to_keep/child_dir'), 'index.html')) FileUtils.touch(File.join(dest_dir('to_keep/child_dir'), 'index.html'))
@site = Site.new(Jekyll.configuration) @site = Site.new(Jekyll.configuration({
"skip_config_files" => true,
"source" => source_dir,
"destination" => dest_dir
}))
@site.keep_files = ['to_keep/child_dir'] @site.keep_files = ['to_keep/child_dir']
@cleaner = Site::Cleaner.new(@site) @cleaner = Site::Cleaner.new(@site)
@ -43,14 +44,15 @@ class TestCleaner < Test::Unit::TestCase
context "directory containing no files and non-empty directories" do context "directory containing no files and non-empty directories" do
setup do setup do
clear_dest clear_dest
stub(Jekyll).configuration do
Jekyll::Configuration::DEFAULTS.merge({'source' => source_dir, 'destination' => dest_dir})
end
FileUtils.mkdir_p(source_dir('no_files_inside/child_dir')) FileUtils.mkdir_p(source_dir('no_files_inside/child_dir'))
FileUtils.touch(File.join(source_dir('no_files_inside/child_dir'), 'index.html')) FileUtils.touch(File.join(source_dir('no_files_inside/child_dir'), 'index.html'))
@site = Site.new(Jekyll.configuration) @site = Site.new(Jekyll.configuration({
"skip_config_files" => true,
"source" => source_dir,
"destination" => dest_dir
}))
@site.process @site.process
@cleaner = Site::Cleaner.new(@site) @cleaner = Site::Cleaner.new(@site)

View File

@ -187,6 +187,7 @@ class TestCollections < Test::Unit::TestCase
should "not allow symlinks" do should "not allow symlinks" do
assert_not_include @collection.filtered_entries, "um_hi.md" assert_not_include @collection.filtered_entries, "um_hi.md"
assert_not_include @collection.filtered_entries, "/um_hi.md"
end end
should "not include the symlinked file in the list of docs" do should "not include the symlinked file in the list of docs" do

View File

@ -4,7 +4,11 @@ require 'ostruct'
class TestConvertible < Test::Unit::TestCase class TestConvertible < Test::Unit::TestCase
context "yaml front-matter" do context "yaml front-matter" do
setup do setup do
@convertible = OpenStruct.new @convertible = OpenStruct.new(
"site" => Site.new(Jekyll.configuration(
"source" => File.expand_path('../fixtures', __FILE__)
))
)
@convertible.extend Jekyll::Convertible @convertible.extend Jekyll::Convertible
@base = File.expand_path('../fixtures', __FILE__) @base = File.expand_path('../fixtures', __FILE__)
end end

View File

@ -13,6 +13,10 @@ class TestDocument < Test::Unit::TestCase
@document = @site.collections["methods"].docs.first @document = @site.collections["methods"].docs.first
end end
should "exist" do
assert !@document.nil?
end
should "know its relative path" do should "know its relative path" do
assert_equal "_methods/configuration.md", @document.relative_path assert_equal "_methods/configuration.md", @document.relative_path
end end

View File

@ -8,8 +8,7 @@ class TestDraft < Test::Unit::TestCase
context "A Draft" do context "A Draft" do
setup do setup do
clear_dest clear_dest
stub(Jekyll).configuration { Jekyll::Configuration::DEFAULTS } @site = Site.new(site_configuration)
@site = Site.new(Jekyll.configuration)
end end
should "ensure valid drafts are valid" do should "ensure valid drafts are valid" do

View File

@ -3,10 +3,7 @@ require 'helper'
class TestEntryFilter < Test::Unit::TestCase class TestEntryFilter < Test::Unit::TestCase
context "Filtering entries" do context "Filtering entries" do
setup do setup do
stub(Jekyll).configuration do @site = Site.new(site_configuration)
Jekyll::Configuration::DEFAULTS.merge({'source' => source_dir, 'destination' => dest_dir})
end
@site = Site.new(Jekyll.configuration)
end end
should "filter entries" do should "filter entries" do
@ -50,10 +47,7 @@ class TestEntryFilter < Test::Unit::TestCase
end end
should "filter symlink entries when safe mode enabled" do should "filter symlink entries when safe mode enabled" do
stub(Jekyll).configuration do site = Site.new(site_configuration('safe' => true))
Jekyll::Configuration::DEFAULTS.merge({'source' => source_dir, 'destination' => dest_dir, 'safe' => true})
end
site = Site.new(Jekyll.configuration)
stub(File).symlink?('symlink.js') {true} stub(File).symlink?('symlink.js') {true}
files = %w[symlink.js] files = %w[symlink.js]
assert_equal [], site.filter_entries(files) assert_equal [], site.filter_entries(files)
@ -66,10 +60,7 @@ class TestEntryFilter < Test::Unit::TestCase
end end
should "not include symlinks in safe mode" do should "not include symlinks in safe mode" do
stub(Jekyll).configuration do site = Site.new(site_configuration('safe' => true))
Jekyll::Configuration::DEFAULTS.merge({'source' => source_dir, 'destination' => dest_dir, 'safe' => true})
end
site = Site.new(Jekyll.configuration)
site.read_directories("symlink-test") site.read_directories("symlink-test")
assert_equal [], site.pages assert_equal [], site.pages
@ -77,10 +68,7 @@ class TestEntryFilter < Test::Unit::TestCase
end end
should "include symlinks in unsafe mode" do should "include symlinks in unsafe mode" do
stub(Jekyll).configuration do site = Site.new(site_configuration)
Jekyll::Configuration::DEFAULTS.merge({'source' => source_dir, 'destination' => dest_dir, 'safe' => false})
end
site = Site.new(Jekyll.configuration)
site.read_directories("symlink-test") site.read_directories("symlink-test")
assert_not_equal [], site.pages assert_not_equal [], site.pages
@ -90,10 +78,7 @@ class TestEntryFilter < Test::Unit::TestCase
context "#glob_include?" do context "#glob_include?" do
setup do setup do
stub(Jekyll).configuration do @site = Site.new(site_configuration)
Jekyll::Configuration::DEFAULTS.merge({'source' => source_dir, 'destination' => dest_dir})
end
@site = Site.new(Jekyll.configuration)
@filter = EntryFilter.new(@site) @filter = EntryFilter.new(@site)
end end

View File

@ -13,10 +13,7 @@ class TestExcerpt < Test::Unit::TestCase
context "With extraction disabled" do context "With extraction disabled" do
setup do setup do
clear_dest clear_dest
stub(Jekyll).configuration do @site = Site.new(site_configuration('excerpt_separator' => ''))
Jekyll::Configuration::DEFAULTS.merge({'excerpt_separator' => ''})
end
@site = Site.new(Jekyll.configuration)
@post = setup_post("2013-07-22-post-excerpt-with-layout.markdown") @post = setup_post("2013-07-22-post-excerpt-with-layout.markdown")
end end
@ -29,8 +26,7 @@ class TestExcerpt < Test::Unit::TestCase
context "An extracted excerpt" do context "An extracted excerpt" do
setup do setup do
clear_dest clear_dest
stub(Jekyll).configuration { Jekyll::Configuration::DEFAULTS } @site = Site.new(site_configuration)
@site = Site.new(Jekyll.configuration)
@post = setup_post("2013-07-22-post-excerpt-with-layout.markdown") @post = setup_post("2013-07-22-post-excerpt-with-layout.markdown")
@excerpt = @post.send :extract_excerpt @excerpt = @post.send :extract_excerpt
end end

View File

@ -15,8 +15,11 @@ class TestPage < Test::Unit::TestCase
context "A Page" do context "A Page" do
setup do setup do
clear_dest clear_dest
stub(Jekyll).configuration { Jekyll::Configuration::DEFAULTS } @site = Site.new(Jekyll.configuration({
@site = Site.new(Jekyll.configuration) "source" => source_dir,
"destination" => dest_dir,
"skip_config_files" => true
}))
end end
context "processing pages" do context "processing pages" do

View File

@ -15,8 +15,11 @@ class TestPost < Test::Unit::TestCase
context "A Post" do context "A Post" do
setup do setup do
clear_dest clear_dest
stub(Jekyll).configuration { Jekyll::Configuration::DEFAULTS } @site = Site.new(Jekyll.configuration({
@site = Site.new(Jekyll.configuration) "skip_config_files" => true,
"source" => source_dir,
"destination" => dest_dir
}))
end end
should "ensure valid posts are valid" do should "ensure valid posts are valid" do
@ -145,7 +148,7 @@ class TestPost < Test::Unit::TestCase
do_render(post) do_render(post)
post.write(dest_dir) post.write(dest_dir)
assert !File.exist?(unexpected) assert !File.exist?(unexpected), "../../../baddie.html should not exist."
assert File.exist?(File.expand_path("baddie.html", dest_dir)) assert File.exist?(File.expand_path("baddie.html", dest_dir))
end end
@ -606,7 +609,7 @@ class TestPost < Test::Unit::TestCase
should "include templates" do should "include templates" do
post = setup_post("2008-12-13-include.markdown") post = setup_post("2008-12-13-include.markdown")
post.site.source = File.join(File.dirname(__FILE__), 'source') post.site.instance_variable_set(:@source, File.join(File.dirname(__FILE__), 'source'))
do_render(post) do_render(post)
assert_equal "<<< <hr />\n<p>Tom Preston-Werner\ngithub.com/mojombo</p>\n\n<p>This <em>is</em> cool</p>\n >>>", post.output assert_equal "<<< <hr />\n<p>Tom Preston-Werner\ngithub.com/mojombo</p>\n\n<p>This <em>is</em> cool</p>\n >>>", post.output
@ -685,7 +688,7 @@ class TestPost < Test::Unit::TestCase
context "site config with category" do context "site config with category" do
setup do setup do
config = Jekyll::Configuration::DEFAULTS.merge({ config = site_configuration({
'defaults' => [ 'defaults' => [
'scope' => { 'scope' => {
'path' => '' 'path' => ''
@ -712,7 +715,7 @@ class TestPost < Test::Unit::TestCase
context "site config with categories" do context "site config with categories" do
setup do setup do
config = Jekyll::Configuration::DEFAULTS.merge({ config = site_configuration({
'defaults' => [ 'defaults' => [
'scope' => { 'scope' => {
'path' => '' 'path' => ''

View File

@ -8,7 +8,7 @@ class TestSite < Test::Unit::TestCase
end end
should "look for plugins under the site directory by default" do should "look for plugins under the site directory by default" do
site = Site.new(Jekyll::Configuration::DEFAULTS.merge({'source' => File.expand_path(source_dir)})) site = Site.new(site_configuration)
assert_equal [File.join(source_dir, '_plugins')], site.plugins assert_equal [File.join(source_dir, '_plugins')], site.plugins
end end
@ -44,30 +44,35 @@ class TestSite < Test::Unit::TestCase
end end
context "creating sites" do context "creating sites" do
setup do setup do
stub(Jekyll).configuration do @site = Site.new(site_configuration)
Jekyll::Configuration::DEFAULTS.merge({'source' => source_dir, 'destination' => dest_dir})
end
@site = Site.new(Jekyll.configuration)
@num_invalid_posts = 4 @num_invalid_posts = 4
end end
teardown do
if defined?(MyGenerator)
self.class.send(:remove_const, :MyGenerator)
end
end
should "have an empty tag hash by default" do should "have an empty tag hash by default" do
assert_equal Hash.new, @site.tags assert_equal Hash.new, @site.tags
end end
should "give site with parsed pages and posts to generators" do should "give site with parsed pages and posts to generators" do
@site.reset
@site.read
class MyGenerator < Generator class MyGenerator < Generator
def generate(site) def generate(site)
site.pages.dup.each do |page| site.pages.dup.each do |page|
raise "#{page} isn't a page" unless page.is_a?(Page) raise "#{page} isn't a page" unless page.is_a?(Page)
raise "#{page} doesn't respond to :name" unless page.respond_to?(:name) raise "#{page} doesn't respond to :name" unless page.respond_to?(:name)
end end
site.file_read_opts[:secret_message] = 'hi'
end end
end end
@site = Site.new(site_configuration)
@site.read
@site.generate @site.generate
assert_not_equal 0, @site.pages.size assert_not_equal 0, @site.pages.size
assert_equal 'hi', @site.file_read_opts[:secret_message]
end end
should "reset data before processing" do should "reset data before processing" do
@ -221,22 +226,14 @@ class TestSite < Test::Unit::TestCase
context 'error handling' do context 'error handling' do
should "raise if destination is included in source" do should "raise if destination is included in source" do
stub(Jekyll).configuration do
Jekyll::Configuration::DEFAULTS.merge({'source' => source_dir, 'destination' => source_dir})
end
assert_raise Jekyll::Errors::FatalException do assert_raise Jekyll::Errors::FatalException do
site = Site.new(Jekyll.configuration) site = Site.new(site_configuration('destination' => source_dir))
end end
end end
should "raise if destination is source" do should "raise if destination is source" do
stub(Jekyll).configuration do
Jekyll::Configuration::DEFAULTS.merge({'source' => source_dir, 'destination' => File.join(source_dir, "..")})
end
assert_raise Jekyll::Errors::FatalException do assert_raise Jekyll::Errors::FatalException do
site = Site.new(Jekyll.configuration) site = Site.new(site_configuration('destination' => File.join(source_dir, "..")))
end end
end end
end end
@ -281,7 +278,7 @@ class TestSite < Test::Unit::TestCase
end end
should 'remove orphaned files in destination - keep_files .svn' do should 'remove orphaned files in destination - keep_files .svn' do
config = Jekyll::Configuration::DEFAULTS.merge({'source' => source_dir, 'destination' => dest_dir, 'keep_files' => ['.svn']}) config = site_configuration('keep_files' => %w{.svn})
@site = Site.new(config) @site = Site.new(config)
@site.process @site.process
assert !File.exist?(dest_dir('.htpasswd')) assert !File.exist?(dest_dir('.htpasswd'))
@ -308,7 +305,7 @@ class TestSite < Test::Unit::TestCase
end end
custom_processor = "CustomMarkdown" custom_processor = "CustomMarkdown"
s = Site.new(Jekyll.configuration.merge({ 'markdown' => custom_processor })) s = Site.new(site_configuration('markdown' => custom_processor))
assert_nothing_raised do assert_nothing_raised do
s.process s.process
end end
@ -331,7 +328,7 @@ class TestSite < Test::Unit::TestCase
end end
bad_processor = "Custom::Markdown" bad_processor = "Custom::Markdown"
s = Site.new(Jekyll.configuration.merge({ 'markdown' => bad_processor })) s = Site.new(site_configuration('markdown' => bad_processor))
assert_raise Jekyll::Errors::FatalException do assert_raise Jekyll::Errors::FatalException do
s.process s.process
end end
@ -345,13 +342,13 @@ class TestSite < Test::Unit::TestCase
should 'not throw an error at initialization time' do should 'not throw an error at initialization time' do
bad_processor = 'not a processor name' bad_processor = 'not a processor name'
assert_nothing_raised do assert_nothing_raised do
Site.new(Jekyll.configuration.merge({ 'markdown' => bad_processor })) Site.new(site_configuration('markdown' => bad_processor))
end end
end end
should 'throw FatalException at process time' do should 'throw FatalException at process time' do
bad_processor = 'not a processor name' bad_processor = 'not a processor name'
s = Site.new(Jekyll.configuration.merge({ 'markdown' => bad_processor })) s = Site.new(site_configuration('markdown' => bad_processor))
assert_raise Jekyll::Errors::FatalException do assert_raise Jekyll::Errors::FatalException do
s.process s.process
end end
@ -360,7 +357,7 @@ class TestSite < Test::Unit::TestCase
context 'data directory' do context 'data directory' do
should 'auto load yaml files' do should 'auto load yaml files' do
site = Site.new(Jekyll.configuration) site = Site.new(site_configuration)
site.process site.process
file_content = SafeYAML.load_file(File.join(source_dir, '_data', 'members.yaml')) file_content = SafeYAML.load_file(File.join(source_dir, '_data', 'members.yaml'))
@ -370,7 +367,7 @@ class TestSite < Test::Unit::TestCase
end end
should 'auto load yml files' do should 'auto load yml files' do
site = Site.new(Jekyll.configuration) site = Site.new(site_configuration)
site.process site.process
file_content = SafeYAML.load_file(File.join(source_dir, '_data', 'languages.yml')) file_content = SafeYAML.load_file(File.join(source_dir, '_data', 'languages.yml'))
@ -380,7 +377,7 @@ class TestSite < Test::Unit::TestCase
end end
should 'auto load json files' do should 'auto load json files' do
site = Site.new(Jekyll.configuration) site = Site.new(site_configuration)
site.process site.process
file_content = SafeYAML.load_file(File.join(source_dir, '_data', 'members.json')) file_content = SafeYAML.load_file(File.join(source_dir, '_data', 'members.json'))
@ -390,7 +387,7 @@ class TestSite < Test::Unit::TestCase
end end
should 'auto load yaml files in subdirectory' do should 'auto load yaml files in subdirectory' do
site = Site.new(Jekyll.configuration) site = Site.new(site_configuration)
site.process site.process
file_content = SafeYAML.load_file(File.join(source_dir, '_data', 'categories', 'dairy.yaml')) file_content = SafeYAML.load_file(File.join(source_dir, '_data', 'categories', 'dairy.yaml'))
@ -400,7 +397,7 @@ class TestSite < Test::Unit::TestCase
end end
should "load symlink files in unsafe mode" do should "load symlink files in unsafe mode" do
site = Site.new(Jekyll.configuration.merge({'safe' => false})) site = Site.new(site_configuration('safe' => false))
site.process site.process
file_content = SafeYAML.load_file(File.join(source_dir, '_data', 'products.yml')) file_content = SafeYAML.load_file(File.join(source_dir, '_data', 'products.yml'))
@ -410,7 +407,7 @@ class TestSite < Test::Unit::TestCase
end end
should "not load symlink files in safe mode" do should "not load symlink files in safe mode" do
site = Site.new(Jekyll.configuration.merge({'safe' => true})) site = Site.new(site_configuration('safe' => true))
site.process site.process
assert_nil site.data['products'] assert_nil site.data['products']