diff options
author | Brian Rosner <brosner@gmail.com> | 2009-11-10 09:18:55 -0700 |
---|---|---|
committer | Brian Rosner <brosner@gmail.com> | 2009-11-10 09:18:55 -0700 |
commit | db86053e5b5f767988fe91b991078d54cb59c7d3 (patch) | |
tree | a43a37433651ea207fdb54f459388b03227484d1 | |
parent | ea4d288ddeaf3b06e2a0781d3037660de8f36bb3 (diff) | |
download | chishop-db86053e5b5f767988fe91b991078d54cb59c7d3.tar.bz2 chishop-db86053e5b5f767988fe91b991078d54cb59c7d3.tar.xz chishop-db86053e5b5f767988fe91b991078d54cb59c7d3.zip |
improved distutils POST data handling based on how PyPI actually does it
-rw-r--r-- | djangopypi/utils.py | 38 | ||||
-rw-r--r-- | djangopypi/views.py | 54 |
2 files changed, 50 insertions, 42 deletions
diff --git a/djangopypi/utils.py b/djangopypi/utils.py new file mode 100644 index 0000000..ba2013e --- /dev/null +++ b/djangopypi/utils.py | |||
@@ -0,0 +1,38 @@ | |||
1 | import sys | ||
2 | import traceback | ||
3 | |||
4 | from django.core.files.uploadedfile import SimpleUploadedFile | ||
5 | from django.utils.datastructures import MultiValueDict | ||
6 | |||
7 | |||
8 | def transmute(f): | ||
9 | if hasattr(f, "filename") and f.filename: | ||
10 | v = SimpleUploadedFile(f.filename, f.value, f.type) | ||
11 | else: | ||
12 | v = f.value.decode("utf-8") | ||
13 | return v | ||
14 | |||
15 | |||
16 | def decode_fs(fs): | ||
17 | POST, FILES = {}, {} | ||
18 | for k in fs.keys(): | ||
19 | v = transmute(fs[k]) | ||
20 | if isinstance(v, SimpleUploadedFile): | ||
21 | FILES[k] = [v] | ||
22 | else: | ||
23 | # Distutils sends UNKNOWN for empty fields (e.g platform) | ||
24 | # [russell.sim@gmail.com] | ||
25 | if v == "UNKNOWN": | ||
26 | v = None | ||
27 | POST[k] = [v] | ||
28 | return MultiValueDict(POST), MultiValueDict(FILES) | ||
29 | |||
30 | |||
31 | def debug(func): | ||
32 | # @debug is handy when debugging distutils requests | ||
33 | def _wrapped(*args, **kwargs): | ||
34 | try: | ||
35 | return func(*args, **kwargs) | ||
36 | except: | ||
37 | traceback.print_exception(*sys.exc_info()) | ||
38 | return _wrapped \ No newline at end of file | ||
diff --git a/djangopypi/views.py b/djangopypi/views.py index 90929a1..784651b 100644 --- a/djangopypi/views.py +++ b/djangopypi/views.py | |||
@@ -30,8 +30,14 @@ POSSIBILITY OF SUCH DAMAGE. | |||
30 | 30 | ||
31 | """ | 31 | """ |
32 | 32 | ||
33 | import cgi | ||
33 | import os | 34 | import os |
34 | 35 | ||
36 | try: | ||
37 | from cStringIO import StringIO | ||
38 | except ImportError: | ||
39 | from StringIO import StringIO | ||
40 | |||
35 | from django.conf import settings | 41 | from django.conf import settings |
36 | from django.http import Http404, HttpResponse, HttpResponseBadRequest | 42 | from django.http import Http404, HttpResponse, HttpResponseBadRequest |
37 | from django.http import QueryDict, HttpResponseForbidden | 43 | from django.http import QueryDict, HttpResponseForbidden |
@@ -45,53 +51,17 @@ from django.core.files.uploadedfile import SimpleUploadedFile | |||
45 | from django.contrib.auth import authenticate, login | 51 | from django.contrib.auth import authenticate, login |
46 | from djangopypi.http import HttpResponseNotImplemented | 52 | from djangopypi.http import HttpResponseNotImplemented |
47 | from djangopypi.http import HttpResponseUnauthorized | 53 | from djangopypi.http import HttpResponseUnauthorized |
54 | from djangopypi.utils import decode_fs | ||
48 | 55 | ||
49 | 56 | ||
50 | ALREADY_EXISTS_FMT = _("""A file named "%s" already exists for %s. To fix """ | 57 | ALREADY_EXISTS_FMT = _("""A file named "%s" already exists for %s. To fix """ |
51 | + "problems with that you should create a new release.") | 58 | + "problems with that you should create a new release.") |
52 | 59 | ||
53 | 60 | ||
54 | def parse_weird_post_data(raw_post_data): | 61 | def parse_distutils_request(request): |
55 | """ For some reason Django can't parse the HTTP POST data | 62 | fp = StringIO(request.raw_post_data) |
56 | sent by ``distutils`` register/upload commands. | 63 | fs = cgi.FieldStorage(fp=fp, environ=request.META) |
57 | 64 | return decode_fs(fs) | |
58 | This parser should be able to so, and returns a | ||
59 | :class:`django.utils.datastructures.MultiValueDict` | ||
60 | as you would expect from a regular ``request.POST`` object. | ||
61 | """ | ||
62 | # If anyone knows any better way to do this, you're welcome | ||
63 | # to give me a solid beating. [askh@opera.com] | ||
64 | sep = raw_post_data.splitlines()[1] | ||
65 | items = raw_post_data.split(sep) | ||
66 | post_data = {} | ||
67 | files = {} | ||
68 | for part in [e for e in items if not e.isspace()]: | ||
69 | item = part.splitlines() | ||
70 | if len(item) < 2: continue | ||
71 | header = item[1].replace("Content-Disposition: form-data; ", "") | ||
72 | kvpairs = header.split(";") | ||
73 | headers = {} | ||
74 | for kvpair in kvpairs: | ||
75 | if not kvpair: continue | ||
76 | key, value = kvpair.split("=") | ||
77 | headers[key] = value.strip('"') | ||
78 | if not "name" in headers: continue | ||
79 | content = part[len("\n".join(item[0:2]))+2:len(part)-1] | ||
80 | if "filename" in headers: | ||
81 | file = SimpleUploadedFile(headers["filename"], content, | ||
82 | content_type="application/gzip") | ||
83 | files["distribution"] = [file] | ||
84 | elif headers["name"] in post_data: | ||
85 | post_data[headers["name"]].append(content) | ||
86 | else: | ||
87 | # Distutils sends UNKNOWN for empty fields (e.g platform) | ||
88 | # [russell.sim@gmail.com] | ||
89 | if content == 'UNKNOWN': | ||
90 | post_data[headers["name"]] = [None] | ||
91 | else: | ||
92 | post_data[headers["name"]] = [content] | ||
93 | |||
94 | return MultiValueDict(post_data), MultiValueDict(files) | ||
95 | 65 | ||
96 | 66 | ||
97 | def login_basic_auth(request): | 67 | def login_basic_auth(request): |
@@ -181,7 +151,7 @@ ACTIONS = { | |||
181 | 151 | ||
182 | def simple(request, template_name="djangopypi/simple.html"): | 152 | def simple(request, template_name="djangopypi/simple.html"): |
183 | if request.method == "POST": | 153 | if request.method == "POST": |
184 | post_data, files = parse_weird_post_data(request.raw_post_data) | 154 | post_data, files = parse_distutils_request(request.raw_post_data) |
185 | action_name = post_data.get(":action") | 155 | action_name = post_data.get(":action") |
186 | if action_name not in ACTIONS: | 156 | if action_name not in ACTIONS: |
187 | return HttpResponseNotImplemented( | 157 | return HttpResponseNotImplemented( |