Class: Tus::Server
- Inherits:
-
Roda
- Object
- Roda
- Tus::Server
- Defined in:
- lib/tus/server.rb
Defined Under Namespace
Classes: Goliath
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
- #handle_cors! ⇒ Object
-
#handle_range_request!(length) ⇒ Object
“Range” header handling logic copied from Rack::File.
- #max_size ⇒ Object
- #no_content! ⇒ Object
- #storage ⇒ Object
- #validate_content_length!(size, info) ⇒ Object
- #validate_content_type! ⇒ Object
- #validate_partial_uploads!(part_uids) ⇒ Object
- #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
346 347 348 349 350 |
# File 'lib/tus/server.rb', line 346 def created!(location) response.status = 201 response.headers["Location"] = location request.halt end |
#error!(status, message) ⇒ Object
352 353 354 355 356 357 |
# File 'lib/tus/server.rb', line 352 def error!(status, ) response.status = status response.write() unless request.head? response.headers["Content-Type"] = "text/plain" request.halt end |
#expiration_interval ⇒ Object
371 372 373 |
# File 'lib/tus/server.rb', line 371 def expiration_interval opts[:expiration_interval] end |
#expiration_time ⇒ Object
367 368 369 |
# File 'lib/tus/server.rb', line 367 def expiration_time opts[:expiration_time] end |
#get_input(info) ⇒ Object
180 181 182 183 184 185 186 |
# File 'lib/tus/server.rb', line 180 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
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 |
# File 'lib/tus/server.rb', line 325 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
“Range” header handling logic copied from Rack::File
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 |
# File 'lib/tus/server.rb', line 300 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 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. Return error, and file size: response.headers["Content-Range"] = "bytes */#{length}" error!(416, "Byte range unsatisfiable") else # Partial content: range = ranges[0] response.status = 206 response.headers["Content-Range"] = "bytes #{range.begin}-#{range.end}/#{length}" end range end |
#max_size ⇒ Object
363 364 365 |
# File 'lib/tus/server.rb', line 363 def max_size opts[:max_size] end |
#no_content! ⇒ Object
341 342 343 344 |
# File 'lib/tus/server.rb', line 341 def no_content! response.status = 204 request.halt end |
#storage ⇒ Object
359 360 361 |
# File 'lib/tus/server.rb', line 359 def storage opts[:storage] || Tus::Storage::Filesystem.new("data") end |
#validate_content_length!(size, info) ⇒ Object
225 226 227 228 229 230 231 232 |
# File 'lib/tus/server.rb', line 225 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
188 189 190 |
# File 'lib/tus/server.rb', line 188 def validate_content_type! error!(415, "Invalid Content-Type header") if request.content_type != RESUMABLE_CONTENT_TYPE end |
#validate_partial_uploads!(part_uids) ⇒ Object
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 |
# File 'lib/tus/server.rb', line 265 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!(404, "One or more partial uploads were not found") end |
#validate_tus_resumable! ⇒ Object
192 193 194 195 196 197 198 199 |
# File 'lib/tus/server.rb', line 192 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
289 290 291 292 293 294 295 296 297 |
# File 'lib/tus/server.rb', line 289 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
252 253 254 255 256 257 258 259 260 261 262 263 |
# File 'lib/tus/server.rb', line 252 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
234 235 236 |
# File 'lib/tus/server.rb', line 234 def validate_upload_finished!(info) error!(403, "Cannot download unfinished upload") unless info.length == info.offset end |
#validate_upload_length! ⇒ Object
201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/tus/server.rb', line 201 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
238 239 240 241 242 243 244 245 246 247 248 249 250 |
# File 'lib/tus/server.rb', line 238 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
213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/tus/server.rb', line 213 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 |