Skip to content

Commit bb7375b

Browse files
committed
Merge remote-tracking branch 'origin/master' into edge
2 parents baff99e + 8fcc3c3 commit bb7375b

File tree

8 files changed

+108
-7
lines changed

8 files changed

+108
-7
lines changed

mig/shared/configuration.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ def fix_missing(config_file, verbose=True):
181181
'mig_system_storage': '~/state/mig_system_storage',
182182
'mig_system_run': '~/state/mig_system_run/',
183183
'wwwpublic': '~/state/wwwpublic/',
184+
'wwwserve_max_bytes': -1,
184185
'vm_home': '~/state/vm_home',
185186
'server_cert': '~/certs/cert.pem',
186187
'server_key': '~/certs/key.pem',
@@ -600,6 +601,7 @@ def fix_missing(config_file, verbose=True):
600601
'myfiles_py_location': '',
601602
'public_key_file': '',
602603
'wwwpublic': '',
604+
'wwwserve_max_bytes': -1,
603605
# Virtual machine VNC proxy helpers
604606
'vm_home': '',
605607
'vm_proxy_host': '',

mig/shared/functionality/cat.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
from __future__ import absolute_import
3131

32+
import errno
3233
import glob
3334
import mimetypes
3435
import os
@@ -46,6 +47,36 @@
4647
from mig.shared.validstring import valid_user_path
4748

4849

50+
def _check_serve_permitted(configuration, paths):
51+
"""Given a set of paths ensure that their total size is within limits."""
52+
53+
wwwserve_max_bytes = configuration.wwwserve_max_bytes
54+
55+
try:
56+
serve_paths_stat_results = (os.stat(path) for path in paths)
57+
except OSError as oserr:
58+
# Backwards compatible version of the FileNotFoundError from py3
59+
if oserr.errno == errno.ENOENT:
60+
return False
61+
62+
serve_paths_total_bytes = sum(
63+
st.st_size for st in serve_paths_stat_results)
64+
65+
if wwwserve_max_bytes > -1 and serve_paths_total_bytes > wwwserve_max_bytes:
66+
return False
67+
68+
return True
69+
70+
71+
def _render_error_text_for_serve_limit(configuration):
72+
"""Helper to inform about suitable alternatives when above limit"""
73+
limit = configuration.wwwserve_max_bytes
74+
alternatives = ', '.join(configuration.storage_protocols)
75+
return ("Site configuration prevents web serving contents bigger than "
76+
"%d bytes - please use better alternatives (%s) to retrieve large "
77+
"data.") % (limit, alternatives)
78+
79+
4980
def signature():
5081
"""Signature of the main function"""
5182

@@ -166,6 +197,12 @@ def _main(configuration, logger, op_name='', output_objects=[], client_id=None,
166197
'name': pattern})
167198
status = returnvalues.FILE_NOT_FOUND
168199

200+
if not _check_serve_permitted(configuration, paths=match):
201+
status = returnvalues.REJECTED_ERROR
202+
text = _render_error_text_for_serve_limit(configuration)
203+
output_objects.append({'object_type': 'error_text', 'text': text})
204+
return (output_objects, status)
205+
169206
for abs_path in match:
170207
output_lines = []
171208
relative_path = abs_path.replace(base_dir, '')

mig/shared/returnvalues.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,7 @@
4646
USER_NOT_CREATED = (201, 'USER_NOT_CREATED')
4747
OUTPUT_VALIDATION_ERROR = (202, 'The output the server '
4848
+ 'has generated could not be validated')
49+
50+
# REQUEST ERRORS
51+
52+
REJECTED_ERROR = (422, 'REJECTED')

tests/data/loading.gif

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../mig/images/loading.gif

tests/fixture/mig_shared_configuration--new.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -665,5 +665,6 @@
665665
"workflows_vgrid_patterns_home": "",
666666
"workflows_vgrid_recipes_home": "",
667667
"workflows_vgrid_tasks_home": "",
668-
"wwwpublic": ""
668+
"wwwpublic": "",
669+
"wwwserve_max_bytes": -1
669670
}

tests/support/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242

4343
from tests.support.configsupp import FakeConfiguration
4444
from tests.support.suppconst import MIG_BASE, TEST_BASE, TEST_FIXTURE_DIR, \
45-
TEST_OUTPUT_DIR
45+
TEST_OUTPUT_DIR, TEST_DATA_DIR
4646

4747
PY2 = (sys.version_info[0] == 2)
4848

tests/support/suppconst.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
# Use abspath for __file__ on Py2
3232
_SUPPORT_DIR = os.path.dirname(os.path.abspath(__file__))
3333
TEST_BASE = os.path.normpath(os.path.join(_SUPPORT_DIR, ".."))
34+
TEST_DATA_DIR = os.path.join(TEST_BASE, "data")
3435
TEST_FIXTURE_DIR = os.path.join(TEST_BASE, "fixture")
3536
TEST_OUTPUT_DIR = os.path.join(TEST_BASE, "output")
3637
MIG_BASE = os.path.realpath(os.path.join(TEST_BASE, ".."))

tests/test_mig_shared_functionality_cat.py

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,16 @@
2525
# --- END_HEADER ---
2626
#
2727

28-
"""Unit tests of the MiG functionality file implementing the cat backed"""
28+
"""Unit tests of the MiG functionality file implementing the cat backend"""
2929

3030
from __future__ import print_function
3131
import importlib
3232
import os
3333
import shutil
3434
import sys
3535

36-
from tests.support import MIG_BASE, MigTestCase, testmain, fixturefile, \
37-
fixturefile_normname, ensure_dirs_exist, temppath
36+
from tests.support import MIG_BASE, TEST_DATA_DIR, MigTestCase, testmain, \
37+
fixturefile, fixturefile_normname, ensure_dirs_exist, temppath
3838

3939
from mig.shared.base import client_id_dir
4040
from mig.shared.functionality.cat import _main as main
@@ -85,19 +85,74 @@ def before_each(self):
8585
temppath(self.test_user_dir, self, skip_output_anchor=True)
8686
self.test_environ = create_http_environ(self.configuration)
8787

88-
def test_returns_file_output_with_single_file_match(self):
88+
def test_file_serving_a_single_file_match(self):
8989
with open(os.path.join(self.test_user_dir, 'foobar.txt'), 'w'):
9090
pass
9191
payload = {
9292
'path': ['foobar.txt'],
9393
}
9494

9595
(output_objects, status) = main(self.configuration, self.logger,
96-
client_id=self.TEST_CLIENT_ID, user_arguments_dict=payload, environ=self.test_environ)
96+
client_id=self.TEST_CLIENT_ID,
97+
user_arguments_dict=payload,
98+
environ=self.test_environ)
9799
self.assertEqual(len(output_objects), 1)
98100
output_obj = output_objects[0]
99101
self.assertEqual(output_obj['object_type'], 'file_output')
100102

103+
def test_file_serving_at_limit(self):
104+
test_binary_file = os.path.realpath(
105+
os.path.join(TEST_DATA_DIR, 'loading.gif'))
106+
test_binary_file_size = os.stat(test_binary_file).st_size
107+
with open(test_binary_file, 'rb') as fh_test_file:
108+
test_binary_file_data = fh_test_file.read()
109+
shutil.copyfile(test_binary_file, os.path.join(
110+
self.test_user_dir, 'loading.gif'))
111+
payload = {
112+
'output_format': ['file'],
113+
'path': ['loading.gif'],
114+
}
115+
116+
self.configuration.wwwserve_max_bytes = test_binary_file_size
117+
118+
(output_objects, status) = main(self.configuration, self.logger,
119+
client_id=self.TEST_CLIENT_ID,
120+
user_arguments_dict=payload,
121+
environ=self.test_environ)
122+
# TODO: two file_output objects seem to be returned
123+
self.assertEqual(len(output_objects), 3)
124+
relevant_obj = output_objects[2]
125+
self.assertEqual(relevant_obj['object_type'], 'file_output')
126+
self.assertEqual(len(relevant_obj['lines']), 1)
127+
self.assertEqual(relevant_obj['lines'][0], test_binary_file_data)
128+
129+
def test_file_serving_over_limit(self):
130+
test_binary_file = os.path.realpath(os.path.join(TEST_DATA_DIR,
131+
'loading.gif'))
132+
test_binary_file_size = os.stat(test_binary_file).st_size
133+
with open(test_binary_file, 'rb') as fh_test_file:
134+
test_binary_file_data = fh_test_file.read()
135+
shutil.copyfile(test_binary_file, os.path.join(self.test_user_dir,
136+
'loading.gif'))
137+
payload = {
138+
'output_format': ['file'],
139+
'path': ['loading.gif'],
140+
}
141+
142+
self.configuration.wwwserve_max_bytes = test_binary_file_size - 1
143+
144+
(output_objects, status) = main(self.configuration, self.logger,
145+
client_id=self.TEST_CLIENT_ID,
146+
user_arguments_dict=payload,
147+
environ=self.test_environ)
148+
self.assertEqual(len(output_objects), 4)
149+
relevant_obj = output_objects[3]
150+
self.assertEqual(relevant_obj['object_type'], 'error_text')
151+
self.assertEqual(relevant_obj['text'],
152+
"Site configuration prevents web serving contents "
153+
"bigger than 3896 bytes - please use better "
154+
"alternatives (sftp) to retrieve large data.")
155+
101156

102157
if __name__ == '__main__':
103158
testmain()

0 commit comments

Comments
 (0)