1# Copyright (c) 2012 Red Hat, Inc.
2#
3#    Licensed under the Apache License, Version 2.0 (the "License"); you may
4#    not use this file except in compliance with the License. You may obtain
5#    a copy of the License at
6#
7#         http://www.apache.org/licenses/LICENSE-2.0
8#
9#    Unless required by applicable law or agreed to in writing, software
10#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12#    License for the specific language governing permissions and limitations
13#    under the License.
14
15"""
16Request Body limiting middleware.
17
18"""
19
20import logging
21
22from oslo_config import cfg
23import webob.dec
24import webob.exc
25
26from oslo_middleware._i18n import _
27from oslo_middleware import base
28
29LOG = logging.getLogger(__name__)
30
31
32_oldopts = [cfg.DeprecatedOpt('osapi_max_request_body_size',
33                              group='DEFAULT'),
34            cfg.DeprecatedOpt('max_request_body_size',
35                              group='DEFAULT')]
36
37_opts = [
38    # default request size is 112k
39    cfg.IntOpt('max_request_body_size',
40               default=114688,
41               help='The maximum body size for each '
42                    ' request, in bytes.',
43               deprecated_opts=_oldopts)
44]
45
46
47class LimitingReader(object):
48    """Reader to limit the size of an incoming request."""
49    def __init__(self, data, limit):
50        """Initiates LimitingReader object.
51
52        :param data: Underlying data object
53        :param limit: maximum number of bytes the reader should allow
54        """
55        self.data = data
56        self.limit = limit
57        self.bytes_read = 0
58
59    def __iter__(self):
60        for chunk in self.data:
61            self.bytes_read += len(chunk)
62            if self.bytes_read > self.limit:
63                msg = _("Request is too large. Larger than %s") % self.limit
64                raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg)
65            else:
66                yield chunk
67
68    def read(self, i=None):
69        # NOTE(jamielennox): We can't simply provide the default to the read()
70        # call as the expected default differs between mod_wsgi and eventlet
71        if i is None:
72            result = self.data.read()
73        else:
74            result = self.data.read(i)
75        self.bytes_read += len(result)
76        if self.bytes_read > self.limit:
77            msg = _("Request is too large. Larger than %s.") % self.limit
78            raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg)
79        return result
80
81
82class RequestBodySizeLimiter(base.ConfigurableMiddleware):
83    """Limit the size of incoming requests."""
84
85    def __init__(self, application, conf=None):
86        super(RequestBodySizeLimiter, self).__init__(application, conf)
87        self.oslo_conf.register_opts(_opts, group='oslo_middleware')
88
89    @webob.dec.wsgify
90    def __call__(self, req):
91        max_size = self._conf_get('max_request_body_size')
92        if (req.content_length is not None and
93                req.content_length > max_size):
94            msg = _("Request is too large. "
95                    "Larger than max_request_body_size (%s).") % max_size
96            LOG.info(msg)
97            raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg)
98        if req.content_length is None:
99            limiter = LimitingReader(req.body_file, max_size)
100            req.body_file = limiter
101        return self.application
102