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"- HOOKS =
i[before_create after_create after_finish after_terminate]
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
- #redirect_download ⇒ 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
419 420 421 422 423 |
# File 'lib/tus/server.rb', line 419 def created!(location) response.status = 201 response.headers["Location"] = location request.halt end |
#error!(status, message) ⇒ Object
425 426 427 428 429 430 |
# File 'lib/tus/server.rb', line 425 def error!(status, ) response.status = status response.write() unless request.head? response.headers["Content-Type"] = "text/plain" request.halt end |
#expiration_interval ⇒ Object
457 458 459 |
# File 'lib/tus/server.rb', line 457 def expiration_interval opts[:expiration_interval] end |
#expiration_time ⇒ Object
453 454 455 |
# File 'lib/tus/server.rb', line 453 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.
228 229 230 231 232 233 234 |
# File 'lib/tus/server.rb', line 228 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
398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 |
# File 'lib/tus/server.rb', line 398 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.
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 |
# File 'lib/tus/server.rb', line 371 def handle_range_request!(length) 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 # we support ranged requests response.headers["Accept-Ranges"] = "bytes" 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
449 450 451 |
# File 'lib/tus/server.rb', line 449 def max_size opts[:max_size] end |
#no_content! ⇒ Object
414 415 416 417 |
# File 'lib/tus/server.rb', line 414 def no_content! response.status = 204 request.halt end |
#redirect_download ⇒ Object
432 433 434 435 436 437 438 439 440 441 442 443 |
# File 'lib/tus/server.rb', line 432 def redirect_download value = opts[:redirect_download] if opts[:download_url] value ||= opts[:download_url] warn "[TUS-RUBY-SERVER DEPRECATION] The :download_url option has been renamed to :redirect_download." end value = storage.method(:file_url) if value == true value end |
#storage ⇒ Object
445 446 447 |
# File 'lib/tus/server.rb', line 445 def storage opts[:storage] || Tus::Storage::Filesystem.new("data") end |
#validate_content_length!(size, info) ⇒ Object
273 274 275 276 277 278 279 280 |
# File 'lib/tus/server.rb', line 273 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
236 237 238 |
# File 'lib/tus/server.rb', line 236 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.
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 |
# File 'lib/tus/server.rb', line 314 def validate_partial_uploads!(part_uids) input = Queue.new part_uids.each { |part_uid| input << part_uid } input.close results = Queue.new thread_count = storage.concurrency[:concatenation] if storage.respond_to?(:concurrency) thread_count ||= 10 threads = thread_count.times.map do Thread.new do begin loop do part_uid = input.pop or break part_info = storage.read_info(part_uid) results << Tus::Info.new(part_info) end nil rescue => error input.clear error end end end errors = threads.map(&:value).compact if errors.any? { |error| error.is_a?(Tus::NotFound) } error!(400, "One or more partial uploads were not found") elsif errors.any? fail errors.first end part_infos = Array.new(results.size) { results.pop } # convert Queue into an Array unless part_infos.all?(&:partial?) error!(400, "One or more uploads were not partial") end if max_size && part_infos.map(&:length).inject(0, :+) > max_size error!(400, "The sum of partial upload lengths exceed Tus-Max-Size") end end |
#validate_tus_resumable! ⇒ Object
240 241 242 243 244 245 246 247 |
# File 'lib/tus/server.rb', line 240 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
359 360 361 362 363 364 365 366 367 |
# File 'lib/tus/server.rb', line 359 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
300 301 302 303 304 305 306 307 308 309 310 311 |
# File 'lib/tus/server.rb', line 300 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 !~ /#{request.script_name}\/\w+$/ end end end |
#validate_upload_finished!(info) ⇒ Object
282 283 284 |
# File 'lib/tus/server.rb', line 282 def validate_upload_finished!(info) error!(403, "Cannot download unfinished upload") unless info.length == info.offset end |
#validate_upload_length! ⇒ Object
249 250 251 252 253 254 255 256 257 258 259 |
# File 'lib/tus/server.rb', line 249 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
286 287 288 289 290 291 292 293 294 295 296 297 298 |
# File 'lib/tus/server.rb', line 286 def = request.headers["Upload-Metadata"] .split(",").each do |string| key, value = string.split(" ", 2) 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
261 262 263 264 265 266 267 268 269 270 271 |
# File 'lib/tus/server.rb', line 261 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 |