1 from django
.shortcuts
import render
, get_object_or_404
2 from django
.http
import HttpResponse
, Http404
, HttpResponseRedirect
3 from pgweb
.util
.decorators
import login_required
4 from django
.views
.decorators
.csrf
import csrf_exempt
5 from django
.conf
import settings
8 import cPickle
as pickle
11 from pgweb
.util
.decorators
import nocache
12 from pgweb
.util
.contexts
import render_pgweb
13 from pgweb
.util
.helpers
import simple_form
, PgXmlHelper
, HttpServerError
14 from pgweb
.util
.misc
import varnish_purge
, version_sort
16 from pgweb
.core
.models
import Version
17 from .models
import Category
, Product
, StackBuilderApp
18 from .forms
import ProductForm
24 def ftpbrowser(request
, subpath
):
26 # An actual path has been selected. Fancy!
28 if subpath
.find('..') > -1:
29 # Just claim it doesn't exist if the user tries to do this
32 subpath
= subpath
.strip('/')
36 # Pickle up the list of things we need
38 f
= open(settings
.FTP_PICKLE
, "rb")
39 allnodes
= pickle
.load(f
)
41 except Exception as e
:
42 return HttpServerError(request
, "Failed to load ftp site information: %s" % e
)
44 # An incoming subpath may either be canonical, or have one or more elements
45 # present that are actually symlinks. For each element of the path, test to
46 # see if it is present in the pickle. If not, look for a symlink entry with
47 # and if present, replace the original entry with the symlink target.
51 for d
in subpath
.split('/'):
52 # Check if allnodes contains a node matching the path
53 if d
in allnodes
[parent
]:
54 if allnodes
[parent
][d
]['t'] == 'd':
55 canonpath
= os
.path
.join(canonpath
, d
)
56 elif allnodes
[parent
][d
]['t'] == 'l':
57 canonpath
= os
.path
.join(canonpath
, allnodes
[parent
][d
]['d']).strip('/')
59 # There's a matching node, but it's not a link or a directory
64 # There's no matching node
67 # If we wound up with a canonical path that doesn't match the original request,
69 canonpath
= canonpath
.strip('/')
70 if subpath
!= canonpath
:
71 return HttpResponseRedirect('/ftp/' + canonpath
)
73 node
= allnodes
[subpath
]
77 directories
= [{'link': k
, 'url': k
, 'type': 'd'} for k
, v
in node
.items() if v
['t'] == 'd']
78 # Add all symlinks (only directories supported)
79 directories
.extend([{'link': k
, 'url': v
['d'], 'type': 'l'} for k
, v
in node
.items() if v
['t'] == 'l'])
81 # A ittle early sorting wouldn't go amiss, so .. ends up at the top
82 directories
.sort(key
=version_sort
, reverse
=True)
84 # Add a link to the parent directory
86 directories
.insert(0, {'link': '[Parent Directory]', 'url': '..'})
89 files
= [{'name': k
, 'mtime': v
['d'], 'size': v
['s']} for k
, v
in node
.items() if v
['t'] == 'f']
94 for pathpiece
in subpath
.split('/'):
96 # Trailing slash will give out an empty pathpiece
99 breadroot
= "%s/%s" % (breadroot
, pathpiece
)
101 breadroot
= pathpiece
102 breadcrumbs
.append({'name': pathpiece
, 'path': breadroot
})
104 # Check if there are any "content files" we should render directly on the webpage
105 file_readme
= ('README' in node
and node
['README']['t'] == 'f') and node
['README']['c'] or None
106 file_message
= ('.message' in node
and node
['.message']['t'] == 'f') and node
['.message']['c'] or None
107 file_maintainer
= ('CURRENT_MAINTAINER' in node
and node
['CURRENT_MAINTAINER']['t'] == 'f') and node
['CURRENT_MAINTAINER']['c'] or None
111 return render_pgweb(request
, 'download', 'downloads/ftpbrowser.html', {
112 'basepath': subpath
.rstrip('/'),
113 'directories': directories
,
114 'files': sorted(files
),
115 'breadcrumbs': breadcrumbs
,
116 'readme': file_readme
,
117 'messagefile': file_message
,
118 'maintainer': file_maintainer
,
122 # Accept an upload of the ftpsite pickle. This is fairly resource consuming,
123 # and not very optimized, but that's why we limit it so that only the ftp
124 # server(s) can post it.
125 # There is no concurrency check - the ftp site better not send more than one
128 def uploadftp(request
):
129 if request
.method
!= 'PUT':
130 return HttpServerError(request
, "Invalid method")
131 if not request
.META
['REMOTE_ADDR'] in settings
.FTP_MASTERS
:
132 return HttpServerError(request
, "Invalid client address")
133 # We have the data in request.body. Attempt to load it as
134 # a pickle to make sure it's properly formatted
135 pickle
.loads(request
.body
)
137 # Next, check if it's the same as the current file
138 f
= open(settings
.FTP_PICKLE
, "rb")
141 if x
== request
.body
:
142 # Don't rewrite the file or purge any data if nothing changed
143 return HttpResponse("NOT CHANGED", content_type
="text/plain")
145 # File has changed - let's write it!
146 f
= open("%s.new" % settings
.FTP_PICKLE
, "wb")
147 f
.write(request
.body
)
149 os
.rename("%s.new" % settings
.FTP_PICKLE
, settings
.FTP_PICKLE
)
151 # Purge it out of varnish so we start responding right away
152 varnish_purge("/ftp")
154 # Finally, indicate to the client that we're happy
155 return HttpResponse("OK", content_type
="text/plain")
159 def uploadyum(request
):
160 if request
.method
!= 'PUT':
161 return HttpServerError(request
, "Invalid method")
162 if not request
.META
['REMOTE_ADDR'] in settings
.FTP_MASTERS
:
163 return HttpServerError(request
, "Invalid client address")
164 # We have the data in request.body. Attempt to load it as
165 # json to ensure correct format.
166 json
.loads(request
.body
)
168 # Next, check if it's the same as the current file
169 if os
.path
.isfile(settings
.YUM_JSON
):
170 with
open(settings
.YUM_JSON
, "r") as f
:
171 if f
.read() == request
.body
:
172 # Don't rewrite the file or purge any data if nothing changed
173 return HttpResponse("NOT CHANGED", content_type
="text/plain")
175 # File has changed - let's write it!
176 with
open("%s.new" % settings
.YUM_JSON
, "w") as f
:
177 f
.write(request
.body
)
179 os
.rename("%s.new" % settings
.YUM_JSON
, settings
.YUM_JSON
)
181 # Purge it out of varnish so we start responding right away
182 varnish_purge("/download/js/yum.js")
184 # Finally, indicate to the client that we're happy
185 return HttpResponse("OK", content_type
="text/plain")
189 def mirrorselect(request
, path
):
190 # Old access to mirrors will just redirect to the main ftp site.
191 # We don't really need it anymore, but the cost of keeping it is
193 return HttpResponseRedirect("https://ftp.postgresql.org/pub/%s" % path
)
196 # Render javascript for yum downloads
198 with
open(settings
.YUM_JSON
) as f
:
200 return render(request
, 'downloads/js/yum.js', {
202 'supported_versions': ','.join([str(v
.numtree
) for v
in Version
.objects
.filter(supported
=True)]),
203 }, content_type
='application/json')
209 def categorylist(request
):
210 categories
= Category
.objects
.all()
211 return render_pgweb(request
, 'download', 'downloads/categorylist.html', {
212 'categories': categories
,
216 def productlist(request
, catid
, junk
=None):
217 category
= get_object_or_404(Category
, pk
=catid
)
218 products
= Product
.objects
.select_related('org', 'licencetype').filter(category
=category
, approved
=True)
219 return render_pgweb(request
, 'download', 'downloads/productlist.html', {
220 'category': category
,
221 'products': products
,
222 'productcount': len(products
),
227 def productform(request
, itemid
):
228 return simple_form(Product
, itemid
, request
, ProductForm
,
229 redirect
='/account/edit/products/')
235 def applications_v2_xml(request
):
236 all_apps
= StackBuilderApp
.objects
.select_related().filter(active
=True)
238 resp
= HttpResponse(content_type
='text/xml')
239 x
= PgXmlHelper(resp
, skipempty
=True)
241 x
.startElement('applications', {})
243 x
.startElement('application', {})
244 x
.add_xml_element('id', a
.textid
)
245 x
.add_xml_element('platform', a
.platform
)
246 x
.add_xml_element('secondaryplatform', a
.secondaryplatform
)
247 x
.add_xml_element('version', a
.version
)
248 x
.add_xml_element('name', a
.name
)
249 x
.add_xml_element('description', a
.description
)
250 x
.add_xml_element('category', a
.category
)
251 x
.add_xml_element('pgversion', a
.pgversion
)
252 x
.add_xml_element('edbversion', a
.edbversion
)
253 x
.add_xml_element('format', a
.format
)
254 x
.add_xml_element('installoptions', a
.installoptions
)
255 x
.add_xml_element('upgradeoptions', a
.upgradeoptions
)
256 x
.add_xml_element('checksum', a
.checksum
)
257 x
.add_xml_element('mirrorpath', a
.mirrorpath
)
258 x
.add_xml_element('alturl', a
.alturl
)
259 x
.add_xml_element('versionkey', a
.versionkey
)
260 x
.add_xml_element('manifesturl', a
.manifesturl
)
261 for dep
in a
.txtdependencies
.split(','):
262 x
.add_xml_element('dependency', dep
)
263 x
.endElement('application')
264 x
.endElement('applications')