Class: Tus::Server
- Inherits:
-
Roda
- Object
- Roda
- Tus::Server
- Defined in:
- lib/tus/server.rb
Constant Summary collapse
- SUPPORTED_VERSIONS =
["1.0.0"]
- SUPPORTED_EXTENSIONS =
[ "creation", "creation-defer-length", "termination", "expiration", "concatenation", "checksum", ]
- SUPPORTED_CHECKSUM_ALGORITHMS =
%w[sha1 sha256 sha384 sha512 md5 crc32]
- RESUMABLE_CONTENT_TYPE =
"application/offset+octet-stream"
Instance Method Summary collapse
- #created!(location) ⇒ Object
- #error!(status, message) ⇒ Object
- #expiration_interval ⇒ Object
- #expiration_time ⇒ Object
-
#get_input(info) ⇒ Object
Wraps the Rack input (request body) into a Tus::Input object, applying a size limit if one exists.
- #handle_cors! ⇒ Object
-
#handle_range_request!(length) ⇒ Object
Handles partial responses requested in the “Range” header.
- #max_size ⇒ Object
- #no_content! ⇒ Object
- #storage ⇒ Object
- #validate_content_length!(size, info) ⇒ Object
- #validate_content_type! ⇒ Object
-
#validate_partial_uploads!(part_uids) ⇒ Object
Validates that each partial upload exists and is marked as one.
- #validate_tus_resumable! ⇒ Object
- #validate_upload_checksum!(input) ⇒ Object
- #validate_upload_concat! ⇒ Object
- #validate_upload_finished!(info) ⇒ Object
- #validate_upload_length! ⇒ Object
- #validate_upload_metadata! ⇒ Object
- #validate_upload_offset!(info) ⇒ Object
Instance Method Details
#created!(location) ⇒ Object
356 357 358 359 360 |
# File 'lib/tus/server.rb', line 356 def created!(location) response.status = 201 response.headers["Location"] = location request.halt end |
#error!(status, message) ⇒ Object
362 363 364 365 366 367 |
# File 'lib/tus/server.rb', line 362 def error!(status, ) response.status = status response.write() unless request.head? response.headers["Content-Type"] = "text/plain" request.halt end |
#expiration_interval ⇒ Object
381 382 383 |
# File 'lib/tus/server.rb', line 381 def expiration_interval opts[:expiration_interval] end |
#expiration_time ⇒ Object
377 378 379 |
# File 'lib/tus/server.rb', line 377 def expiration_time opts[:expiration_time] end |
#get_input(info) ⇒ Object
Wraps the Rack input (request body) into a Tus::Input object, applying a size limit if one exists.
186 187 188 189 190 191 192 |
# File 'lib/tus/server.rb', line 186 def get_input(info) offset = info.offset total = info.length || max_size limit = total - offset if total Tus::Input.new(request.body, limit: limit) end |
#handle_cors! ⇒ Object
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 |
# File 'lib/tus/server.rb', line 335 def handle_cors! origin = request.headers["Origin"] return if origin.to_s == "" response.headers["Access-Control-Allow-Origin"] = origin if request. response.headers["Access-Control-Allow-Methods"] = "POST, GET, HEAD, PATCH, DELETE, OPTIONS" response.headers["Access-Control-Allow-Headers"] = "Origin, X-Requested-With, Content-Type, Upload-Length, Upload-Offset, Tus-Resumable, Upload-Metadata" response.headers["Access-Control-Max-Age"] = "86400" else response.headers["Access-Control-Expose-Headers"] = "Upload-Offset, Location, Upload-Length, Tus-Version, Tus-Resumable, Tus-Max-Size, Tus-Extension, Upload-Metadata" end end |
#handle_range_request!(length) ⇒ Object
Handles partial responses requested in the “Range” header. Implementation is mostly copied from Rack::File.
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 |
# File 'lib/tus/server.rb', line 308 def handle_range_request!(length) # we support ranged requests response.headers["Accept-Ranges"] = "bytes" if Rack.release >= "2.0" ranges = Rack::Utils.get_byte_ranges(request.headers["Range"], length) else ranges = Rack::Utils.byte_ranges(request.env, length) end if ranges.nil? || ranges.length > 1 # no ranges, or multiple ranges (which we don't support) response.status = 200 range = 0..length-1 elsif ranges.empty? # unsatisfiable range response.headers["Content-Range"] = "bytes */#{length}" error!(416, "Byte range unsatisfiable") else range = ranges[0] response.status = 206 response.headers["Content-Range"] = "bytes #{range.begin}-#{range.end}/#{length}" end range end |
#max_size ⇒ Object
373 374 375 |
# File 'lib/tus/server.rb', line 373 def max_size opts[:max_size] end |
#no_content! ⇒ Object
351 352 353 354 |
# File 'lib/tus/server.rb', line 351 def no_content! response.status = 204 request.halt end |
#storage ⇒ Object
369 370 371 |
# File 'lib/tus/server.rb', line 369 def storage opts[:storage] || Tus::Storage::Filesystem.new("data") end |
#validate_content_length!(size, info) ⇒ Object
231 232 233 234 235 236 237 238 |
# File 'lib/tus/server.rb', line 231 def validate_content_length!(size, info) if info.length error!(403, "Cannot modify completed upload") if info.offset == info.length error!(413, "Size of this chunk surpasses Upload-Length") if info.offset + size > info.length elsif max_size error!(413, "Size of this chunk surpasses Tus-Max-Size") if info.offset + size > max_size end end |
#validate_content_type! ⇒ Object
194 195 196 |
# File 'lib/tus/server.rb', line 194 def validate_content_type! error!(415, "Invalid Content-Type header") if request.content_type != RESUMABLE_CONTENT_TYPE end |
#validate_partial_uploads!(part_uids) ⇒ Object
Validates that each partial upload exists and is marked as one.
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 |
# File 'lib/tus/server.rb', line 272 def validate_partial_uploads!(part_uids) queue = Queue.new part_uids.each { |part_uid| queue << part_uid } threads = 10.times.map do Thread.new do results = [] loop do part_uid = queue.deq(true) rescue break part_info = storage.read_info(part_uid) results << part_info["Upload-Concat"] end results end end upload_concat_values = threads.flat_map(&:value) unless upload_concat_values.all? { |value| value == "partial" } error!(400, "One or more uploads were not partial") end rescue Tus::NotFound error!(400, "One or more partial uploads were not found") end |
#validate_tus_resumable! ⇒ Object
198 199 200 201 202 203 204 205 |
# File 'lib/tus/server.rb', line 198 def validate_tus_resumable! client_version = request.headers["Tus-Resumable"] unless SUPPORTED_VERSIONS.include?(client_version) response.headers["Tus-Version"] = SUPPORTED_VERSIONS.join(",") error!(412, "Unsupported version") end end |
#validate_upload_checksum!(input) ⇒ Object
296 297 298 299 300 301 302 303 304 |
# File 'lib/tus/server.rb', line 296 def validate_upload_checksum!(input) algorithm, checksum = request.headers["Upload-Checksum"].split(" ") error!(400, "Invalid Upload-Checksum header") if algorithm.nil? || checksum.nil? error!(400, "Invalid Upload-Checksum header") unless SUPPORTED_CHECKSUM_ALGORITHMS.include?(algorithm) generated_checksum = Tus::Checksum.generate(algorithm, input) error!(460, "Upload-Checksum value doesn't match generated checksum") if generated_checksum != checksum end |
#validate_upload_concat! ⇒ Object
258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/tus/server.rb', line 258 def validate_upload_concat! upload_concat = request.headers["Upload-Concat"] error!(400, "Invalid Upload-Concat header") if upload_concat !~ /^(partial|final)/ if upload_concat.start_with?("final") string = upload_concat.split(";").last string.split(" ").each do |url| error!(400, "Invalid Upload-Concat header") if url !~ %r{^#{request.script_name}/\w+$} end end end |
#validate_upload_finished!(info) ⇒ Object
240 241 242 |
# File 'lib/tus/server.rb', line 240 def validate_upload_finished!(info) error!(403, "Cannot download unfinished upload") unless info.length == info.offset end |
#validate_upload_length! ⇒ Object
207 208 209 210 211 212 213 214 215 216 217 |
# File 'lib/tus/server.rb', line 207 def validate_upload_length! upload_length = request.headers["Upload-Length"] error!(400, "Missing Upload-Length header") if upload_length.to_s == "" error!(400, "Invalid Upload-Length header") if upload_length =~ /\D/ error!(400, "Invalid Upload-Length header") if upload_length.to_i < 0 if max_size && upload_length.to_i > max_size error!(413, "Upload-Length header too large") end end |
#validate_upload_metadata! ⇒ Object
244 245 246 247 248 249 250 251 252 253 254 255 256 |
# File 'lib/tus/server.rb', line 244 def = request.headers["Upload-Metadata"] .split(",").each do |string| key, value = string.split(" ") error!(400, "Invalid Upload-Metadata header") if key.nil? error!(400, "Invalid Upload-Metadata header") if key.ord > 127 error!(400, "Invalid Upload-Metadata header") if key =~ /,| / error!(400, "Invalid Upload-Metadata header") if value =~ /[^a-zA-Z0-9+\/=]/ end end |
#validate_upload_offset!(info) ⇒ Object
219 220 221 222 223 224 225 226 227 228 229 |
# File 'lib/tus/server.rb', line 219 def validate_upload_offset!(info) upload_offset = request.headers["Upload-Offset"] error!(400, "Missing Upload-Offset header") if upload_offset.to_s == "" error!(400, "Invalid Upload-Offset header") if upload_offset =~ /\D/ error!(400, "Invalid Upload-Offset header") if upload_offset.to_i < 0 if upload_offset.to_i != info.offset error!(409, "Upload-Offset header doesn't match current offset") end end |