1# -*- coding: utf-8 -*-
2# vi:ts=4:et
3
4import time as _time, sys
5import bottle
6try:
7    import json
8except ImportError:
9    import simplejson as json
10
11py3 = sys.version_info[0] == 3
12
13app = bottle.Bottle()
14app.debug = True
15
16@app.route('/success')
17def ok():
18    return 'success'
19
20@app.route('/short_wait')
21def short_wait():
22    _time.sleep(0.1)
23    return 'success'
24
25@app.route('/status/403')
26def forbidden():
27    return bottle.HTTPResponse('forbidden', 403)
28
29@app.route('/status/404')
30def not_found():
31    return bottle.HTTPResponse('not found', 404)
32
33@app.route('/postfields', method='post')
34def postfields():
35    return json.dumps(dict(bottle.request.forms))
36
37@app.route('/raw_utf8', method='post')
38def raw_utf8():
39    data = bottle.request.body.getvalue().decode('utf8')
40    return json.dumps(data)
41
42# XXX file is not a bottle FileUpload instance, but FieldStorage?
43def xconvert_file(key, file):
44    return {
45        'key': key,
46        'name': file.name,
47        'raw_filename': file.raw_filename,
48        'headers': file.headers,
49        'content_type': file.content_type,
50        'content_length': file.content_length,
51        'data': file.read(),
52    }
53
54if hasattr(bottle, 'FileUpload'):
55    # bottle 0.12
56    def convert_file(key, file):
57        return {
58            'name': file.name,
59            # file.filename lowercases the file name
60            # https://github.com/defnull/bottle/issues/582
61            # raw_filenames is a string on python 3
62            'filename': file.raw_filename,
63            'data': file.file.read().decode(),
64        }
65else:
66    # bottle 0.11
67    def convert_file(key, file):
68        return {
69            'name': file.name,
70            'filename': file.filename,
71            'data': file.file.read().decode(),
72        }
73
74@app.route('/files', method='post')
75def files():
76    files = [convert_file(key, bottle.request.files[key]) for key in bottle.request.files]
77    return json.dumps(files)
78
79@app.route('/header')
80def header():
81    return bottle.request.headers.get(bottle.request.query['h'], '')
82
83# This is a hacky endpoint to test non-ascii text being given to libcurl
84# via headers.
85# HTTP RFC requires headers to be latin1-encoded.
86# Any string can be decoded as latin1; here we encode the header value
87# back into latin1 to obtain original bytestring, then decode it in utf-8.
88# Thanks to bdarnell for the idea: https://github.com/pycurl/pycurl/issues/124
89@app.route('/header_utf8')
90def header_utf8():
91    header_value = bottle.request.headers[bottle.request.query['h']]
92    if py3:
93        # header_value is a string, headers are decoded in latin1
94        header_value = header_value.encode('latin1').decode('utf8')
95    else:
96        # header_value is a binary string, decode in utf-8 directly
97        header_value = header_value.decode('utf8')
98    return header_value
99
100@app.route('/param_utf8_hack', method='post')
101def param_utf8_hack():
102    param = bottle.request.forms['p']
103    if py3:
104        # python 3 decodes bytes as latin1 perhaps?
105        # apply the latin1-utf8 hack
106        param = param.encode('latin').decode('utf8')
107    return param
108
109def pause_writer(interval):
110    yield 'part1'
111    _time.sleep(interval)
112    yield 'part2'
113
114@app.route('/pause')
115def pause():
116    return pause_writer(0.5)
117
118@app.route('/long_pause')
119def long_pause():
120    return pause_writer(1)
121
122@app.route('/utf8_body')
123def utf8_body():
124    # bottle encodes the body
125    return 'Дружба народов'
126
127@app.route('/invalid_utf8_body')
128def invalid_utf8_body():
129    # bottle encodes the body
130    raise bottle.HTTPResponse(b'\xb3\xd2\xda\xcd\xd7', 200)
131
132@app.route('/set_cookie_invalid_utf8')
133def set_cookie_invalid_utf8():
134    bottle.response.set_header('Set-Cookie', '\xb3\xd2\xda\xcd\xd7=%96%A6g%9Ay%B0%A5g%A7tm%7C%95%9A')
135    return 'cookie set'
136
137@app.route('/content_type_invalid_utf8')
138def content_type_invalid_utf8():
139    bottle.response.set_header('Content-Type', '\xb3\xd2\xda\xcd\xd7')
140    return 'content type set'
141
142@app.route('/status_invalid_utf8')
143def status_invalid_utf8():
144    raise bottle.HTTPResponse('status set', '555 \xb3\xd2\xda\xcd\xd7')
145