Class: Tilia::Dav::PartialUpdate::Plugin

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

Overview

Partial update plugin (Patch method)

This plugin provides a way to modify only part of a target resource It may bu used to update a file chunk, upload big a file into smaller chunks or resume an upload.

patch_plugin = new SabreDAVPartialUpdatePlugin server.add_plugin(patch_plugin)

Constant Summary collapse

RANGE_APPEND =
1
RANGE_START =
2
RANGE_END =
3

Instance Method Summary collapse

Methods inherited from ServerPlugin

#plugin_info, #supported_report_set

Instance Method Details

#featuresObject

Returns a list of features for the HTTP OPTIONS Dav: header.

Returns:

  • array



68
69
70
# File 'lib/tilia/dav/partial_update/plugin.rb', line 68

def features
  ['sabredav-partialupdate']
end

#http_methods(uri) ⇒ Object

Use this method to tell the server this plugin defines additional HTTP methods.

This method is passed a uri. It should only return HTTP methods that are available for the specified uri.

We claim to support PATCH method (partirl update) if and only if

- the node exist
- the node implements our partial update interface

Parameters:

  • string

    uri

Returns:

  • array



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

def http_methods(uri)
  tree = @server.tree

  if tree.node_exists(uri)
    node = tree.node_for_path(uri)
    return ['PATCH'] if node.is_a?(IPatchSupport)
  end
  []
end

#http_patch(request, response) ⇒ Object

Patch an uri

The WebDAV patch request can be used to modify only a part of an existing resource. If the resource does not exist yet and the first offset is not 0, the request fails

Parameters:

  • RequestInterface

    request

  • ResponseInterface

    response

Returns:

  • void



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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/tilia/dav/partial_update/plugin.rb', line 81

def http_patch(request, response)
  path = request.path

  # Get the node. Will throw a 404 if not found
  node = @server.tree.node_for_path(path)
  unless node.is_a?(IPatchSupport)
    fail Exception::MethodNotAllowed, 'The target resource does not support the PATCH method.'
  end

  range = http_update_range(request)

  unless range
    fail Exception::BadRequest, 'No valid "X-Update-Range" found in the headers'
  end

  content_type = request.header('Content-Type').to_s.downcase

  unless content_type == 'application/x-sabredav-partialupdate'
    fail Exception::UnsupportedMediaType, "Unknown Content-Type header \"#{content_type}\""
  end

  len = @server.http_request.header('Content-Length').to_i
  if len == 0
    fail Exception::LengthRequired, 'A Content-Length header is required'
  end

  case range[0]
  when RANGE_START
    # Calculate the end-range if it doesn't exist.
    if range[2].blank?
      range[2] = range[1] + len - 1
    else
      if range[2] < range[1]
        fail Exception::RequestedRangeNotSatisfiable, "The end offset (#{range[2]}) is lower than the start offset (#{range[1]})"
      end
      if range[2] - range[1] + 1 != len
        fail Exception::RequestedRangeNotSatisfiable, "Actual data length (#{len}) is not consistent with begin (#{range[1]}) and end (#{range[2]}) offsets"
      end
    end
  end

  unless @server.emit('beforeWriteContent', [path, node, nil, nil])
    return nil
  end

  body = @server.http_request.body

  etag = node.patch(body, range[0], range[1])

  @server.emit('afterWriteContent', [path, node])

  response.update_header('Content-Length', '0')
  response.update_header('ETag', etag) if etag

  response.status = 204

  # Breaks the event chain
  false
end

#http_update_range(request) ⇒ Object

Returns the HTTP custom range update header

This method returns null if there is no well-formed HTTP range request header. It returns array(1) if it was an append request, array(2, start, end) if it’s a start and end range, lastly it’s array(3, endoffset) if the offset was negative, and should be calculated from the end of the file.

Examples:

null - invalid

1
  • append

2,10,15
  • update bytes 10, 11, 12, 13, 14, 15

2,10,null
  • update bytes 10 until the end of the patch body

3,-5
  • update from 5 bytes from the end of the file.

Parameters:

  • RequestInterface

    request

Returns:

  • array|null



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/tilia/dav/partial_update/plugin.rb', line 159

def http_update_range(request)
  range = request.header('X-Update-Range')
  return nil unless range

  # Matching "Range: bytes=1234-5678: both numbers are optional

  matches = /^(append)|(?:bytes=([0-9]+)-([0-9]*))|(?:bytes=(-[0-9]+))$/i.match(range)
  return nil unless matches

  if matches[1] == 'append'
    return [RANGE_APPEND]
  elsif matches[2].to_s.size > 0
    return [RANGE_START, matches[2].to_i, matches[3].blank? ? nil : matches[3].to_i]
  else
    return [RANGE_END, matches[4].to_i]
  end
end

#plugin_nameObject

Returns a plugin name.

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

Returns:

  • string



39
40
41
# File 'lib/tilia/dav/partial_update/plugin.rb', line 39

def plugin_name
  'partialupdate'
end

#setup(server) ⇒ Object

Initializes the plugin

This method is automatically called by the Server class after addPlugin.

Parameters:

  • DAV\Server

    server

Returns:

  • void



28
29
30
31
# File 'lib/tilia/dav/partial_update/plugin.rb', line 28

def setup(server)
  @server = server
  server.on('method:PATCH', method(:http_patch))
end