diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-04-15 06:09:11 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-04-15 06:09:11 +0300 |
commit | b71a496c7a3e109f7c85ad7ac453e6f7bf7cda45 (patch) | |
tree | 0a76fc00ef860bd369dcaa3f136ee36275eb47f5 /lib/gitlab/wiki_pages | |
parent | c2041156b8b3063d6cf29b324416e8469e588923 (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.rb | 120 |
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 |