1import os 2import socket 3from pathlib import Path 4 5import pytest 6 7from unit.applications.proto import ApplicationProto 8from unit.utils import waitforfiles 9 10 11client = ApplicationProto() 12 13 14@pytest.fixture(autouse=True) 15def setup_method_fixture(temp_dir): 16 assets_dir = f'{temp_dir}/assets' 17 18 Path(f'{assets_dir}/dir').mkdir(parents=True) 19 Path(f'{assets_dir}/index.html').write_text('0123456789', encoding='utf-8') 20 Path(f'{assets_dir}/README').write_text('readme', encoding='utf-8') 21 Path(f'{assets_dir}/log.log').write_text('[debug]', encoding='utf-8') 22 Path(f'{assets_dir}/dir/file').write_text('blah', encoding='utf-8') 23 24 assert 'success' in client.conf( 25 { 26 "listeners": {"*:8080": {"pass": "routes"}}, 27 "routes": [{"action": {"share": f'{assets_dir}$uri'}}], 28 "settings": { 29 "http": { 30 "static": {"mime_types": {"text/plain": [".log", "README"]}} 31 } 32 }, 33 } 34 ) 35 36 37def test_static_index(temp_dir): 38 def set_index(index): 39 assert 'success' in client.conf( 40 {"share": f'{temp_dir}/assets$uri', "index": index}, 41 'routes/0/action', 42 ), 'configure index' 43 44 set_index('README') 45 assert client.get()['body'] == 'readme', 'index' 46 47 client.conf_delete('routes/0/action/index') 48 assert client.get()['body'] == '0123456789', 'delete index' 49 50 set_index('') 51 assert client.get()['status'] == 404, 'index empty' 52 53 54def test_static_index_default(): 55 assert client.get(url='/index.html')['body'] == '0123456789', 'index' 56 assert client.get(url='/')['body'] == '0123456789', 'index 2' 57 assert client.get(url='//')['body'] == '0123456789', 'index 3' 58 assert client.get(url='/.')['body'] == '0123456789', 'index 4' 59 assert client.get(url='/./')['body'] == '0123456789', 'index 5' 60 assert client.get(url='/?blah')['body'] == '0123456789', 'index vars' 61 assert client.get(url='/#blah')['body'] == '0123456789', 'index anchor' 62 assert client.get(url='/dir/')['status'] == 404, 'index not found' 63 64 resp = client.get(url='/index.html/') 65 assert resp['status'] == 404, 'index not found 2 status' 66 assert ( 67 resp['headers']['Content-Type'] == 'text/html' 68 ), 'index not found 2 Content-Type' 69 70 71def test_static_index_invalid(skip_alert, temp_dir): 72 skip_alert(r'failed to apply new conf') 73 74 def check_index(index): 75 assert 'error' in client.conf( 76 {"share": f'{temp_dir}/assets$uri', "index": index}, 77 'routes/0/action', 78 ) 79 80 check_index({}) 81 check_index(['index.html', '$blah']) 82 83 84def test_static_large_file(temp_dir): 85 file_size = 32 * 1024 * 1024 86 with open(f'{temp_dir}/assets/large', 'wb') as f: 87 f.seek(file_size - 1) 88 f.write(b'\0') 89 90 assert ( 91 len(client.get(url='/large', read_buffer_size=1024 * 1024)['body']) 92 == file_size 93 ), 'large file' 94 95 96def test_static_etag(temp_dir): 97 etag = client.get(url='/')['headers']['ETag'] 98 etag_2 = client.get(url='/README')['headers']['ETag'] 99 100 assert etag != etag_2, 'different ETag' 101 assert etag == client.get(url='/')['headers']['ETag'], 'same ETag' 102 103 with open(f'{temp_dir}/assets/index.html', 'w', encoding='utf-8') as f: 104 f.write('blah') 105 106 assert etag != client.get(url='/')['headers']['ETag'], 'new ETag' 107 108 109def test_static_redirect(): 110 resp = client.get(url='/dir') 111 assert resp['status'] == 301, 'redirect status' 112 assert resp['headers']['Location'] == '/dir/', 'redirect Location' 113 assert 'Content-Type' not in resp['headers'], 'redirect Content-Type' 114 115 116def test_static_space_in_name(temp_dir): 117 assets_dir = f'{temp_dir}/assets' 118 119 Path(f'{assets_dir}/dir/file').rename(f'{assets_dir}/dir/fi le') 120 121 assert waitforfiles(f'{assets_dir}/dir/fi le') 122 assert client.get(url='/dir/fi le')['body'] == 'blah', 'file name' 123 124 Path(f'{assets_dir}/dir').rename(f'{assets_dir}/di r') 125 assert waitforfiles(f'{assets_dir}/di r/fi le') 126 assert client.get(url='/di r/fi le')['body'] == 'blah', 'dir name' 127 128 Path(f'{assets_dir}/di r').rename(f'{assets_dir}/ di r ') 129 assert waitforfiles(f'{assets_dir}/ di r /fi le') 130 assert ( 131 client.get(url='/ di r /fi le')['body'] == 'blah' 132 ), 'dir name enclosing' 133 134 assert ( 135 client.get(url='/%20di%20r%20/fi le')['body'] == 'blah' 136 ), 'dir encoded' 137 assert client.get(url='/ di r %2Ffi le')['body'] == 'blah', 'slash encoded' 138 assert client.get(url='/ di r /fi%20le')['body'] == 'blah', 'file encoded' 139 assert ( 140 client.get(url='/%20di%20r%20%2Ffi%20le')['body'] == 'blah' 141 ), 'encoded' 142 assert ( 143 client.get(url='/%20%64%69%20%72%20%2F%66%69%20%6C%65')['body'] 144 == 'blah' 145 ), 'encoded 2' 146 147 Path(f'{assets_dir}/ di r /fi le').rename(f'{assets_dir}/ di r / fi le ') 148 assert waitforfiles(f'{assets_dir}/ di r / fi le ') 149 assert ( 150 client.get(url='/%20di%20r%20/%20fi%20le%20')['body'] == 'blah' 151 ), 'file name enclosing' 152 153 try: 154 Path(f'{temp_dir}/ф а').touch() 155 utf8 = True 156 157 except KeyboardInterrupt: 158 raise 159 160 except: 161 utf8 = False 162 163 if utf8: 164 Path(f'{assets_dir}/ di r / fi le ').rename( 165 f'{assets_dir}/ di r /фа йл' 166 ) 167 assert waitforfiles(f'{assets_dir}/ di r /фа йл') 168 assert client.get(url='/ di r /фа йл')['body'] == 'blah' 169 170 Path(f'{assets_dir}/ di r ').rename(f'{assets_dir}/ди ректория') 171 assert waitforfiles(f'{assets_dir}/ди ректория/фа йл') 172 assert ( 173 client.get(url='/ди ректория/фа йл')['body'] == 'blah' 174 ), 'dir name 2' 175 176 177def test_static_unix_socket(temp_dir): 178 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 179 sock.bind(f'{temp_dir}/assets/unix_socket') 180 181 assert client.get(url='/unix_socket')['status'] == 404, 'socket' 182 183 sock.close() 184 185 186def test_static_unix_fifo(temp_dir): 187 os.mkfifo(f'{temp_dir}/assets/fifo') 188 189 assert client.get(url='/fifo')['status'] == 404, 'fifo' 190 191 192def test_static_method(): 193 resp = client.head() 194 assert resp['status'] == 200, 'HEAD status' 195 assert resp['body'] == '', 'HEAD empty body' 196 197 assert client.delete()['status'] == 405, 'DELETE' 198 assert client.post()['status'] == 405, 'POST' 199 assert client.put()['status'] == 405, 'PUT' 200 201 202def test_static_path(): 203 assert client.get(url='/dir/../dir/file')['status'] == 200, 'relative' 204 205 assert client.get(url='./')['status'] == 400, 'path invalid' 206 assert client.get(url='../')['status'] == 400, 'path invalid 2' 207 assert client.get(url='/..')['status'] == 400, 'path invalid 3' 208 assert client.get(url='../assets/')['status'] == 400, 'path invalid 4' 209 assert client.get(url='/../assets/')['status'] == 400, 'path invalid 5' 210 211 212def test_static_two_clients(): 213 sock = client.get(no_recv=True) 214 sock2 = client.get(no_recv=True) 215 216 assert sock.recv(1) == b'H', 'client 1' 217 assert sock2.recv(1) == b'H', 'client 2' 218 assert sock.recv(1) == b'T', 'client 1 again' 219 assert sock2.recv(1) == b'T', 'client 2 again' 220 221 sock.close() 222 sock2.close() 223 224 225def test_static_mime_types(): 226 assert 'success' in client.conf( 227 { 228 "text/x-code/x-blah/x-blah": "readme", 229 "text/plain": [".html", ".log", "file"], 230 }, 231 'settings/http/static/mime_types', 232 ), 'configure mime_types' 233 234 assert ( 235 client.get(url='/README')['headers']['Content-Type'] 236 == 'text/x-code/x-blah/x-blah' 237 ), 'mime_types string case insensitive' 238 assert ( 239 client.get(url='/index.html')['headers']['Content-Type'] == 'text/plain' 240 ), 'mime_types html' 241 assert ( 242 client.get(url='/')['headers']['Content-Type'] == 'text/plain' 243 ), 'mime_types index default' 244 assert ( 245 client.get(url='/dir/file')['headers']['Content-Type'] == 'text/plain' 246 ), 'mime_types file in dir' 247 248 249def test_static_mime_types_partial_match(): 250 assert 'success' in client.conf( 251 { 252 "text/x-blah": ["ile", "fil", "f", "e", ".file"], 253 }, 254 'settings/http/static/mime_types', 255 ), 'configure mime_types' 256 assert 'Content-Type' not in client.get(url='/dir/file'), 'partial match' 257 258 259def test_static_mime_types_reconfigure(): 260 assert 'success' in client.conf( 261 { 262 "text/x-code": "readme", 263 "text/plain": [".html", ".log", "file"], 264 }, 265 'settings/http/static/mime_types', 266 ), 'configure mime_types' 267 268 assert client.conf_get('settings/http/static/mime_types') == { 269 'text/x-code': 'readme', 270 'text/plain': ['.html', '.log', 'file'], 271 }, 'mime_types get' 272 assert ( 273 client.conf_get('settings/http/static/mime_types/text%2Fx-code') 274 == 'readme' 275 ), 'mime_types get string' 276 assert client.conf_get('settings/http/static/mime_types/text%2Fplain') == [ 277 '.html', 278 '.log', 279 'file', 280 ], 'mime_types get array' 281 assert ( 282 client.conf_get('settings/http/static/mime_types/text%2Fplain/1') 283 == '.log' 284 ), 'mime_types get array element' 285 286 assert 'success' in client.conf_delete( 287 'settings/http/static/mime_types/text%2Fplain/2' 288 ), 'mime_types remove array element' 289 assert ( 290 'Content-Type' not in client.get(url='/dir/file')['headers'] 291 ), 'mime_types removed' 292 293 assert 'success' in client.conf_post( 294 '"file"', 'settings/http/static/mime_types/text%2Fplain' 295 ), 'mime_types add array element' 296 assert ( 297 client.get(url='/dir/file')['headers']['Content-Type'] == 'text/plain' 298 ), 'mime_types reverted' 299 300 assert 'success' in client.conf( 301 '"file"', 'settings/http/static/mime_types/text%2Fplain' 302 ), 'configure mime_types update' 303 assert ( 304 client.get(url='/dir/file')['headers']['Content-Type'] == 'text/plain' 305 ), 'mime_types updated' 306 assert ( 307 'Content-Type' not in client.get(url='/log.log')['headers'] 308 ), 'mime_types updated 2' 309 310 assert 'success' in client.conf( 311 '".log"', 'settings/http/static/mime_types/text%2Fblahblahblah' 312 ), 'configure mime_types create' 313 assert ( 314 client.get(url='/log.log')['headers']['Content-Type'] 315 == 'text/blahblahblah' 316 ), 'mime_types create' 317 318 319def test_static_mime_types_correct(): 320 assert 'error' in client.conf( 321 {"text/x-code": "readme", "text/plain": "readme"}, 322 'settings/http/static/mime_types', 323 ), 'mime_types same extensions' 324 assert 'error' in client.conf( 325 {"text/x-code": [".h", ".c"], "text/plain": ".c"}, 326 'settings/http/static/mime_types', 327 ), 'mime_types same extensions array' 328 assert 'error' in client.conf( 329 { 330 "text/x-code": [".h", ".c", "readme"], 331 "text/plain": "README", 332 }, 333 'settings/http/static/mime_types', 334 ), 'mime_types same extensions case insensitive' 335 336 337@pytest.mark.skip('not yet') 338def test_static_mime_types_invalid(temp_dir): 339 assert 'error' in client.http( 340 b"""PUT /config/settings/http/static/mime_types/%0%00% HTTP/1.1\r 341Host: localhost\r 342Connection: close\r 343Content-Length: 6\r 344\r 345\"blah\"""", 346 raw_resp=True, 347 raw=True, 348 sock_type='unix', 349 addr=f'{temp_dir}/control.unit.sock', 350 ), 'mime_types invalid' 351