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.
- swift/account/server.py +1 -11
- swift/cli/info.py +28 -1
- swift-2.32.1.data/scripts/swift-recon-cron → swift/cli/recon_cron.py +4 -13
- swift/cli/reload.py +141 -0
- swift/common/daemon.py +12 -2
- swift/common/db.py +12 -8
- swift/common/http_protocol.py +76 -3
- swift/common/manager.py +18 -5
- swift/common/memcached.py +18 -12
- swift/common/middleware/proxy_logging.py +35 -27
- swift/common/middleware/s3api/acl_handlers.py +1 -1
- swift/common/middleware/s3api/controllers/__init__.py +3 -0
- swift/common/middleware/s3api/controllers/acl.py +3 -2
- swift/common/middleware/s3api/controllers/logging.py +2 -2
- swift/common/middleware/s3api/controllers/multi_upload.py +30 -6
- swift/common/middleware/s3api/controllers/object_lock.py +44 -0
- swift/common/middleware/s3api/s3api.py +4 -0
- swift/common/middleware/s3api/s3request.py +19 -12
- swift/common/middleware/s3api/s3response.py +13 -2
- swift/common/middleware/s3api/utils.py +1 -1
- swift/common/middleware/slo.py +395 -298
- swift/common/middleware/staticweb.py +45 -14
- swift/common/middleware/tempurl.py +132 -91
- swift/common/request_helpers.py +32 -8
- swift/common/storage_policy.py +1 -1
- swift/common/swob.py +5 -2
- swift/common/utils/__init__.py +230 -135
- swift/common/utils/timestamp.py +23 -2
- swift/common/wsgi.py +8 -0
- swift/container/backend.py +126 -21
- swift/container/replicator.py +42 -6
- swift/container/server.py +264 -145
- swift/container/sharder.py +50 -30
- swift/container/updater.py +1 -0
- swift/obj/auditor.py +2 -1
- swift/obj/diskfile.py +55 -19
- swift/obj/expirer.py +1 -13
- swift/obj/mem_diskfile.py +2 -1
- swift/obj/mem_server.py +1 -0
- swift/obj/replicator.py +2 -2
- swift/obj/server.py +12 -23
- swift/obj/updater.py +1 -0
- swift/obj/watchers/dark_data.py +72 -34
- swift/proxy/controllers/account.py +3 -2
- swift/proxy/controllers/base.py +217 -127
- swift/proxy/controllers/container.py +274 -289
- swift/proxy/controllers/obj.py +98 -141
- swift/proxy/server.py +2 -12
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-container-info +3 -0
- swift-2.33.1.data/scripts/swift-recon-cron +24 -0
- {swift-2.32.1.dist-info → swift-2.33.1.dist-info}/AUTHORS +3 -1
- {swift-2.32.1.dist-info → swift-2.33.1.dist-info}/METADATA +4 -3
- {swift-2.32.1.dist-info → swift-2.33.1.dist-info}/RECORD +94 -91
- {swift-2.32.1.dist-info → swift-2.33.1.dist-info}/WHEEL +1 -1
- {swift-2.32.1.dist-info → swift-2.33.1.dist-info}/entry_points.txt +1 -0
- swift-2.33.1.dist-info/pbr.json +1 -0
- swift-2.32.1.dist-info/pbr.json +0 -1
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-account-audit +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-account-auditor +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-account-info +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-account-reaper +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-account-replicator +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-account-server +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-config +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-container-auditor +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-container-reconciler +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-container-replicator +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-container-server +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-container-sharder +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-container-sync +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-container-updater +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-dispersion-populate +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-dispersion-report +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-drive-audit +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-form-signature +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-get-nodes +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-init +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-object-auditor +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-object-expirer +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-object-info +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-object-reconstructor +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-object-relinker +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-object-replicator +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-object-server +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-object-updater +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-oldies +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-orphans +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-proxy-server +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-recon +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-reconciler-enqueue +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-ring-builder +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-ring-builder-analyzer +0 -0
- {swift-2.32.1.data → swift-2.33.1.data}/scripts/swift-ring-composer +0 -0
- {swift-2.32.1.dist-info → swift-2.33.1.dist-info}/LICENSE +0 -0
- {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=
|
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
|
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
|
-
|
289
|
-
|
290
|
-
|
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 = '?' + '&'.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="
|
347
|
+
' <td class="colname"><a href="../%s">../</a></td>\n' \
|
317
348
|
' <td class="colsize"> </td>\n' \
|
318
349
|
' <td class="coldate"> </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"> </td>\n' \
|
329
360
|
' <td class="coldate"> </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 =
|
499
|
-
temp_url_sig,
|
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(
|
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':
|
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
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
if
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
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
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
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(
|
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),
|
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
|
swift/common/request_helpers.py
CHANGED
@@ -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,
|
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
|
464
|
-
|
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
|
469
|
+
If ``hash`` is None, no MD5 verification will be done.
|
467
470
|
|
468
|
-
If
|
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
|
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
|
-
|
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.
|
swift/common/storage_policy.py
CHANGED
@@ -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)
|
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,
|
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
|
-
|
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 \
|