Class: Tus::Server

Inherits:
Roda
  • Object
show all
Defined in:
lib/tus/server.rb

Constant Summary collapse

SUPPORTED_VERSIONS =
["1.0.0"]
SUPPORTED_EXTENSIONS =
[
  "creation", "creation-defer-length",
  "termination",
  "expiration",
  "concatenation", "concatenation-unfinished",
  "checksum",
]
SUPPORTED_CHECKSUM_ALGORITHMS =
%w[sha1 sha256 sha384 sha512 md5 crc32]
RESUMABLE_CONTENT_TYPE =
"application/offset+octet-stream"

Instance Method Summary collapse

Instance Method Details

#created!(location) ⇒ Object



304
305
306
307
308
# File 'lib/tus/server.rb', line 304

def created!(location)
  response.status = 201
  response.headers["Location"] = location
  request.halt
end

#error!(status, message) ⇒ Object



314
315
316
317
318
# File 'lib/tus/server.rb', line 314

def error!(status, message)
  response.status = status
  response.write(message) unless request.head?
  request.halt
end

#expiration_intervalObject



332
333
334
# File 'lib/tus/server.rb', line 332

def expiration_interval
  opts[:expiration_interval]
end

#expiration_timeObject



328
329
330
# File 'lib/tus/server.rb', line 328

def expiration_time
  opts[:expiration_time]
end

#handle_cors!Object



283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/tus/server.rb', line 283

def handle_cors!
  origin = request.headers["Origin"]

  return if origin.to_s == ""

  response.headers["Access-Control-Allow-Origin"] = origin

  if request.options?
    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



258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/tus/server.rb', line 258

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_sizeObject



324
325
326
# File 'lib/tus/server.rb', line 324

def max_size
  opts[:max_size]
end

#no_content!Object



299
300
301
302
# File 'lib/tus/server.rb', line 299

def no_content!
  response.status = 204
  request.halt
end

#not_found!(message = "Upload not found") ⇒ Object



310
311
312
# File 'lib/tus/server.rb', line 310

def not_found!(message = "Upload not found")
  error!(404, message)
end

#storageObject



320
321
322
# File 'lib/tus/server.rb', line 320

def storage
  opts[:storage] || Tus::Storage::Filesystem.new("data")
end

#validate_content_length!(current_offset, length) ⇒ Object



207
208
209
210
211
212
213
214
# File 'lib/tus/server.rb', line 207

def validate_content_length!(current_offset, length)
  if length
    error!(403, "Cannot modify completed upload") if current_offset == length
    error!(413, "Size of this chunk surpasses Upload-Length") if Integer(request.content_length) + current_offset > length
  else
    error!(413, "Size of this chunk surpasses Tus-Max-Size") if Integer(request.content_length) + current_offset > max_size
  end
end

#validate_content_type!Object



170
171
172
# File 'lib/tus/server.rb', line 170

def validate_content_type!
  error!(415, "Invalid Content-Type header") if request.content_type != RESUMABLE_CONTENT_TYPE
end

#validate_tus_resumable!Object



174
175
176
177
178
179
180
181
# File 'lib/tus/server.rb', line 174

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



247
248
249
250
251
252
253
254
255
# File 'lib/tus/server.rb', line 247

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, "Checksum from Upload-Checksum header doesn't match generated") if generated_checksum != checksum
end

#validate_upload_concat!Object



234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/tus/server.rb', line 234

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!(length, current_offset) ⇒ Object



216
217
218
# File 'lib/tus/server.rb', line 216

def validate_upload_finished!(length, current_offset)
  error!(403, "Cannot download unfinished upload") unless length && current_offset && length == current_offset
end

#validate_upload_length!Object



183
184
185
186
187
188
189
190
191
192
193
# File 'lib/tus/server.rb', line 183

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



220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/tus/server.rb', line 220

def validate_upload_metadata!
   = request.headers["Upload-Metadata"]

  .split(",").each do |string|
    key, value = string.split(" ")

    error!(400, "Invalid Upload-Metadata header") if key.nil? || value.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!(current_offset) ⇒ Object



195
196
197
198
199
200
201
202
203
204
205
# File 'lib/tus/server.rb', line 195

def validate_upload_offset!(current_offset)
  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 != current_offset
    error!(409, "Upload-Offset header doesn't match current offset")
  end
end