aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian Rosner <brosner@gmail.com>2009-11-10 09:18:55 -0700
committerBrian Rosner <brosner@gmail.com>2009-11-10 09:18:55 -0700
commitdb86053e5b5f767988fe91b991078d54cb59c7d3 (patch)
treea43a37433651ea207fdb54f459388b03227484d1
parentea4d288ddeaf3b06e2a0781d3037660de8f36bb3 (diff)
downloadchishop-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.py38
-rw-r--r--djangopypi/views.py54
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 @@
1import sys
2import traceback
3
4from django.core.files.uploadedfile import SimpleUploadedFile
5from django.utils.datastructures import MultiValueDict
6
7
8def 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
16def 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
31def 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
33import cgi
33import os 34import os
34 35
36try:
37 from cStringIO import StringIO
38except ImportError:
39 from StringIO import StringIO
40
35from django.conf import settings 41from django.conf import settings
36from django.http import Http404, HttpResponse, HttpResponseBadRequest 42from django.http import Http404, HttpResponse, HttpResponseBadRequest
37from django.http import QueryDict, HttpResponseForbidden 43from django.http import QueryDict, HttpResponseForbidden
@@ -45,53 +51,17 @@ from django.core.files.uploadedfile import SimpleUploadedFile
45from django.contrib.auth import authenticate, login 51from django.contrib.auth import authenticate, login
46from djangopypi.http import HttpResponseNotImplemented 52from djangopypi.http import HttpResponseNotImplemented
47from djangopypi.http import HttpResponseUnauthorized 53from djangopypi.http import HttpResponseUnauthorized
54from djangopypi.utils import decode_fs
48 55
49 56
50ALREADY_EXISTS_FMT = _("""A file named "%s" already exists for %s. To fix """ 57ALREADY_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
54def parse_weird_post_data(raw_post_data): 61def 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
97def login_basic_auth(request): 67def login_basic_auth(request):
@@ -181,7 +151,7 @@ ACTIONS = {
181 151
182def simple(request, template_name="djangopypi/simple.html"): 152def 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(