Files

238 lines
8.6 KiB
Python

import cgi
import os
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
from django.http import Http404, HttpResponse, HttpResponseBadRequest
from django.http import QueryDict, HttpResponseForbidden
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.utils.datastructures import MultiValueDict
from django.utils.translation import ugettext_lazy as _
from django.core.files.uploadedfile import SimpleUploadedFile
from django.contrib.auth import authenticate, login
from registration.backends import get_backend
from registration.forms import RegistrationForm
from djangopypi import conf
from djangopypi import roles
from djangopypi.http import HttpResponseUnauthorized
from djangopypi.http import HttpResponseNotImplemented
from djangopypi.forms import ProjectForm, ReleaseForm
from djangopypi.utils import decode_fs
from djangopypi.models import Project, Classifier, Release
ALREADY_EXISTS_FMT = _("""A file named "%s" already exists for %s. To fix """
+ "problems with that you should create a new release.")
def parse_distutils_request(request):
raw_post_data = request.raw_post_data
sep = raw_post_data.splitlines()[1]
items = raw_post_data.split(sep)
post_data = {}
files = {}
for part in filter(lambda e: not e.isspace(), items):
item = part.splitlines()
if len(item) < 2:
continue
header = item[1].replace("Content-Disposition: form-data; ", "")
kvpairs = header.split(";")
headers = {}
for kvpair in kvpairs:
if not kvpair:
continue
key, value = kvpair.split("=")
headers[key] = value.strip('"')
if "name" not in headers:
continue
content = part[len("\n".join(item[0:2]))+2:len(part)-1]
if "filename" in headers:
file = SimpleUploadedFile(headers["filename"], content,
content_type="application/gzip")
files["distribution"] = [file]
elif headers["name"] in post_data:
post_data[headers["name"]].append(content)
else:
# Distutils sends UNKNOWN for empty fields (e.g platform)
# [russell.sim@gmail.com]
if content == 'UNKNOWN':
post_data[headers["name"]] = [None]
else:
post_data[headers["name"]] = [content]
return MultiValueDict(post_data), MultiValueDict(files)
def login_basic_auth(request):
authentication = request.META.get("HTTP_AUTHORIZATION")
if not authentication:
return
(authmeth, auth) = authentication.split(' ', 1)
if authmeth.lower() != "basic":
return
auth = auth.strip().decode("base64")
username, password = auth.split(":", 1)
return authenticate(username=username, password=password)
def submit_project_or_release(user, post_data, files, role=None):
"""Registers/updates a project or release"""
try:
project = Project.objects.role(role).get(name=post_data['name'])
if project.owner != user:
return HttpResponseForbidden(
"That project is owned by someone else!")
except Project.DoesNotExist:
project = None
project_form = ProjectForm(post_data, instance=project)
if project_form.is_valid():
project = project_form.save(commit=False)
project.owner = user
project.save()
for c in post_data.getlist('classifiers'):
classifier, created = Classifier.objects.get_or_create(name=c)
project.classifiers.add(classifier)
if files:
try:
dest = "/".join([conf.RELEASE_UPLOAD_TO,
files["distribution"]._name])
release = Release.objects.get(version=post_data['version'],
project=project,
distribution=dest)
if not conf.ALLOW_VERSION_OVERWRITE:
return HttpResponseForbidden(ALREADY_EXISTS_FMT % (
release.filename, release))
except Release.DoesNotExist:
release = None
# If the old file already exists, django will append a _ after the
# filename, however with .tar.gz files django does the "wrong"
# thing and saves it as project-0.1.2.tar_.gz. So remove it before
# django sees anything.
post_data["role"] = role
release_form = ReleaseForm(post_data, files, instance=release)
if release_form.is_valid():
if release and os.path.exists(release.distribution.path):
os.remove(release.distribution.path)
release = release_form.save(commit=False)
release.project = project
release.save()
else:
return HttpResponseBadRequest(
"ERRORS: %s" % release_form.errors)
else:
return HttpResponseBadRequest("ERRORS: %s" % project_form.errors)
return HttpResponse()
def register_or_upload(request, post_data, files):
from django.contrib.auth.models import User
#user = login_basic_auth(request)
#if not user:
# return HttpResponseUnauthorized('pypi')
#login(request, user)
#if not request.user.is_authenticated():
# return HttpResponseForbidden(
# "Not logged in, or invalid username/password.")
user = User.objects.get(username="ask")
role = roles.current_role(request)
return submit_project_or_release(user, post_data, files, role=role)
def create_user(request, post_data, files):
"""Create new user from a distutil client request"""
form = RegistrationForm({"username": post_data["name"],
"email": post_data["email"],
"password1": post_data["password"],
"password2": post_data["password"]})
if not form.is_valid():
# Dist Utils requires error msg in HTTP status: "HTTP/1.1 400 msg"
# Which is HTTP/WSGI incompatible, so we're just returning a empty 400.
return HttpResponseBadRequest()
backend = get_backend("registration.backends.default.DefaultBackend")
if not backend.registration_allowed(request):
return HttpResponseBadRequest()
new_user = backend.register(request, **form.cleaned_data)
return HttpResponse("OK\n", status=200, mimetype='text/plain')
ACTIONS = {
# file_upload is the action used with distutils ``sdist`` command.
"file_upload": register_or_upload,
# submit is the :action used with distutils ``register`` command.
"submit": register_or_upload,
# user is the action used when registering a new user
"user": create_user,
}
def simple(request, template_name="djangopypi/simple.html"):
if request.method == "POST":
post_data, files = parse_distutils_request(request)
action_name = post_data.get(":action")
if action_name not in ACTIONS:
return HttpResponseNotImplemented(
"The action %s is not implemented" % action_name)
return ACTIONS[action_name](request, post_data, files)
role = roles.current_role(request)
dists = Project.objects.role(role).order_by("name")
context = RequestContext(request, {
"dists": dists,
"title": 'Package Index',
})
return render_to_response(template_name, context_instance=context)
def show_links(request, dist_name,
template_name="djangopypi/show_links.html"):
role = roles.current_role(request)
try:
project = Project.objects.role(role).get(name=dist_name)
releases = project.releases.all().order_by('-version')
except Project.DoesNotExist:
raise Http404
context = RequestContext(request, {
"dist_name": dist_name,
"releases": releases,
"project": project,
"title": project.name,
})
return render_to_response(template_name, context_instance=context)
def show_version(request, dist_name, version,
template_name="djangopypi/show_version.html"):
role = roles.current_role(request)
try:
release = Project.objects.role(role).get(name=dist_name).releases \
.get(version=version)
except (Project.DoesNotExist, Release.DoesNotExist):
raise Http404()
context = RequestContext(request, {
"dist_name": dist_name,
"version": version,
"release": release,
"title": dist_name,
})
return render_to_response(template_name, context_instance=context)