swift 2.32.1__py2.py3-none-any.whl → 2.33.1__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. swift/account/server.py +1 -11
  2. swift/cli/info.py +28 -1
  3. swift-2.32.1.data/scripts/swift-recon-cron → swift/cli/recon_cron.py +4 -13
  4. swift/cli/reload.py +141 -0
  5. swift/common/daemon.py +12 -2
  6. swift/common/db.py +12 -8
  7. swift/common/http_protocol.py +76 -3
  8. swift/common/manager.py +18 -5
  9. swift/common/memcached.py +18 -12
  10. swift/common/middleware/proxy_logging.py +35 -27
  11. swift/common/middleware/s3api/acl_handlers.py +1 -1
  12. swift/common/middleware/s3api/controllers/__init__.py +3 -0
  13. swift/common/middleware/s3api/controllers/acl.py +3 -2
  14. swift/common/middleware/s3api/controllers/logging.py +2 -2
  15. swift/common/middleware/s3api/controllers/multi_upload.py +30 -6
  16. swift/common/middleware/s3api/controllers/object_lock.py +44 -0
  17. swift/common/middleware/s3api/s3api.py +4 -0
  18. swift/common/middleware/s3api/s3request.py +19 -12
  19. swift/common/middleware/s3api/s3response.py +13 -2
  20. swift/common/middleware/s3api/utils.py +1 -1
  21. swift/common/middleware/slo.py +395 -298
  22. swift/common/middleware/staticweb.py +45 -14
  23. swift/common/middleware/tempurl.py +132 -91
  24. swift/common/request_helpers.py +32 -8
  25. swift/common/storage_policy.py +1 -1
  26. swift/common/swob.py +5 -2
  27. swift/common/utils/__init__.py +230 -135
  28. swift/common/utils/timestamp.py +23 -2
  29. swift/common/wsgi.py +8 -0
  30. swift/container/backend.py +126 -21
  31. swift/container/replicator.py +42 -6
  32. swift/container/server.py +264 -145
  33. swift/container/sharder.py +50 -30
  34. swift/container/updater.py +1 -0
  35. swift/obj/auditor.py +2 -1
  36. swift/obj/diskfile.py +55 -19
  37. swift/obj/expirer.py +1 -13
  38. swift/obj/mem_diskfile.py +2 -1
  39. swift/obj/mem_server.py +1 -0
  40. swift/obj/replicator.py +2 -2
  41. swift/obj/server.py +12 -23
  42. swift/obj/updater.py +1 -0
  43. swift/obj/watchers/dark_data.py +72 -34
  44. swift/proxy/controllers/account.py +3 -2
  45. swift/proxy/controllers/base.py +217 -127
  46. swift/proxy/controllers/container.py +274 -289
  47. swift/proxy/controllers/obj.py +98 -141
  48. swift/proxy/server.py +2 -12
  49. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-container-info +3 -0
  50. swift-2.33.1.data/scripts/swift-recon-cron +24 -0
  51. {swift-2.32.1.dist-info → swift-2.33.1.dist-info}/AUTHORS +3 -1
  52. {swift-2.32.1.dist-info → swift-2.33.1.dist-info}/METADATA +4 -3
  53. {swift-2.32.1.dist-info → swift-2.33.1.dist-info}/RECORD +94 -91
  54. {swift-2.32.1.dist-info → swift-2.33.1.dist-info}/WHEEL +1 -1
  55. {swift-2.32.1.dist-info → swift-2.33.1.dist-info}/entry_points.txt +1 -0
  56. swift-2.33.1.dist-info/pbr.json +1 -0
  57. swift-2.32.1.dist-info/pbr.json +0 -1
  58. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-account-audit +0 -0
  59. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-account-auditor +0 -0
  60. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-account-info +0 -0
  61. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-account-reaper +0 -0
  62. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-account-replicator +0 -0
  63. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-account-server +0 -0
  64. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-config +0 -0
  65. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-container-auditor +0 -0
  66. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-container-reconciler +0 -0
  67. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-container-replicator +0 -0
  68. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-container-server +0 -0
  69. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-container-sharder +0 -0
  70. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-container-sync +0 -0
  71. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-container-updater +0 -0
  72. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-dispersion-populate +0 -0
  73. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-dispersion-report +0 -0
  74. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-drive-audit +0 -0
  75. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-form-signature +0 -0
  76. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-get-nodes +0 -0
  77. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-init +0 -0
  78. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-object-auditor +0 -0
  79. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-object-expirer +0 -0
  80. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-object-info +0 -0
  81. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-object-reconstructor +0 -0
  82. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-object-relinker +0 -0
  83. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-object-replicator +0 -0
  84. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-object-server +0 -0
  85. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-object-updater +0 -0
  86. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-oldies +0 -0
  87. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-orphans +0 -0
  88. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-proxy-server +0 -0
  89. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-recon +0 -0
  90. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-reconciler-enqueue +0 -0
  91. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-ring-builder +0 -0
  92. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-ring-builder-analyzer +0 -0
  93. {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-ring-composer +0 -0
  94. {swift-2.32.1.dist-info → swift-2.33.1.dist-info}/LICENSE +0 -0
  95. {swift-2.32.1.dist-info → swift-2.33.1.dist-info}/top_level.txt +0 -0
@@ -59,7 +59,8 @@ requests for paths not found.
59
59
 
60
60
  For pseudo paths that have no <index.name>, this middleware can serve HTML file
61
61
  listings if you set the ``X-Container-Meta-Web-Listings: true`` metadata item
62
- on the container.
62
+ on the container. Note that the listing must be authorized; you may want a
63
+ container ACL like ``X-Container-Read: .r:*,.rlistings``.
63
64
 
64
65
  If listings are enabled, the listings can have a custom style sheet by setting
65
66
  the X-Container-Meta-Web-Listings-CSS header. For instance, setting
@@ -68,6 +69,17 @@ the .../listing.css style sheet. If you "view source" in your browser on a
68
69
  listing page, you will see the well defined document structure that can be
69
70
  styled.
70
71
 
72
+ Additionally, prefix-based :ref:`tempurl` parameters may be used to authorize
73
+ requests instead of making the whole container publicly readable. This gives
74
+ clients dynamic discoverability of the objects available within that prefix.
75
+
76
+ .. note::
77
+
78
+ ``temp_url_prefix`` values should typically end with a slash (``/``) when
79
+ used with StaticWeb. StaticWeb's redirects will not carry over any TempURL
80
+ parameters, as they likely indicate that the user created an overly-broad
81
+ TempURL.
82
+
71
83
  By default, the listings will be rendered with a label of
72
84
  "Listing of /v1/account/container/path". This can be altered by
73
85
  setting a ``X-Container-Meta-Web-Listings-Label: <label>``. For example,
@@ -137,6 +149,7 @@ from swift.common.wsgi import make_env, WSGIContext
137
149
  from swift.common.http import is_success, is_redirection, HTTP_NOT_FOUND
138
150
  from swift.common.swob import Response, HTTPMovedPermanently, HTTPNotFound, \
139
151
  Request, wsgi_quote, wsgi_to_str, str_to_wsgi
152
+ from swift.common.middleware.tempurl import get_temp_url_info
140
153
  from swift.proxy.controllers.base import get_container_info
141
154
 
142
155
 
@@ -225,7 +238,7 @@ class _StaticWebContext(WSGIContext):
225
238
  self._dir_type = meta.get('web-directory-type', '').strip()
226
239
  return container_info
227
240
 
228
- def _listing(self, env, start_response, prefix=None):
241
+ def _listing(self, env, start_response, prefix=''):
229
242
  """
230
243
  Sends an HTML object listing to the remote client.
231
244
 
@@ -240,8 +253,7 @@ class _StaticWebContext(WSGIContext):
240
253
  '/'.join(groups[4:]))
241
254
 
242
255
  if not config_true_value(self._listings):
243
- body = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 ' \
244
- 'Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n' \
256
+ body = '<!DOCTYPE html>\n' \
245
257
  '<html>\n' \
246
258
  '<head>\n' \
247
259
  '<title>Listing of %s</title>\n' % html_escape(label)
@@ -285,9 +297,28 @@ class _StaticWebContext(WSGIContext):
285
297
  if prefix and not listing:
286
298
  resp = HTTPNotFound()(env, self._start_response)
287
299
  return self._error_response(resp, env, start_response)
288
- headers = {'Content-Type': 'text/html; charset=UTF-8'}
289
- body = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 ' \
290
- 'Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">\n' \
300
+
301
+ tempurl_qs = tempurl_prefix = ''
302
+ if env.get('REMOTE_USER') == '.wsgi.tempurl':
303
+ sig, expires, tempurl_prefix, _filename, inline, ip_range = \
304
+ get_temp_url_info(env)
305
+ if tempurl_prefix is None:
306
+ tempurl_prefix = ''
307
+ else:
308
+ parts = [
309
+ 'temp_url_prefix=%s' % quote(tempurl_prefix),
310
+ 'temp_url_expires=%s' % quote(str(expires)),
311
+ 'temp_url_sig=%s' % sig,
312
+ ]
313
+ if ip_range:
314
+ parts.append('temp_url_ip_range=%s' % quote(ip_range))
315
+ if inline:
316
+ parts.append('inline')
317
+ tempurl_qs = '?' + '&amp;'.join(parts)
318
+
319
+ headers = {'Content-Type': 'text/html; charset=UTF-8',
320
+ 'X-Backend-Content-Generator': 'staticweb'}
321
+ body = '<!DOCTYPE html>\n' \
291
322
  '<html>\n' \
292
323
  ' <head>\n' \
293
324
  ' <title>Listing of %s</title>\n' % \
@@ -311,12 +342,12 @@ class _StaticWebContext(WSGIContext):
311
342
  ' <th class="colsize">Size</th>\n' \
312
343
  ' <th class="coldate">Date</th>\n' \
313
344
  ' </tr>\n' % html_escape(label)
314
- if prefix:
345
+ if len(prefix) > len(tempurl_prefix):
315
346
  body += ' <tr id="parent" class="item">\n' \
316
- ' <td class="colname"><a href="../">../</a></td>\n' \
347
+ ' <td class="colname"><a href="../%s">../</a></td>\n' \
317
348
  ' <td class="colsize">&nbsp;</td>\n' \
318
349
  ' <td class="coldate">&nbsp;</td>\n' \
319
- ' </tr>\n'
350
+ ' </tr>\n' % tempurl_qs
320
351
  for item in listing:
321
352
  if 'subdir' in item:
322
353
  subdir = item['subdir'] if six.PY3 else \
@@ -328,7 +359,7 @@ class _StaticWebContext(WSGIContext):
328
359
  ' <td class="colsize">&nbsp;</td>\n' \
329
360
  ' <td class="coldate">&nbsp;</td>\n' \
330
361
  ' </tr>\n' % \
331
- (quote(subdir), html_escape(subdir))
362
+ (quote(subdir) + tempurl_qs, html_escape(subdir))
332
363
  for item in listing:
333
364
  if 'name' in item:
334
365
  name = item['name'] if six.PY3 else \
@@ -349,7 +380,7 @@ class _StaticWebContext(WSGIContext):
349
380
  ' </tr>\n' % \
350
381
  (' '.join('type-' + html_escape(t.lower())
351
382
  for t in content_type.split('/')),
352
- quote(name), html_escape(name),
383
+ quote(name) + tempurl_qs, html_escape(name),
353
384
  bytes, last_modified)
354
385
  body += ' </table>\n' \
355
386
  ' </body>\n' \
@@ -542,8 +573,8 @@ class StaticWeb(object):
542
573
  return self.app(env, start_response)
543
574
  if env['REQUEST_METHOD'] not in ('HEAD', 'GET'):
544
575
  return self.app(env, start_response)
545
- if env.get('REMOTE_USER') and \
546
- not config_true_value(env.get('HTTP_X_WEB_MODE', 'f')):
576
+ if env.get('REMOTE_USER') and env['REMOTE_USER'] != '.wsgi.tempurl' \
577
+ and not config_true_value(env.get('HTTP_X_WEB_MODE', 'f')):
547
578
  return self.app(env, start_response)
548
579
  if not container:
549
580
  return self.app(env, start_response)
@@ -309,13 +309,15 @@ from six.moves.urllib.parse import urlencode
309
309
 
310
310
  from swift.proxy.controllers.base import get_account_info, get_container_info
311
311
  from swift.common.header_key_dict import HeaderKeyDict
312
+ from swift.common.http import is_success
312
313
  from swift.common.digest import get_allowed_digests, \
313
314
  extract_digest_and_algorithm, DEFAULT_ALLOWED_DIGESTS, get_hmac
314
315
  from swift.common.swob import header_to_environ_key, HTTPUnauthorized, \
315
316
  HTTPBadRequest, wsgi_to_str
316
317
  from swift.common.utils import split_path, get_valid_utf8_str, \
317
- streq_const_time, quote, get_logger
318
+ streq_const_time, quote, get_logger, close_if_possible
318
319
  from swift.common.registry import register_swift_info, register_sensitive_param
320
+ from swift.common.wsgi import WSGIContext
319
321
 
320
322
 
321
323
  DISALLOWED_INCOMING_HEADERS = 'x-object-manifest x-symlink-target'
@@ -364,6 +366,55 @@ def get_tempurl_keys_from_metadata(meta):
364
366
  if key.lower() in ('temp-url-key', 'temp-url-key-2')]
365
367
 
366
368
 
369
+ def normalize_temp_url_expires(value):
370
+ """
371
+ Returns the normalized expiration value as an int
372
+
373
+ If not None, the value is converted to an int if possible or 0
374
+ if not, and checked for expiration (returns 0 if expired).
375
+ """
376
+ if value is None:
377
+ return value
378
+ try:
379
+ temp_url_expires = int(value)
380
+ except ValueError:
381
+ try:
382
+ temp_url_expires = timegm(strptime(
383
+ value, EXPIRES_ISO8601_FORMAT))
384
+ except ValueError:
385
+ temp_url_expires = 0
386
+ if temp_url_expires < time():
387
+ temp_url_expires = 0
388
+ return temp_url_expires
389
+
390
+
391
+ def get_temp_url_info(env):
392
+ """
393
+ Returns the provided temporary URL parameters (sig, expires, prefix,
394
+ temp_url_ip_range), if given and syntactically valid.
395
+ Either sig, expires or prefix could be None if not provided.
396
+
397
+ :param env: The WSGI environment for the request.
398
+ :returns: (sig, expires, prefix, filename, inline,
399
+ temp_url_ip_range) as described above.
400
+ """
401
+ sig = expires = prefix = ip_range = filename = inline = None
402
+ qs = parse_qs(env.get('QUERY_STRING', ''), keep_blank_values=True)
403
+ if 'temp_url_ip_range' in qs:
404
+ ip_range = qs['temp_url_ip_range'][0]
405
+ if 'temp_url_sig' in qs:
406
+ sig = qs['temp_url_sig'][0]
407
+ if 'temp_url_expires' in qs:
408
+ expires = qs['temp_url_expires'][0]
409
+ if 'temp_url_prefix' in qs:
410
+ prefix = qs['temp_url_prefix'][0]
411
+ if 'filename' in qs:
412
+ filename = qs['filename'][0]
413
+ if 'inline' in qs:
414
+ inline = True
415
+ return (sig, expires, prefix, filename, inline, ip_range)
416
+
417
+
367
418
  def disposition_format(disposition_type, filename):
368
419
  # Content-Disposition in HTTP is defined in
369
420
  # https://tools.ietf.org/html/rfc6266 and references
@@ -495,9 +546,10 @@ class TempURL(object):
495
546
  """
496
547
  if env['REQUEST_METHOD'] == 'OPTIONS':
497
548
  return self.app(env, start_response)
498
- info = self._get_temp_url_info(env)
499
- temp_url_sig, temp_url_expires, temp_url_prefix, filename, \
549
+ info = get_temp_url_info(env)
550
+ temp_url_sig, client_temp_url_expires, temp_url_prefix, filename, \
500
551
  inline_disposition, temp_url_ip_range = info
552
+ temp_url_expires = normalize_temp_url_expires(client_temp_url_expires)
501
553
  if temp_url_sig is None and temp_url_expires is None:
502
554
  return self.app(env, start_response)
503
555
  if not temp_url_sig or not temp_url_expires:
@@ -511,7 +563,10 @@ class TempURL(object):
511
563
  if hash_algorithm not in self.allowed_digests:
512
564
  return self._invalid(env, start_response)
513
565
 
514
- account, container, obj = self._get_path_parts(env)
566
+ account, container, obj = self._get_path_parts(
567
+ env, allow_container_root=(
568
+ env['REQUEST_METHOD'] in ('GET', 'HEAD') and
569
+ temp_url_prefix == ""))
515
570
  if not account:
516
571
  return self._invalid(env, start_response)
517
572
 
@@ -577,116 +632,102 @@ class TempURL(object):
577
632
  env['swift.authorize_override'] = True
578
633
  env['REMOTE_USER'] = '.wsgi.tempurl'
579
634
  qs = {'temp_url_sig': temp_url_sig,
580
- 'temp_url_expires': temp_url_expires}
635
+ 'temp_url_expires': client_temp_url_expires}
581
636
  if temp_url_prefix is not None:
582
637
  qs['temp_url_prefix'] = temp_url_prefix
583
638
  if filename:
584
639
  qs['filename'] = filename
585
640
  env['QUERY_STRING'] = urlencode(qs)
586
641
 
587
- def _start_response(status, headers, exc_info=None):
588
- headers = self._clean_outgoing_headers(headers)
589
- if env['REQUEST_METHOD'] in ('GET', 'HEAD') and status[0] == '2':
590
- # figure out the right value for content-disposition
591
- # 1) use the value from the query string
592
- # 2) use the value from the object metadata
593
- # 3) use the object name (default)
594
- out_headers = []
595
- existing_disposition = None
596
- for h, v in headers:
597
- if h.lower() != 'content-disposition':
598
- out_headers.append((h, v))
599
- else:
600
- existing_disposition = v
601
- if inline_disposition:
602
- if filename:
603
- disposition_value = disposition_format('inline',
604
- filename)
605
- else:
606
- disposition_value = 'inline'
607
- elif filename:
608
- disposition_value = disposition_format('attachment',
642
+ ctx = WSGIContext(self.app)
643
+ app_iter = ctx._app_call(env)
644
+ ctx._response_headers = self._clean_outgoing_headers(
645
+ ctx._response_headers)
646
+ if env['REQUEST_METHOD'] in ('GET', 'HEAD') and \
647
+ is_success(ctx._get_status_int()):
648
+ # figure out the right value for content-disposition
649
+ # 1) use the value from the query string
650
+ # 2) use the value from the object metadata
651
+ # 3) use the object name (default)
652
+ out_headers = []
653
+ existing_disposition = None
654
+ content_generator = None
655
+ for h, v in ctx._response_headers:
656
+ if h.lower() == 'x-backend-content-generator':
657
+ content_generator = v
658
+
659
+ if h.lower() != 'content-disposition':
660
+ out_headers.append((h, v))
661
+ else:
662
+ existing_disposition = v
663
+ if content_generator == 'staticweb':
664
+ inline_disposition = True
665
+ elif obj == "":
666
+ # Generally, tempurl requires an object. We carved out an
667
+ # exception to allow GETs at the container root for the sake
668
+ # of staticweb, but we can't tell whether we'll have a
669
+ # staticweb response or not until after we call the app
670
+ close_if_possible(app_iter)
671
+ return self._invalid(env, start_response)
672
+
673
+ if inline_disposition:
674
+ if filename:
675
+ disposition_value = disposition_format('inline',
609
676
  filename)
610
- elif existing_disposition:
611
- disposition_value = existing_disposition
612
677
  else:
613
- name = basename(wsgi_to_str(env['PATH_INFO']).rstrip('/'))
614
- disposition_value = disposition_format('attachment',
615
- name)
616
- # this is probably just paranoia, I couldn't actually get a
617
- # newline into existing_disposition
618
- value = disposition_value.replace('\n', '%0A')
619
- out_headers.append(('Content-Disposition', value))
620
-
621
- # include Expires header for better cache-control
622
- out_headers.append(('Expires', strftime(
623
- "%a, %d %b %Y %H:%M:%S GMT",
624
- gmtime(temp_url_expires))))
625
- headers = out_headers
626
- return start_response(status, headers, exc_info)
627
-
628
- return self.app(env, _start_response)
629
-
630
- def _get_path_parts(self, env):
678
+ disposition_value = 'inline'
679
+ elif filename:
680
+ disposition_value = disposition_format('attachment',
681
+ filename)
682
+ elif existing_disposition:
683
+ disposition_value = existing_disposition
684
+ else:
685
+ name = basename(wsgi_to_str(env['PATH_INFO']).rstrip('/'))
686
+ disposition_value = disposition_format('attachment',
687
+ name)
688
+ # this is probably just paranoia, I couldn't actually get a
689
+ # newline into existing_disposition
690
+ value = disposition_value.replace('\n', '%0A')
691
+ out_headers.append(('Content-Disposition', value))
692
+
693
+ # include Expires header for better cache-control
694
+ out_headers.append(('Expires', strftime(
695
+ "%a, %d %b %Y %H:%M:%S GMT",
696
+ gmtime(temp_url_expires))))
697
+ ctx._response_headers = out_headers
698
+ start_response(
699
+ ctx._response_status,
700
+ ctx._response_headers,
701
+ ctx._response_exc_info)
702
+ return app_iter
703
+
704
+ def _get_path_parts(self, env, allow_container_root=False):
631
705
  """
632
706
  Return the account, container and object name for the request,
633
707
  if it's an object request and one of the configured methods;
634
708
  otherwise, None is returned.
635
709
 
710
+ If it's a container request and allow_root_container is true,
711
+ the object name returned will be the empty string.
712
+
636
713
  :param env: The WSGI environment for the request.
714
+ :param allow_container_root: Whether requests to the root of a
715
+ container should be allowed.
637
716
  :returns: (Account str, container str, object str) or
638
717
  (None, None, None).
639
718
  """
640
719
  if env['REQUEST_METHOD'] in self.conf['methods']:
641
720
  try:
642
- ver, acc, cont, obj = split_path(env['PATH_INFO'], 4, 4, True)
721
+ ver, acc, cont, obj = split_path(
722
+ env['PATH_INFO'], 3 if allow_container_root else 4,
723
+ 4, True)
643
724
  except ValueError:
644
725
  return (None, None, None)
645
- if ver == 'v1' and obj.strip('/'):
646
- return (wsgi_to_str(acc), wsgi_to_str(cont), wsgi_to_str(obj))
726
+ if ver == 'v1' and (allow_container_root or obj.strip('/')):
727
+ return (wsgi_to_str(acc), wsgi_to_str(cont),
728
+ wsgi_to_str(obj) if obj else '')
647
729
  return (None, None, None)
648
730
 
649
- def _get_temp_url_info(self, env):
650
- """
651
- Returns the provided temporary URL parameters (sig, expires, prefix,
652
- temp_url_ip_range), if given and syntactically valid.
653
- Either sig, expires or prefix could be None if not provided.
654
- If provided, expires is also converted to an int if possible or 0
655
- if not, and checked for expiration (returns 0 if expired).
656
-
657
- :param env: The WSGI environment for the request.
658
- :returns: (sig, expires, prefix, filename, inline,
659
- temp_url_ip_range) as described above.
660
- """
661
- temp_url_sig = temp_url_expires = temp_url_prefix = filename =\
662
- inline = None
663
- temp_url_ip_range = None
664
- qs = parse_qs(env.get('QUERY_STRING', ''), keep_blank_values=True)
665
- if 'temp_url_ip_range' in qs:
666
- temp_url_ip_range = qs['temp_url_ip_range'][0]
667
- if 'temp_url_sig' in qs:
668
- temp_url_sig = qs['temp_url_sig'][0]
669
- if 'temp_url_expires' in qs:
670
- try:
671
- temp_url_expires = int(qs['temp_url_expires'][0])
672
- except ValueError:
673
- try:
674
- temp_url_expires = timegm(strptime(
675
- qs['temp_url_expires'][0],
676
- EXPIRES_ISO8601_FORMAT))
677
- except ValueError:
678
- temp_url_expires = 0
679
- if temp_url_expires < time():
680
- temp_url_expires = 0
681
- if 'temp_url_prefix' in qs:
682
- temp_url_prefix = qs['temp_url_prefix'][0]
683
- if 'filename' in qs:
684
- filename = qs['filename'][0]
685
- if 'inline' in qs:
686
- inline = True
687
- return (temp_url_sig, temp_url_expires, temp_url_prefix, filename,
688
- inline, temp_url_ip_range)
689
-
690
731
  def _get_keys(self, env):
691
732
  """
692
733
  Returns the X-[Account|Container]-Meta-Temp-URL-Key[-2] header values
@@ -36,7 +36,8 @@ from swift.common.swob import HTTPBadRequest, \
36
36
  HTTPServiceUnavailable, Range, is_chunked, multi_range_iterator, \
37
37
  HTTPPreconditionFailed, wsgi_to_bytes, wsgi_unquote, wsgi_to_str
38
38
  from swift.common.utils import split_path, validate_device_partition, \
39
- close_if_possible, maybe_multipart_byteranges_to_document_iters, \
39
+ close_if_possible, friendly_close, \
40
+ maybe_multipart_byteranges_to_document_iters, \
40
41
  multipart_byteranges_to_document_iters, parse_content_type, \
41
42
  parse_content_range, csv_append, list_from_csv, Spliterator, quote, \
42
43
  RESERVED, config_true_value, md5, CloseableChain, select_ip_port
@@ -460,15 +461,17 @@ class SegmentedIterable(object):
460
461
  :param app: WSGI application from which segments will come
461
462
 
462
463
  :param listing_iter: iterable yielding the object segments to fetch,
463
- along with the byte subranges to fetch, in the form of a 5-tuple
464
- (object-path, object-etag, object-size, first-byte, last-byte).
464
+ along with the byte sub-ranges to fetch. Each yielded item should be a
465
+ dict with the following keys: ``path`` or ``raw_data``,
466
+ ``first-byte``, ``last-byte``, ``hash`` (optional), ``bytes``
467
+ (optional).
465
468
 
466
- If object-etag is None, no MD5 verification will be done.
469
+ If ``hash`` is None, no MD5 verification will be done.
467
470
 
468
- If object-size is None, no length verification will be done.
471
+ If ``bytes`` is None, no length verification will be done.
469
472
 
470
- If first-byte and last-byte are None, then the entire object will be
471
- fetched.
473
+ If ``first-byte`` and ``last-byte`` are None, then the entire object
474
+ will be fetched.
472
475
 
473
476
  :param max_get_time: maximum permitted duration of a GET request (seconds)
474
477
  :param logger: logger object
@@ -740,7 +743,10 @@ class SegmentedIterable(object):
740
743
  for x in mri:
741
744
  yield x
742
745
  finally:
743
- self.close()
746
+ # Spliterator and multi_range_iterator can't possibly know we've
747
+ # consumed the whole of the app_iter, but we want to read/close the
748
+ # final segment response
749
+ friendly_close(self.app_iter)
744
750
 
745
751
  def validate_first_segment(self):
746
752
  """
@@ -900,6 +906,24 @@ def update_ignore_range_header(req, name):
900
906
  req.headers[hdr] = csv_append(req.headers.get(hdr), name)
901
907
 
902
908
 
909
+ def resolve_ignore_range_header(req, metadata):
910
+ """
911
+ Helper function to remove Range header from request if metadata matching
912
+ the X-Backend-Ignore-Range-If-Metadata-Present header is found.
913
+
914
+ :param req: a swob Request
915
+ :param metadata: dictionary of object metadata
916
+ """
917
+ ignore_range_headers = set(
918
+ h.strip().lower()
919
+ for h in req.headers.get(
920
+ 'X-Backend-Ignore-Range-If-Metadata-Present',
921
+ '').split(','))
922
+ if ignore_range_headers.intersection(
923
+ h.lower() for h in metadata):
924
+ req.headers.pop('Range', None)
925
+
926
+
903
927
  def is_use_replication_network(headers=None):
904
928
  """
905
929
  Determine if replication network should be used.
@@ -160,7 +160,7 @@ class BaseStoragePolicy(object):
160
160
  object_ring=None, aliases='',
161
161
  diskfile_module='egg:swift#replication.fs'):
162
162
  # do not allow BaseStoragePolicy class to be instantiated directly
163
- if type(self) == BaseStoragePolicy:
163
+ if type(self) is BaseStoragePolicy:
164
164
  raise TypeError("Can't instantiate BaseStoragePolicy directly")
165
165
  # policy parameter validation
166
166
  try:
swift/common/swob.py CHANGED
@@ -55,7 +55,7 @@ from six.moves import urllib
55
55
 
56
56
  from swift.common.header_key_dict import HeaderKeyDict
57
57
  from swift.common.utils import UTC, reiterate, split_path, Timestamp, pairs, \
58
- close_if_possible, closing_if_possible, config_true_value, drain_and_close
58
+ close_if_possible, closing_if_possible, config_true_value, friendly_close
59
59
  from swift.common.exceptions import InvalidTimestamp
60
60
 
61
61
 
@@ -1395,12 +1395,15 @@ class Response(object):
1395
1395
  if empty_resp is not None:
1396
1396
  self.status = empty_resp
1397
1397
  self.content_length = 0
1398
+ # the existing successful response and it's app_iter have been
1399
+ # determined to not meet the conditions of the reqeust, the
1400
+ # response app_iter should be closed but not drained.
1398
1401
  close_if_possible(app_iter)
1399
1402
  return [b'']
1400
1403
 
1401
1404
  if self.request and self.request.method == 'HEAD':
1402
1405
  # We explicitly do NOT want to set self.content_length to 0 here
1403
- drain_and_close(app_iter) # be friendly to our app_iter
1406
+ friendly_close(app_iter) # be friendly to our app_iter
1404
1407
  return [b'']
1405
1408
 
1406
1409
  if self.conditional_response and self.request and \