Module: K8::Util

Defined in:
lib/keight.rb

Defined Under Namespace

Classes: ShellCommand, ShellCommandError, TemporaryFile

Constant Summary collapse

ESCAPE_HTML =
{'&'=>'&amp;', '<'=>'&lt;', '>'=>'&gt;', '"'=>'&quot;', "'"=>'&#39;'}
MULTIPART_MAX_FILESIZE =

50MB

50 * 1024 * 1024
MULTIPART_BUFFER_SIZE =

10MB

10 * 1024 * 1024
WEEKDAYS =
['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].freeze
MONTHS =
[nil, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'].freeze

Class Method Summary collapse

Class Method Details

._http_utc_time_without_locale(utc_time) ⇒ Object



399
400
401
402
403
# File 'lib/keight.rb', line 399

def _http_utc_time_without_locale(utc_time)
  utc_time.utc?  or
    raise ArgumentError.new("http_utc_time(#{utc_time.inspect}): expected UTC time but got local time.")
  return utc_time.strftime("#{WEEKDAYS[utc_time.wday]}, %d #{MONTHS[utc_time.month]} %Y %H:%M:%S GMT")
end

._mp_err(msg) ⇒ Object



366
367
368
# File 'lib/keight.rb', line 366

def _mp_err(msg)
  return HttpException.new(400, msg)
end

._parse(query_str, separator) ⇒ Object



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/keight.rb', line 245

def _parse(query_str, separator)
  #; [!engr6] returns empty Hash/dict object when query string is empty.
  d = {}
  return d if query_str.empty?
  #; [!fzt3w] parses query string and returns Hash/dict object.
  equal    = '='
  brackets = '[]'
  query_str.split(separator).each do |s|
    #kv = s.split('=', 2)
    #if kv.length == 2
    #  k, v = kv
    #else
    #  k = kv[0]; v = ""
    #end
    k, v = s.split(equal, 2)
    v ||= ''
    k = percent_decode(k) unless k =~ /\A[-.\w]+\z/
    v = percent_decode(v) unless v =~ /\A[-.\w]+\z/
    #; [!t0w33] regards as array of string when param name ends with '[]'.
    if k.end_with?(brackets)
      (d[k] ||= []) << v
    else
      d[k] = v
    end
  end
  return d
end

._parse_multipart(stdin, boundary, content_length, max_filesize, bufsize) {|last| ... } ⇒ Object

Yields:

  • (last)


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
# File 'lib/keight.rb', line 314

def _parse_multipart(stdin, boundary, content_length, max_filesize, bufsize)
  first_line = "--#{boundary}\r\n"
  last_line  = "\r\n--#{boundary}--\r\n"
  separator  = "\r\n--#{boundary}\r\n"
  s = stdin.read(first_line.bytesize)
  s == first_line  or
    raise _mp_err("invalid first line. exected=#{first_line.inspect}, actual=#{s.inspect}")
  len = content_length - first_line.bytesize - last_line.bytesize
  len > 0  or
    raise _mp_err("invalid content length.")
  last = nil
  while len > 0
    n = bufsize < len ? bufsize : len
    buf = stdin.read(n)
    break if buf.nil? || buf.empty?
    len -= buf.bytesize
    buf = (last << buf) if last
    parts = buf.split(separator)
    ! (parts.length == 1 && buf.bytesize > max_filesize)  or
      raise _mp_err("too large file or data (max: about #{max_filesize/(1024*1024)}MB)")
    last = parts.pop()
    parts.each do |part|
      yield part
    end
  end
  yield last if last
  s = stdin.read(last_line.bytesize)
  s == last_line  or
    raise _mp_err("invalid last line.")
end

._parse_multipart_header(header) ⇒ Object



346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
# File 'lib/keight.rb', line 346

def _parse_multipart_header(header)
  cont_disp = cont_type = nil
  header.split("\r\n").each do |line|
    name, val = line.split(/: */, 2)
    if    name == 'Content-Disposition'; cont_disp = val
    elsif name == 'Content-Type'       ; cont_type = val
    else                               ; nil
    end
  end
  cont_disp  or
    raise _mp_err("Content-Disposition is required.")
  cont_disp =~ /form-data; *name=(?:"([^"\r\n]*)"|([^;\r\n]+))/  or
    raise _mp_err("Content-Disposition is invalid.")
  param_name = percent_decode($1 || $2)
  filename = (cont_disp =~ /; *filename=(?:"([^"\r\n]+)"|([^;\r\n]+))/ \
              ? percent_decode($1 || $2) : nil)
  return param_name, filename, cont_type
end

.build_query_string(query) ⇒ Object



273
274
275
276
277
278
279
280
281
282
# File 'lib/keight.rb', line 273

def build_query_string(query)
  case query
  when nil    ; return nil
  when String ; return query
  when Hash, Array
    return query.collect {|k, v| "#{percent_decode(k.to_s)}=#{percent_decode(v.to_s)}" }.join('&')
  else
    raise ArgumentError.new("Hash or Array expected but got #{query.inspect}.")
  end
end

.escape_html(str) ⇒ Object Also known as: h



216
217
218
219
# File 'lib/keight.rb', line 216

def escape_html(str)
  #; [!90jx8] escapes '& < > " \'' into '&amp; &lt; &gt; &quot; &#39;'.
  return str.gsub(/[&<>"']/, ESCAPE_HTML)
end

.guess_content_type(filename, default = 'application/octet-stream') ⇒ Object



380
381
382
383
384
385
# File 'lib/keight.rb', line 380

def guess_content_type(filename, default='application/octet-stream')
  #; [!xw0js] returns content type guessed from filename.
  #; [!dku5c] returns 'application/octet-stream' when failed to guess content type.
  ext = File.extname(filename)
  return MIME_TYPES[ext] || default
end

.http_utc_time(utc_time) ⇒ Object

similar to Time#httpdate() in ‘time.rb’



387
388
389
390
391
392
393
# File 'lib/keight.rb', line 387

def http_utc_time(utc_time)   # similar to Time#httpdate() in 'time.rb'
  #; [!3z5lf] raises error when argument is not UTC.
  utc_time.utc?  or
    raise ArgumentError.new("http_utc_time(#{utc_time.inspect}): expected UTC time but got local time.")
  #; [!5k50b] converts Time object into HTTP date format string.
  return utc_time.strftime('%a, %d %b %Y %H:%M:%S GMT')
end


241
242
243
# File 'lib/keight.rb', line 241

def parse_cookie_string(cookie_str)
  return _parse(cookie_str, /;\s*/)
end

.parse_multipart(stdin, boundary, content_length, max_filesize = nil, bufsize = nil) ⇒ Object



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/keight.rb', line 287

def parse_multipart(stdin, boundary, content_length, max_filesize=nil, bufsize=nil)
  max_filesize ||= MULTIPART_MAX_FILESIZE
  bufsize      ||= MULTIPART_BUFFER_SIZE
  #; [!mqrei] parses multipart form data.
  params = {}   # {"name": "value"}
  files  = {}   # {"name": UploadedFile}
  _parse_multipart(stdin, boundary, content_length, max_filesize, bufsize) do |part|
    header, body = part.split("\r\n\r\n")
    pname, filename, cont_type = _parse_multipart_header(header)
    if filename
      upfile = UploadedFile.new(filename, cont_type) {|f| f.write(body) }
      pvalue = filename
    else
      upfile = nil
      pvalue = body
    end
    if pname.end_with?('[]')
      (params[pname] ||= []) << pvalue
      (files[pname]  ||= []) << upfile if upfile
    else
      params[pname] = pvalue
      files[pname]  = upfile if upfile
    end
  end
  return params, files
end

.parse_query_string(query_str) ⇒ Object



237
238
239
# File 'lib/keight.rb', line 237

def parse_query_string(query_str)
  return _parse(query_str, /[&;]/)
end

.percent_decode(str) ⇒ Object



232
233
234
235
# File 'lib/keight.rb', line 232

def percent_decode(str)
  #; [!kl9sk] decodes percent encoded string.
  return URI.decode_www_form_component(str)
end

.percent_encode(str) ⇒ Object



227
228
229
230
# File 'lib/keight.rb', line 227

def percent_encode(str)
  #; [!a96jo] encodes string into percent encoding format.
  return URI.encode_www_form_component(str)
end

.randstr_b64Object



371
372
373
374
375
376
377
378
# File 'lib/keight.rb', line 371

def randstr_b64()
  #; [!yq0gv] returns random string, encoded with urlsafe base64.
  ## Don't use SecureRandom; entropy of /dev/random or /dev/urandom
  ## should be left for more secure-sensitive purpose.
  s = "#{rand()}#{rand()}#{rand()}#{Time.now.to_f}"
  binary = Digest::SHA1.digest(s)
  return [binary].pack('m').chomp("=\n").tr('+/', '-_')
end