Class: Tilia::Dav::Browser::Plugin

Inherits:
ServerPlugin show all
Defined in:
lib/tilia/dav/browser/plugin.rb

Overview

Browser Plugin

This plugin provides a html representation, so that a WebDAV server may be accessed using a browser.

The class intercepts GET requests to collection resources and generates a simple html index.

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from ServerPlugin

#features, #http_methods, #supported_report_set

Constructor Details

#initialize(enable_post = true) ⇒ Plugin

Creates the object.

By default it will allow file creation and uploads. Specify the first argument as false to disable this

Parameters:

  • bool

    enable_post



41
42
43
44
45
46
47
48
49
50
# File 'lib/tilia/dav/browser/plugin.rb', line 41

def initialize(enable_post = true)
  @enable_post = true
  @uninteresting_properties = [
    '{DAV:}supportedlock',
    '{DAV:}acl-restrictions',
    '{DAV:}supported-privilege-set',
    '{DAV:}supported-method-set'
  ]
  @enable_post = enable_post
end

Instance Attribute Details

#uninteresting_propertiesObject

A list of properties that are usually not interesting. This can cut down the browser output a bit by removing the properties that most people will likely not want to see.



33
34
35
# File 'lib/tilia/dav/browser/plugin.rb', line 33

def uninteresting_properties
  @uninteresting_properties
end

Instance Method Details

#escape_html(value) ⇒ Object

Escapes a string for html.

Parameters:

  • string

    value

Returns:

  • string



200
201
202
# File 'lib/tilia/dav/browser/plugin.rb', line 200

def escape_html(value)
  CGI.escapeHTML(value)
end

#generate_directory_index(path) ⇒ Object

Generates the html directory index for a given url

Parameters:

  • string

    path

Returns:

  • string



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/tilia/dav/browser/plugin.rb', line 208

def generate_directory_index(path)
  html = generate_header(path.blank? ? '/' : path, path)

  node = @server.tree.node_for_path(path)
  if node.is_a?(ICollection)
    html << '<section><h1>Nodes</h1>'
    html << '<table class="nodeTable">'

    sub_nodes = @server.properties_for_children(
      path,
      [
        '{DAV:}displayname',
        '{DAV:}resourcetype',
        '{DAV:}getcontenttype',
        '{DAV:}getcontentlength',
        '{DAV:}getlastmodified'
      ]
    )

    sub_nodes.each do |sub_path, _sub_props|
      sub_node = @server.tree.node_for_path(sub_path)
      full_path = @server.base_uri + Http::UrlUtil.encode_path(sub_path)
      (_, display_path) = Http::UrlUtil.split_path(sub_path)

      sub_nodes[sub_path]['subNode'] = sub_node
      sub_nodes[sub_path]['fullPath'] = full_path
      sub_nodes[sub_path]['displayPath'] = display_path
    end
    sub_nodes.sort { |a, b| compare_nodes(a, b) }

    sub_nodes.each do |_, sub_props|
      type = {
        'string' => 'Unknown',
        'icon'   => 'cog'
      }
      if sub_props.key?('{DAV:}resourcetype')
        type = map_resource_type(sub_props['{DAV:}resourcetype'].value, sub_props['subNode'])
      end

      html << '<tr>'
      html << '<td class="nameColumn"><a href="' + escape_html(sub_props['fullPath']) + '"><span class="oi" data-glyph="' + escape_html(type['icon']) + '"></span> ' + escape_html(sub_props['displayPath']) + '</a></td>'
      html << '<td class="typeColumn">' + escape_html(type['string']) + '</td>'
      html << '<td>'
      if sub_props.key?('{DAV:}getcontentlength')
        html << escape_html(sub_props['{DAV:}getcontentlength'].to_s + ' bytes')
      end
      html << '</td><td>'
      if sub_props.key?('{DAV:}getlastmodified')
        last_mod = sub_props['{DAV:}getlastmodified'].time
        html << escape_html(last_mod.strftime('%B %e, %Y, %l:%M %P'))
      end
      html << '</td>'

      button_actions = ''
      if sub_props['sub_node'].is_a?(IFile)
        button_actions = '<a href="' + escape_html(sub_props['fullPath']) + '?sabreAction=info"><span class="oi" data-glyph="info"></span></a>'
      end

      box = Box.new(button_actions)
      @server.emit('browserButtonActions', [sub_props['fullPath'], sub_props['subNode'], box])
      button_actions = box.value

      html << "<td>#{button_actions}</td>"
      html << '</tr>'
    end

    html << '</table>'

  end

  html << '</section>'
  html << '<section><h1>Properties</h1>'
  html << '<table class="propTable">'

  # Allprops request
  prop_find = PropFindAll.new(path)
  properties = @server.properties_by_node(prop_find, node)

  properties = prop_find.result_for_multi_status[200]

  properties.each do |prop_name, prop_value|
    if @uninteresting_properties.include?(prop_name)
      html << draw_property_row(prop_name, prop_value)
    end
  end

  html << '</table>'
  html << '</section>'

  # Start of generating actions

  output = ''
  if @enable_post
    box = Box.new(output)
    @server.emit('onHTMLActionsPanel', [node, box])
    output = box.value
  end

  if output
    html << '<section><h1>Actions</h1>'
    html << '<div class="actions">'
    html << output
    html << '</div>'
    html << '</section>'
  end

  html << generate_footer

  @server.http_response.update_header('Content-Security-Policy', "img-src 'self'; style-src 'self';")

  html
end

Generates the page footer.

Returns html.

Returns:

  • string



405
406
407
408
409
410
411
412
# File 'lib/tilia/dav/browser/plugin.rb', line 405

def generate_footer
  version = Version::VERSION
  <<HTML
    <footer>Generated by TiliaDAV #{version} (c)2015-2015 <a href="http://tiliadav.github.io/">http://tiliadav.github.io/</a></footer>
  </body>
</html>
HTML
end

#generate_header(title, path = nil) ⇒ Object

Generates the first block of HTML, including the <head> tag and page header.

Returns footer.

Parameters:

  • string

    title

  • string

    path

Returns:

  • void



355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
# File 'lib/tilia/dav/browser/plugin.rb', line 355

def generate_header(title, path = nil)
  version = Version::VERSION

  vars = {
    'title'     => escape_html(title),
    'favicon'   => escape_html(asset_url('favicon.ico')),
    'style'     => escape_html(asset_url('sabredav.css')),
    'iconstyle' => escape_html(asset_url('openiconic/open-iconic.css')),
    'logo'      => escape_html(asset_url('sabredav.png')),
    'baseUrl'   => @server.base_uri
  }

  html = <<HTML
<!DOCTYPE html>
<html>
<head>
  <title>#{vars['title']} - tilia/dav #{version}</title>
  <link rel="shortcut icon" href="#{vars['favicon']}"   type="image/vnd.microsoft.icon" />
  <link rel="stylesheet"    href="#{vars['style']}"     type="text/css" />
  <link rel="stylesheet"    href="#{vars['iconstyle']}" type="text/css" />
</head>
<body>
  <header>
      <div class="logo">
  <a href="#{vars['baseUrl']}"><img src="#{vars['logo']}" alt="tilia/dav" /> #{vars['title']}</a>
      </div>
  </header>
  <nav>
HTML

  # If the path is empty, there's no parent.
  if !path.blank?
    parent_uri = Http::UrlUtil.split_path(path).first
    full_path = @server.base_uri + Http::UrlUtil.encode_path(parent_uri)
    html << "<a href=\"#{full_path}\" class=\"btn\">⇤ Go to parent</a>"
  else
    html << '<span class="btn disabled">⇤ Go to parent</span>'
  end

  html << ' <a href="?sabreAction=plugins" class="btn"><span class="oi" data-glyph="puzzle-piece"></span> Plugins</a>'
  html << '</nav>'

  html
end

#generate_plugin_listingObject

Generates the ‘plugins’ page.

Returns:

  • string



324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/tilia/dav/browser/plugin.rb', line 324

def generate_plugin_listing
  html = generate_header('Plugins')

  html << '<section><h1>Plugins</h1>'
  html << '<table class="propTable">'
  @server.plugins.each do |_, plugin|
    info = plugin.plugin_info
    html << "<tr><th>#{info['name']}</th>"
    html << "<td>#{info['description']}</td>"
    html << '<td>'
    if info.key?('link') && !info['link'].blank?
      html << "<a href=\"#{escape_html(info['link'])}\"><span class=\"oi\" data-glyph=\"book\"></span></a>"
    end
    html << '</td></tr>'
  end
  html << '</table>'
  html << '</section>'

  html << generate_footer

  html
end

#html_actions_panel(node, output) ⇒ Object

This method is used to generate the ‘actions panel’ output for collections.

This specifically generates the interfaces for creating new files, and creating new directories.

Parameters:

  • DAV\INode

    node

  • mixed

    output

Returns:

  • void



423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
# File 'lib/tilia/dav/browser/plugin.rb', line 423

def html_actions_panel(node, output)
  return nil unless node.is_a?(ICollection)

  # We also know fairly certain that if an object is a non-extended
  # SimpleCollection, we won't need to show the panel either.
  return nil if node.class == Tilia::Dav::SimpleCollection

  output.value << <<FORM
<form method="post" action="">
  <h3>Create new folder</h3>
  <input type="hidden" name="sabreAction" value="mkcol" />
  <label>Name:</label> <input type="text" name="name" /><br />
  <input type="submit" value="create" />
</form>
<form method="post" action="" enctype="multipart/form-data">
  <h3>Upload file</h3>
  <input type="hidden" name="sabreAction" value="put" />
  <label>Name (optional):</label> <input type="text" name="name" /><br />
  <label>File:</label> <input type="file" name="file" /><br />
  <input type="submit" value="upload" />
</form>
FORM
end

#http_get(request, response) ⇒ Object

This method intercepts GET requests to collections and returns the html

Parameters:

  • RequestInterface

    request

  • ResponseInterface

    response

Returns:

  • bool



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/tilia/dav/browser/plugin.rb', line 81

def http_get(request, response)
  # We're not using straight-up $_GET, because we want everything to be
  # unit testable.
  get_vars = request.query_parameters

  # CSP headers
  @server.http_response.update_header('Content-Security-Policy', "img-src 'self'; style-src 'self';")

  sabre_action = get_vars['sabreAction']

  case sabre_action
  when 'asset'
    # Asset handling, such as images
    serve_asset(get_vars['assetName'])
    return false
  when 'plugins'
    response.status = 200
    response.update_header('Content-Type', 'text/html; charset=utf-8')

    response.body = generate_plugin_listing
    return false
  else # includes "when 'info'"
    begin
      @server.tree.node_for_path(request.path)
    rescue Exception::NotFound => e
      # We're simply stopping when the file isn't found to not interfere
      # with other plugins.
      return nil
    end

    response.status = 200
    response.update_header('Content-Type', 'text/html; charset=utf-8')

    response.body = generate_directory_index(request.path)

    return false
  end
end

#http_get_early(request, response) ⇒ Object

This method intercepts GET requests that have ?sabreAction=info appended to the URL

Parameters:

  • RequestInterface

    request

  • ResponseInterface

    response

Returns:

  • bool



71
72
73
74
# File 'lib/tilia/dav/browser/plugin.rb', line 71

def http_get_early(request, response)
  params = request.query_parameters
  return http_get(request, response) if params['sabreAction'] == 'info'
end

#http_post(request, response) ⇒ Object

Handles POST requests for tree operations.

Parameters:

  • RequestInterface

    request

  • ResponseInterface

    response

Returns:

  • bool



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/tilia/dav/browser/plugin.rb', line 125

def http_post(request, response)
  content_type = request.header('Content-Type')
  content_type = content_type.split(';').first

  if content_type != 'application/x-www-form-urlencoded' &&
     content_type != 'multipart/form-data'
    return nil
  end

  post_vars = request.post_data
  return nil unless post_vars.key?('sabreAction')

  uri = request.path

  if @server.emit('onBrowserPostAction', [uri, post_vars['sabreAction'], post_vars])

    case post_vars['sabreAction']
    when 'mkcol'
      if post_vars.key?('name') && !post_vars['name'].blank?
        # Using basename because we won't allow slashes
        folder_name = Http::UrlUtil.split_path(post_vars['name'].strip)[1]

        if post_vars.key?('resourceType')
          resource_type = post_vars['resourceType'].split(',')
        else
          resource_type = ['{DAV:}collection']
        end

        properties = {}
        post_vars.each do |var_name, var_value|
          # Any _POST variable in clark notation is treated
          # like a property.
          next unless var_name[0] == '{'
          var_name = var_name.gsub('*DOT*')
          properties[var_name] = var_value
        end

        mk_col = MkCol.new(resource_type, properties)
        @server.create_collection(uri + '/' + folder_name, mk_col)
      end
    when 'put' # FIXME
      # USE server.http_request.post_data (Rack.POST)
      file = nil
      server.http_request.params.each do |_, value|
        if value.is_a?(Rack::Multipart::UploadedFile)
          file = value
          break
        end
      end

      if file
        # Making sure we only have a 'basename' component
        new_name = ::File.basename(file.original_filename).strip

        if post_vars.key?('name') && !post_vars['name'].blank?
          new_name = ::File.basename(post_vars['name']).strip
        end

        # is there a necessary equivalent in ruby?
        # if (is_uploaded_file(file['tmp_name'])) {
        @server.create_file("#{uri}/#{new_name}", ::File.open(file.path), 'r')
        # end
      end
    end
  end

  response.update_header('Location', request.url)
  response.status = 302
  false
end

#plugin_infoObject

Returns a bunch of meta-data about the plugin.

Providing this information is optional, and is mainly displayed by the Browser plugin.

The description key in the returned array may contain html and will not be sanitized.

Returns:

  • array



683
684
685
686
687
688
689
# File 'lib/tilia/dav/browser/plugin.rb', line 683

def plugin_info
  {
    'name'        => plugin_name,
    'description' => 'Generates HTML indexes and debug information for your sabre/dav server',
    'link'        => 'http://sabre.io/dav/browser-plugin/'
  }
end

#plugin_nameObject

Returns a plugin name.

Using this name other plugins will be able to access other plugins using SabreDAVServer::getPlugin

Returns:

  • string



670
671
672
# File 'lib/tilia/dav/browser/plugin.rb', line 670

def plugin_name
  'browser'
end

#setup(server) ⇒ Object

Initializes the plugin and subscribes to events

Parameters:

  • DAV\Server

    server

Returns:

  • void



56
57
58
59
60
61
62
63
# File 'lib/tilia/dav/browser/plugin.rb', line 56

def setup(server)
  @server = server
  @server.on('method:GET', method(:http_get_early), 90)
  @server.on('method:GET', method(:http_get), 200)
  @server.on('onHTMLActionsPanel', method(:html_actions_panel), 200)

  @server.on('method:POST', method(:http_post)) if @enable_post
end