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
- #download_url ⇒ 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
372 373 374 375 376 |
# File 'lib/tus/server.rb', line 372 def created!(location) response.status = 201 response.headers["Location"] = location request.halt end |
#download_url ⇒ Object
385 386 387 388 389 390 391 |
# File 'lib/tus/server.rb', line 385 def download_url if opts[:download_url] == true storage.method(:file_url) else opts[:download_url] end end |
#error!(status, message) ⇒ Object
378 379 380 381 382 383 |
# File 'lib/tus/server.rb', line 378 def error!(status, ) response.status = status response.write() unless request.head? response.headers["Content-Type"] = "text/plain" request.halt end |
#expiration_interval ⇒ Object
405 406 407 |
# File 'lib/tus/server.rb', line 405 def expiration_interval opts[:expiration_interval] end |
#expiration_time ⇒ Object
401 402 403 |
# File 'lib/tus/server.rb', line 401 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.
202 203 204 205 206 207 208 |
# File 'lib/tus/server.rb', line 202 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
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 |
# File 'lib/tus/server.rb', line 351 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.
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 |
# File 'lib/tus/server.rb', line 324 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
397 398 399 |
# File 'lib/tus/server.rb', line 397 def max_size opts[:max_size] end |
#no_content! ⇒ Object
367 368 369 370 |
# File 'lib/tus/server.rb', line 367 def no_content! response.status = 204 request.halt end |
#storage ⇒ Object
393 394 395 |
# File 'lib/tus/server.rb', line 393 def storage opts[:storage] || Tus::Storage::Filesystem.new("data") end |
#validate_content_length!(size, info) ⇒ Object
247 248 249 250 251 252 253 254 |
# File 'lib/tus/server.rb', line 247 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
210 211 212 |
# File 'lib/tus/server.rb', line 210 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.
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 |
# File 'lib/tus/server.rb', line 288 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
214 215 216 217 218 219 220 221 |
# File 'lib/tus/server.rb', line 214 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
312 313 314 315 316 317 318 319 320 |
# File 'lib/tus/server.rb', line 312 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
274 275 276 277 278 279 280 281 282 283 284 285 |
# File 'lib/tus/server.rb', line 274 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
256 257 258 |
# File 'lib/tus/server.rb', line 256 def validate_upload_finished!(info) error!(403, "Cannot download unfinished upload") unless info.length == info.offset end |
#validate_upload_length! ⇒ Object
223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/tus/server.rb', line 223 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
260 261 262 263 264 265 266 267 268 269 270 271 272 |
# File 'lib/tus/server.rb', line 260 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
235 236 237 238 239 240 241 242 243 244 245 |
# File 'lib/tus/server.rb', line 235 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 |