Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-04-15 06:09:11 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-04-15 06:09:11 +0300
commitb71a496c7a3e109f7c85ad7ac453e6f7bf7cda45 (patch)
tree0a76fc00ef860bd369dcaa3f136ee36275eb47f5 /lib/gitlab/wiki_pages
parentc2041156b8b3063d6cf29b324416e8469e588923 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'lib/gitlab/wiki_pages')
-rw-r--r--lib/gitlab/wiki_pages/front_matter_parser.rb120
1 files changed, 120 insertions, 0 deletions
diff --git a/lib/gitlab/wiki_pages/front_matter_parser.rb b/lib/gitlab/wiki_pages/front_matter_parser.rb
new file mode 100644
index 00000000000..45dc6cf7fd1
--- /dev/null
+++ b/lib/gitlab/wiki_pages/front_matter_parser.rb
@@ -0,0 +1,120 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module WikiPages
+ class FrontMatterParser
+ FEATURE_FLAG = :wiki_front_matter
+
+ # We limit the maximum length of text we are prepared to parse as YAML, to
+ # avoid exploitations and attempts to consume memory and CPU. We allow for:
+ # - a title line
+ # - a "slugs:" line
+ # - and up to 50 slugs
+ #
+ # This limit does not take comments into account.
+ MAX_SLUGS = 50
+ SLUG_LINE_LENGTH = (4 + Gitlab::WikiPages::MAX_DIRECTORY_BYTES + 1 + Gitlab::WikiPages::MAX_TITLE_BYTES)
+ MAX_FRONT_MATTER_LENGTH = (8 + Gitlab::WikiPages::MAX_TITLE_BYTES) + 7 + (SLUG_LINE_LENGTH * MAX_SLUGS)
+
+ ParseError = Class.new(StandardError)
+
+ class Result
+ attr_reader :front_matter, :content, :reason, :error
+
+ def initialize(content:, front_matter: {}, reason: nil, error: nil)
+ @content = content
+ @front_matter = front_matter.freeze
+ @reason = reason
+ @error = error
+ end
+ end
+
+ # @param [String] wiki_content
+ # @param [FeatureGate] feature_gate The scope for feature availability
+ def initialize(wiki_content, feature_gate)
+ @wiki_content = wiki_content
+ @feature_gate = feature_gate
+ end
+
+ def self.enabled?(gate = nil)
+ Feature.enabled?(FEATURE_FLAG, gate)
+ end
+
+ def parse
+ return empty_result unless enabled? && wiki_content.present?
+ return empty_result(block.error) unless block.valid?
+
+ Result.new(front_matter: block.data, content: strip_front_matter_block)
+ rescue ParseError => error
+ empty_result(:parse_error, error)
+ end
+
+ class Block
+ include Gitlab::Utils::StrongMemoize
+
+ def initialize(delim = nil, lang = '', text = nil)
+ @lang = lang.downcase.presence || Gitlab::FrontMatter::DELIM_LANG[delim]
+ @text = text
+ end
+
+ def data
+ @data ||= YAML.safe_load(text, symbolize_names: true)
+ rescue Psych::DisallowedClass, Psych::SyntaxError => error
+ raise ParseError, error.message
+ end
+
+ def valid?
+ error.nil?
+ end
+
+ def error
+ strong_memoize(:error) { no_match? || too_long? || not_yaml? || not_mapping? }
+ end
+
+ private
+
+ attr_reader :lang, :text
+
+ def no_match?
+ :no_match if text.nil?
+ end
+
+ def not_yaml?
+ :not_yaml if lang != 'yaml'
+ end
+
+ def too_long?
+ :too_long if text.size > MAX_FRONT_MATTER_LENGTH
+ end
+
+ def not_mapping?
+ :not_mapping unless data.is_a?(Hash)
+ end
+ end
+
+ private
+
+ attr_reader :wiki_content, :feature_gate
+
+ def empty_result(reason = nil, error = nil)
+ Result.new(content: wiki_content, reason: reason, error: error)
+ end
+
+ def enabled?
+ self.class.enabled?(feature_gate)
+ end
+
+ def block
+ @block ||= parse_front_matter_block
+ end
+
+ def parse_front_matter_block
+ wiki_content.match(Gitlab::FrontMatter::PATTERN) { |m| Block.new(*m.captures) } || Block.new
+ end
+
+ def strip_front_matter_block
+ wiki_content.gsub(Gitlab::FrontMatter::PATTERN, '')
+ end
+ end
+ end
+end