gollum-lib 4.0.3-java

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.
Files changed (41) hide show
  1. checksums.yaml +15 -0
  2. data/Gemfile +3 -0
  3. data/HISTORY.md +11 -0
  4. data/LICENSE +21 -0
  5. data/README.md +286 -0
  6. data/Rakefile +187 -0
  7. data/docs/sanitization.md +33 -0
  8. data/gemspec.rb +100 -0
  9. data/gollum-lib.gemspec +4 -0
  10. data/gollum-lib_java.gemspec +4 -0
  11. data/lib/gollum-lib.rb +64 -0
  12. data/lib/gollum-lib/blob_entry.rb +95 -0
  13. data/lib/gollum-lib/committer.rb +243 -0
  14. data/lib/gollum-lib/file.rb +158 -0
  15. data/lib/gollum-lib/file_view.rb +155 -0
  16. data/lib/gollum-lib/filter.rb +78 -0
  17. data/lib/gollum-lib/filter/code.rb +145 -0
  18. data/lib/gollum-lib/filter/macro.rb +57 -0
  19. data/lib/gollum-lib/filter/metadata.rb +29 -0
  20. data/lib/gollum-lib/filter/plain_text.rb +16 -0
  21. data/lib/gollum-lib/filter/remote_code.rb +63 -0
  22. data/lib/gollum-lib/filter/render.rb +20 -0
  23. data/lib/gollum-lib/filter/sanitize.rb +18 -0
  24. data/lib/gollum-lib/filter/tags.rb +320 -0
  25. data/lib/gollum-lib/filter/toc.rb +109 -0
  26. data/lib/gollum-lib/filter/wsd.rb +54 -0
  27. data/lib/gollum-lib/git_access.rb +247 -0
  28. data/lib/gollum-lib/gitcode.rb +48 -0
  29. data/lib/gollum-lib/helpers.rb +13 -0
  30. data/lib/gollum-lib/hook.rb +35 -0
  31. data/lib/gollum-lib/macro.rb +43 -0
  32. data/lib/gollum-lib/macro/all_pages.rb +11 -0
  33. data/lib/gollum-lib/markup.rb +197 -0
  34. data/lib/gollum-lib/markups.rb +20 -0
  35. data/lib/gollum-lib/page.rb +491 -0
  36. data/lib/gollum-lib/pagination.rb +62 -0
  37. data/lib/gollum-lib/sanitization.rb +176 -0
  38. data/lib/gollum-lib/version.rb +5 -0
  39. data/lib/gollum-lib/wiki.rb +925 -0
  40. data/licenses/licenses.txt +6 -0
  41. metadata +410 -0
@@ -0,0 +1,57 @@
1
+ # ~*~ encoding: utf-8 ~*~
2
+
3
+ # Replace specified tokens with dynamically generated content.
4
+ class Gollum::Filter::Macro < Gollum::Filter
5
+ def extract(data)
6
+ quoted_arg = %r{".*?"}
7
+ unquoted_arg = %r{[^,)]+}
8
+ named_arg = %r{[a-z0-9_]+=".*?"}
9
+
10
+ arg = %r{(?:#{quoted_arg}|#{unquoted_arg}|#{named_arg})}
11
+ arg_list = %r{(\s*|#{arg}(?:\s*,\s*#{arg})*)}
12
+
13
+ data.gsub(/('?)\<\<\s*([A-Z][A-Za-z0-9]*)\s*\(#{arg_list}\)\s*\>\>/) do
14
+ next CGI.escape_html($&[1..-1]) unless $1.empty?
15
+ id = Digest::SHA1.hexdigest($2 + $3)
16
+ macro = $2
17
+ argstr = $3
18
+ args = []
19
+ opts = {}
20
+
21
+ argstr.scan /,?\s*(#{arg})\s*/ do |arg|
22
+ # Stabstabstab
23
+ arg = arg.first
24
+
25
+ if arg =~ /^([a-z0-9_]+)="(.*?)"/
26
+ opts[$1] = $2
27
+ elsif arg =~ /^"(.*)"$/
28
+ args << $1
29
+ else
30
+ args << arg
31
+ end
32
+ end
33
+
34
+ args << opts unless opts.empty?
35
+
36
+ @map[id] = { :macro => macro, :args => args }
37
+ id
38
+ end
39
+ end
40
+
41
+ def process(data)
42
+ @map.each do |id, spec|
43
+ macro = spec[:macro]
44
+ args = spec[:args]
45
+
46
+ data.gsub!(id) do
47
+ begin
48
+ Gollum::Macro.instance(macro, @markup.wiki, @markup.page).render(*args)
49
+ rescue StandardError => e
50
+ "!!!Macro Error: #{e.message}!!!"
51
+ end
52
+ end
53
+ end
54
+
55
+ data
56
+ end
57
+ end
@@ -0,0 +1,29 @@
1
+ # Extract metadata for data and build metadata table. Metadata consists of
2
+ # key/value pairs in "key:value" format found between markers. Each
3
+ # key/value pair must be on its own line. Internal whitespace in keys and
4
+ # values is preserved, but external whitespace is ignored.
5
+ #
6
+ # Because ri and ruby 1.8.7 are awesome, the markers can't
7
+ # be included in this documentation without triggering
8
+ # `Unhandled special: Special: type=17`
9
+ # Please read the source code for the exact markers
10
+ class Gollum::Filter::Metadata < Gollum::Filter
11
+ def extract(data)
12
+ # The markers are `<!-- ---` and `-->`
13
+ data.gsub(/\<\!--+\s+---(.*?)--+\>/m) do
14
+ @markup.metadata ||= {}
15
+ # Split untrusted input on newlines, then remove bits that look like
16
+ # HTML elements before parsing each line.
17
+ $1.split("\n").each do |line|
18
+ line.gsub!(/<[^>]*>/, '')
19
+ k, v = line.split(':', 2)
20
+ @markup.metadata[k.strip] = (v ? v.strip : '') if k
21
+ end
22
+ ''
23
+ end
24
+ end
25
+
26
+ def process(d)
27
+ d
28
+ end
29
+ end
@@ -0,0 +1,16 @@
1
+ # ~*~ encoding: utf-8 ~*~
2
+
3
+ # Plain Text
4
+ #
5
+ # Render plain text documents in a <pre> block without any special markup.
6
+
7
+ class Gollum::Filter::PlainText < Gollum::Filter
8
+
9
+ def extract(data)
10
+ @markup.format == :txt ? "<pre>#{CGI.escapeHTML(data)}</pre>" : data
11
+ end
12
+
13
+ def process(data)
14
+ data
15
+ end
16
+ end
@@ -0,0 +1,63 @@
1
+ # ~*~ encoding: utf-8 ~*~
2
+ require 'net/http'
3
+ require 'net/https' # ruby 1.8.7 fix, remove at upgrade
4
+ require 'uri'
5
+ require 'open-uri'
6
+
7
+ # Remote code - fetch code from url and replace the contents to a
8
+ # code-block that gets run the next parse.
9
+ # Acceptable formats:
10
+ # ```language:local-file.ext```
11
+ # ```language:/abs/other-file.ext```
12
+ # ```language:https://example.com/somefile.txt```
13
+ #
14
+ class Gollum::Filter::RemoteCode < Gollum::Filter
15
+ def extract(data)
16
+ return data if @markup.format == :txt
17
+ data.gsub /^[ \t]*``` ?([^:\n\r]+):((http)?[^`\n\r]+)```/ do
18
+ language = $1
19
+ uri = $2
20
+ protocol = $3
21
+
22
+ # Detect local file
23
+ if protocol.nil?
24
+ if file = @markup.find_file(uri, @markup.wiki.ref)
25
+ contents = file.raw_data
26
+ else
27
+ # How do we communicate a render error?
28
+ next html_error("File not found: #{CGI::escapeHTML(uri)}")
29
+ end
30
+ else
31
+ contents = req(uri)
32
+ end
33
+
34
+ "```#{language}\n#{contents}\n```\n"
35
+ end
36
+ end
37
+
38
+ def process(d)
39
+ d
40
+ end
41
+
42
+ private
43
+
44
+ def req uri, cut = 1
45
+ uri = URI(uri)
46
+ return "Too many redirects or retries" if cut >= 10
47
+ http = Net::HTTP.new uri.host, uri.port
48
+ http.use_ssl = true
49
+ resp = http.get uri.path, {
50
+ 'Accept' => 'text/plain',
51
+ 'Cache-Control' => 'no-cache',
52
+ 'Connection' => 'keep-alive',
53
+ 'User-Agent' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0'
54
+ }
55
+ code = resp.code.to_i
56
+ return resp.body if code == 200
57
+ return "Not Found" if code == 404
58
+ return "Unhandled Response Code #{code}" unless code == 304 or not resp.header['location'].nil?
59
+ loc = URI.parse resp.header['location']
60
+ uri2 = loc.relative? ? (uri + loc) : loc # overloads (+)
61
+ req uri2, (cut + 1)
62
+ end
63
+ end
@@ -0,0 +1,20 @@
1
+ # ~*~ encoding: utf-8 ~*~
2
+
3
+ class Gollum::Filter::Render < Gollum::Filter
4
+ def extract(data)
5
+ begin
6
+ data = GitHub::Markup.render(@markup.name, data)
7
+ if data.nil?
8
+ raise "There was an error converting #{@markup.name} to HTML."
9
+ end
10
+ rescue Object => e
11
+ data = html_error("Failed to render page: #{e.message}")
12
+ end
13
+
14
+ data
15
+ end
16
+
17
+ def process(d)
18
+ d
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ # ~*~ encoding: utf-8 ~*~
2
+
3
+ class Gollum::Filter::Sanitize < Gollum::Filter
4
+ def extract(d)
5
+ d
6
+ end
7
+
8
+ def process(data)
9
+ if @markup.sanitize
10
+ doc = Nokogiri::HTML::DocumentFragment.parse(data)
11
+ doc = @markup.sanitize.clean_node!(doc)
12
+
13
+ doc.to_xml(@markup.to_xml_opts)
14
+ else
15
+ data
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,320 @@
1
+ # ~*~ encoding: utf-8 ~*~
2
+
3
+ # Render all tags (things in double-square-brackets). This one's a biggie.
4
+ class Gollum::Filter::Tags < Gollum::Filter
5
+ # Extract all tags into the tagmap and replace with placeholders.
6
+ def extract(data)
7
+ return data if @markup.format == :txt || @markup.format == :asciidoc
8
+ data.gsub!(/(.?)\[\[(.+?)\]\]([^\[]?)/m) do
9
+ if $1 == "'" && $3 != "'"
10
+ "[[#{$2}]]#{$3}"
11
+ elsif $2.include?('][')
12
+ if $2[0..4] == 'file:'
13
+ pre = $1
14
+ post = $3
15
+ parts = $2.split('][')
16
+ parts[0][0..4] = ""
17
+ link = "#{parts[1]}|#{parts[0].sub(/\.org/, '')}"
18
+ id = register_tag(link)
19
+ "#{pre}#{id}#{post}"
20
+ else
21
+ $&
22
+ end
23
+ else
24
+ id = register_tag($2)
25
+ "#{$1}#{id}#{$3}"
26
+ end
27
+ end
28
+ data
29
+ end
30
+
31
+ def register_tag(tag)
32
+ id = "TAG#{Digest::SHA1.hexdigest(tag)}TAG"
33
+ @map[id] = tag
34
+ id
35
+ end
36
+
37
+ # Process all text nodes from the doc and replace the placeholders with the
38
+ # final markup.
39
+ def process(rendered_data)
40
+ doc = Nokogiri::HTML::DocumentFragment.parse(rendered_data)
41
+ doc.traverse do |node|
42
+ if node.text? then
43
+ content = node.content
44
+ content.gsub!(/TAG[a-f0-9]+TAG/) do |id|
45
+ if tag = @map[id] then
46
+ if is_preformatted?(node) then
47
+ "[[#{tag}]]"
48
+ else
49
+ process_tag(tag).gsub('%2f', '/')
50
+ end
51
+ end
52
+ end
53
+ node.replace(content) if content != node.content
54
+ end
55
+ end
56
+
57
+ doc.to_html
58
+ end
59
+
60
+ private
61
+
62
+ PREFORMATTED_TAGS = %w(code tt)
63
+
64
+ def is_preformatted?(node)
65
+ node && (PREFORMATTED_TAGS.include?(node.name) ||
66
+ node.ancestors.any? { |a| PREFORMATTED_TAGS.include?(a.name) })
67
+ end
68
+
69
+ # Process a single tag into its final HTML form.
70
+ #
71
+ # tag - The String tag contents (the stuff inside the double
72
+ # brackets).
73
+ #
74
+ # Returns the String HTML version of the tag.
75
+ def process_tag(tag)
76
+ if tag =~ /^_TOC_$/
77
+ %{[[#{tag}]]}
78
+ elsif tag =~ /^_$/
79
+ %{<div class="clearfloats"></div>}
80
+ elsif html = process_include_tag(tag)
81
+ html
82
+ elsif html = process_image_tag(tag)
83
+ html
84
+ elsif html = process_external_link_tag(tag)
85
+ html
86
+ elsif html = process_file_link_tag(tag)
87
+ html
88
+ else
89
+ process_page_link_tag(tag)
90
+ end
91
+ end
92
+
93
+ # Attempt to process the tag as an include tag
94
+ #
95
+ # tag - The String tag contents (the stuff inside the double brackets).
96
+ #
97
+ # Returns the String HTML if the tag is a valid image tag or nil
98
+ # if it is not.
99
+ #
100
+ def process_include_tag(tag)
101
+ return unless /^include:/.match(tag)
102
+ page_name = tag[8..-1]
103
+ resolved_page_name = ::File.expand_path(page_name, "/"+@markup.dir)
104
+
105
+ if @markup.include_levels > 0
106
+ page = find_page_from_name(resolved_page_name)
107
+ if page
108
+ page.formatted_data(@markup.encoding, @markup.include_levels-1)
109
+ else
110
+ html_error("Cannot include #{process_page_link_tag(resolved_page_name)} - does not exist yet")
111
+ end
112
+ else
113
+ html_error("Too many levels of included pages, will not include #{process_page_link_tag(resolved_page_name)}")
114
+ end
115
+ end
116
+
117
+ # Attempt to process the tag as an image tag.
118
+ #
119
+ # tag - The String tag contents (the stuff inside the double brackets).
120
+ #
121
+ # Returns the String HTML if the tag is a valid image tag or nil
122
+ # if it is not.
123
+ def process_image_tag(tag)
124
+ parts = tag.split('|')
125
+ return if parts.size.zero?
126
+
127
+ name = parts[0].strip
128
+ path = if file = @markup.find_file(name)
129
+ ::File.join @markup.wiki.base_path, file.path
130
+ elsif name =~ /^https?:\/\/.+(jpg|png|gif|svg|bmp)$/i
131
+ name
132
+ end
133
+
134
+ if path
135
+ opts = parse_image_tag_options(tag)
136
+
137
+ containered = false
138
+
139
+ classes = [] # applied to whatever the outermost container is
140
+ attrs = [] # applied to the image
141
+
142
+ align = opts['align']
143
+ if opts['float']
144
+ containered = true
145
+ align ||= 'left'
146
+ if %w{left right}.include?(align)
147
+ classes << "float-#{align}"
148
+ end
149
+ elsif %w{top texttop middle absmiddle bottom absbottom baseline}.include?(align)
150
+ attrs << %{align="#{align}"}
151
+ elsif align
152
+ if %w{left center right}.include?(align)
153
+ containered = true
154
+ classes << "align-#{align}"
155
+ end
156
+ end
157
+
158
+ if width = opts['width']
159
+ if width =~ /^\d+(\.\d+)?(em|px)$/
160
+ attrs << %{width="#{width}"}
161
+ end
162
+ end
163
+
164
+ if height = opts['height']
165
+ if height =~ /^\d+(\.\d+)?(em|px)$/
166
+ attrs << %{height="#{height}"}
167
+ end
168
+ end
169
+
170
+ if alt = opts['alt']
171
+ attrs << %{alt="#{alt}"}
172
+ end
173
+
174
+ attr_string = attrs.size > 0 ? attrs.join(' ') + ' ' : ''
175
+
176
+ if opts['frame'] || containered
177
+ classes << 'frame' if opts['frame']
178
+ %{<span class="#{classes.join(' ')}">} +
179
+ %{<span>} +
180
+ %{<img src="#{path}" #{attr_string}/>} +
181
+ (alt ? %{<span>#{alt}</span>} : '') +
182
+ %{</span>} +
183
+ %{</span>}
184
+ else
185
+ %{<img src="#{path}" #{attr_string}/>}
186
+ end
187
+ end
188
+ end
189
+
190
+ # Parse any options present on the image tag and extract them into a
191
+ # Hash of option names and values.
192
+ #
193
+ # tag - The String tag contents (the stuff inside the double brackets).
194
+ #
195
+ # Returns the options Hash:
196
+ # key - The String option name.
197
+ # val - The String option value or true if it is a binary option.
198
+ def parse_image_tag_options(tag)
199
+ tag.split('|')[1..-1].inject({}) do |memo, attr|
200
+ parts = attr.split('=').map { |x| x.strip }
201
+ memo[parts[0]] = (parts.size == 1 ? true : parts[1])
202
+ memo
203
+ end
204
+ end
205
+
206
+ # Return the String HTML if the tag is a valid external link tag or
207
+ # nil if it is not.
208
+ def process_external_link_tag(tag)
209
+ parts = tag.split('|')
210
+ return if parts.size.zero?
211
+ if parts.size == 1
212
+ url = parts[0].strip
213
+ else
214
+ name, url = *parts.compact.map(&:strip)
215
+ end
216
+ accepted_protocols = @markup.wiki.sanitization.protocols['a']['href'].dup
217
+ if accepted_protocols.include?(:relative)
218
+ accepted_protocols.select!{|protocol| protocol != :relative}
219
+ regexp = %r{^((#{accepted_protocols.join("|")}):)?(//)}
220
+ else
221
+ regexp = %r{^((#{accepted_protocols.join("|")}):)}
222
+ end
223
+ if url =~ regexp
224
+ if name.nil?
225
+ %{<a href="#{url}">#{url}</a>}
226
+ else
227
+ %{<a href="#{url}">#{name}</a>}
228
+ end
229
+ else
230
+ nil
231
+ end
232
+
233
+ end
234
+
235
+ # Attempt to process the tag as a file link tag.
236
+ #
237
+ # tag - The String tag contents (the stuff inside the double
238
+ # brackets).
239
+ #
240
+ # Returns the String HTML if the tag is a valid file link tag or nil
241
+ # if it is not.
242
+ def process_file_link_tag(tag)
243
+ parts = tag.split('|')
244
+ return if parts.size.zero?
245
+
246
+ name = parts[0].strip
247
+ path = parts[1] && parts[1].strip
248
+ path = if path && file = @markup.find_file(path)
249
+ ::File.join @markup.wiki.base_path, file.path
250
+ else
251
+ nil
252
+ end
253
+
254
+ if name && path && file
255
+ %{<a href="#{::File.join @markup.wiki.base_path, file.path}">#{name}</a>}
256
+ elsif name && path
257
+ %{<a href="#{path}">#{name}</a>}
258
+ else
259
+ nil
260
+ end
261
+ end
262
+
263
+ # Attempt to process the tag as a page link tag.
264
+ #
265
+ # tag - The String tag contents (the stuff inside the double
266
+ # brackets).
267
+ #
268
+ # Returns the String HTML if the tag is a valid page link tag or nil
269
+ # if it is not.
270
+ def process_page_link_tag(tag)
271
+ parts = tag.split('|')
272
+ parts.reverse! if @markup.format == :mediawiki
273
+
274
+ name, page_name = *parts.compact.map(&:strip)
275
+ cname = @markup.wiki.page_class.cname(page_name || name)
276
+
277
+ presence = "absent"
278
+ link_name = cname
279
+ page, extra = find_page_from_name(cname)
280
+ if page
281
+ link_name = @markup.wiki.page_class.cname(page.name)
282
+ presence = "present"
283
+ end
284
+ link = ::File.join(@markup.wiki.base_path, page ? page.escaped_url_path : CGI.escape(link_name))
285
+
286
+ # //page is invalid
287
+ # strip all duplicate forward slashes using helpers.rb trim_leading_slash
288
+ # //page => /page
289
+ link = trim_leading_slash link
290
+
291
+ %{<a class="internal #{presence}" href="#{link}#{extra}">#{name}</a>}
292
+ end
293
+
294
+ # Find a page from a given cname. If the page has an anchor (#) and has
295
+ # no match, strip the anchor and try again.
296
+ #
297
+ # cname - The String canonical page name including path.
298
+ #
299
+ # Returns a Gollum::Page instance if a page is found, or an Array of
300
+ # [Gollum::Page, String extra] if a page without the extra anchor data
301
+ # is found.
302
+ def find_page_from_name(cname)
303
+ slash = cname.rindex('/')
304
+
305
+ unless slash.nil?
306
+ name = cname[slash+1..-1]
307
+ path = cname[0..slash]
308
+ page = @markup.wiki.paged(name, path)
309
+ else
310
+ page = @markup.wiki.paged(cname, '/') || @markup.wiki.page(cname)
311
+ end
312
+
313
+ if page
314
+ return page
315
+ end
316
+ if pos = cname.index('#')
317
+ [@markup.wiki.page(cname[0...pos]), cname[pos..-1]]
318
+ end
319
+ end
320
+ end