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
359 360 361 362 363 |
# File 'lib/tus/server.rb', line 359 def created!(location) response.status = 201 response.headers["Location"] = location request.halt end |
#error!(status, message) ⇒ Object
365 366 367 368 369 370 |
# File 'lib/tus/server.rb', line 365 def error!(status, ) response.status = status response.write() unless request.head? response.headers["Content-Type"] = "text/plain" request.halt end |
#expiration_interval ⇒ Object
384 385 386 |
# File 'lib/tus/server.rb', line 384 def expiration_interval opts[:expiration_interval] end |
#expiration_time ⇒ Object
380 381 382 |
# File 'lib/tus/server.rb', line 380 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.
189 190 191 192 193 194 195 |
# File 'lib/tus/server.rb', line 189 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
338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 |
# File 'lib/tus/server.rb', line 338 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.
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 |
# File 'lib/tus/server.rb', line 311 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
376 377 378 |
# File 'lib/tus/server.rb', line 376 def max_size opts[:max_size] end |
#no_content! ⇒ Object
354 355 356 357 |
# File 'lib/tus/server.rb', line 354 def no_content! response.status = 204 request.halt end |
#storage ⇒ Object
372 373 374 |
# File 'lib/tus/server.rb', line 372 def storage opts[:storage] || Tus::Storage::Filesystem.new("data") end |
#validate_content_length!(size, info) ⇒ Object
234 235 236 237 238 239 240 241 |
# File 'lib/tus/server.rb', line 234 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
197 198 199 |
# File 'lib/tus/server.rb', line 197 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.
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 |
# File 'lib/tus/server.rb', line 275 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
201 202 203 204 205 206 207 208 |
# File 'lib/tus/server.rb', line 201 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
299 300 301 302 303 304 305 306 307 |
# File 'lib/tus/server.rb', line 299 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
261 262 263 264 265 266 267 268 269 270 271 272 |
# File 'lib/tus/server.rb', line 261 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
243 244 245 |
# File 'lib/tus/server.rb', line 243 def validate_upload_finished!(info) error!(403, "Cannot download unfinished upload") unless info.length == info.offset end |
#validate_upload_length! ⇒ Object
210 211 212 213 214 215 216 217 218 219 220 |
# File 'lib/tus/server.rb', line 210 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
247 248 249 250 251 252 253 254 255 256 257 258 259 |
# File 'lib/tus/server.rb', line 247 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
222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/tus/server.rb', line 222 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 |