gollum-lib 4.0.3-java

Sign up to get free protection for your applications and to get access to all the features.
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