## -*- coding: utf-8 -*-
##
## CDDL HEADER START
##
## The contents of this file are subject to the terms of the
## Common Development and Distribution License (the "License").
## You may not use this file except in compliance with the License.
##
## You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
## See the License for the specific language governing permissions
## and limitations under the License.
##
## When distributing Covered Code, include this CDDL HEADER in each
## file and include the License file at usr/src/OPENSOLARIS.LICENSE.
## If applicable, add the following below this CDDL HEADER, with the
## fields enclosed by brackets "[]" replaced with your own identifying
## information: Portions Copyright [yyyy] [name of copyright owner]
##
## CDDL HEADER END
##
## Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved.
##
<%!
import six
if six.PY2:
import cgi
else:
# cgi.escape is deprecated since python 3.2
import html as cgi
import itertools
import pkg.actions as actions
import pkg.query_parser as qp
import pkg.server.api_errors as api_errors
import pkg.version as version
import re
from six.moves.urllib.parse import urlencode, quote, urlparse, urlunparse
%>\
<%inherit file="layout.shtml"/>\
<%page args="g_vars"/>\
<%
catalog = g_vars["catalog"]
request = g_vars["request"]
http_depot = g_vars["http_depot"]
%>\
<%def name="page_title(g_vars)"><%
return "Package Search"
%></%def>\
<%def name="get_search_criteria(request)"><%
# Based on the request parameters, return a dict representing the
# search criteria.
criteria = {
"token": request.params.get("token", ""),
"query_error": request.params.get("qe", ""),
}
criteria["searched"] = len(criteria["token"])
show = request.params.get("show", "p")
if show == "p" or show not in ("a", "p"):
criteria["return_type"] = qp.Query.RETURN_PACKAGES
elif show == "a":
criteria["return_type"] = qp.Query.RETURN_ACTIONS
for name, default in (("rpp", 50), ("start", 0), ("failed", 0),
("sav", 0), ("cs", 0)):
val = request.params.get(name, default)
try:
val = int(val)
except ValueError:
val = default
# Force boolean type for these parameters.
if name in ("cs", "sav"):
if val:
val = True
else:
val = False
criteria[name] = val
return criteria
%></%def>\
<%def name="search(catalog, request, criteria)"><%
# Gets the search results for the specified catalog based on the
# provided search criteria.
query_error = None
token = criteria["token"]
cs = criteria["cs"]
rpp = criteria["rpp"]
return_type = criteria["return_type"]
start_val = criteria["start"]
# This criteria is optional, so use get to retrieve it.
mver = criteria.get("selected_ver", None)
if mver:
# Replace leading version components with wildcard so that
# matching is only performed using build_release and branch.
mver = "*,{0}-{1}".format(mver.build_release, mver.branch)
# Determines whether all versions or only the latest vresion of a
# package is shown. This is ignored when the return_type is not
sav = criteria["sav"]
try:
# Search results are limited to just one more than the
# results per page so that a query that exceeds it can be
# detected.
results = catalog.search(token,
case_sensitive=cs,
return_type=return_type,
start_point=start_val,
num_to_return=(rpp + 1),
matching_version=mver,
return_latest=not sav)
except qp.QueryLengthExceeded as e:
results = None
query_error = str(e)
except qp.QueryException as e:
results = None
query_error = str(e)
except Exception as e:
results = None
query_error = quote(str(e))
# Before showing the results, the type of results being shown has to be
# determined since the user might have overridden the return_type
# selection above using query syntax. To do that, the first result will
# have to be checked for the real return type.
if results:
try:
if six.PY3:
result = results.__next__()
else:
result = results.next()
except StopIteration:
result = None
results = None
pass
if result and result[1] == qp.Query.RETURN_PACKAGES:
return_type = result[1]
results = itertools.chain([result], results)
elif result and result[1] == qp.Query.RETURN_ACTIONS:
return_type = result[1]
results = itertools.chain([result], results)
elif result:
return_type = qp.Query.RETURN_PACKAGES
query_error = "Only the display of packages or " \
"actions for search results is supported."
request.log("Unsupported return_type '{0}' "
"requested for search query: '{1}'.".format(
return_type, token))
return return_type, results, query_error
%></%def>\
<%def name="display_search_form(criteria, request)"><%
# Returns an HTML form with all of the elements needed to perform a
# search using the specified search criteria and request.
token = criteria["token"]
search_uri = "advanced_search.shtml"
if criteria["searched"]:
search_uri = request.url(qs=request.query_string, relative=True)
search_uri = search_uri.replace("search.shtml",
%>\
<form class="search" action="search.shtml">
<p>
<input id="search-field" type="text" size="40"
maxlength="512" name="token"
value="${token | h}" title="search field"/>
<input id="submit-search" type="submit"
name="action" value="Search"/>
<a href="${search_uri | h}">Advanced Search</a>
</p>
</form>
</%def>\
<%def name="get_prev_page_uri(criteria, request)"><%
# Returns a URL relative to the current request path with the
# starting range of the previous page of search results set.
uri = request.url(qs=request.query_string, relative=True)
scheme, netloc, path, params, query, fragment = urlparse(uri)
nparams = []
for name, val in six.iteritems(request.params):
if name == "start":
continue
nparams.append((name, val))
start = criteria["start"]
start = start - criteria["rpp"]
if start < 0:
start = 0
nparams.append(("start", start))
qs = urlencode(nparams)
uri = urlunparse((scheme, netloc, path, params, qs, fragment))
return uri
%></%def>\
<%def name="get_next_page_uri(criteria, request, result_count)"><%
# Returns a URL relative to the current request path with the
# starting range of the next page of search results set.
uri = request.url(qs=request.query_string, relative=True)
scheme, netloc, path, params, query, fragment = urlparse(uri)
nparams = []
for name, val in six.iteritems(request.params):
if name == "start":
continue
nparams.append((name, val))
start = criteria["start"]
nparams.append(("start", (start + result_count - 1)))
qs = urlencode(nparams)
uri = urlunparse((scheme, netloc, path, params, qs, fragment))
return uri
%></%def>\
<%def name="display_pagination(criteria, result_count, colspan=1)"><%
# Returns a table row with the appropriate pagination controls
# based on the provided search criteria.
show_prev = criteria["start"] > 0
show_next = result_count > criteria["rpp"]
if not (show_prev or show_next):
# Nothing to display.
return ""
%>\
<tr class="last">
% if show_prev:
<td colspan="${colspan}">
<a href="${self.get_prev_page_uri(criteria, request) | h}">
Previous</a>
</td>
% else:
<td colspan="${colspan}"> </td>
% endif
% if show_next:
<td class="last" colspan="${colspan}">
<a href="${self.get_next_page_uri(criteria, request,
result_count) | h}">
Next</a>
</td>
% else:
<td class="last" colspan="${colspan}"> </td>
% endif
</tr>
</%def>\
<div id="yui-main">
% if not catalog.search_available:
<div class="yui-b">
<p>Search functionality is not available at this time.</p>
</div>
% else:
<%
results = None
return_type = None
criteria = self.get_search_criteria(request)
searched = criteria["searched"]
failed = criteria["failed"]
query_error = criteria["query_error"]
if query_error:
# Sanitize query_error to prevent misuse;
lines = cgi.escape(query_error, True).splitlines(True)
n_qe = ""
last_pre = False
# Put all lines which start with a \t in <pre> tags since these
# contain pre-formatted error descriptions.
for l in lines:
if l.startswith("\t"):
if not last_pre:
n_qe += "<pre>"
n_qe += l
last_pre = True
else:
if last_pre:
n_qe += "</pre>"
last_pre = False
n_qe += l.replace("\n","<br/>")
else:
if last_pre:
last_pre = False
n_qe += "</pre>"
query_error = n_qe
if not failed and searched:
return_type, results, query_error = self.search(
catalog, request, criteria)
if query_error or not results:
# Reload the page with a few extra query parameters set
# so that failed searches can be detected in the server
# logs (including that of any proxies in front of the
# depot server).
uri = request.url(qs=request.query_string,
relative=True)
if http_depot:
# if using the http-depot, we need to redirect
# to the appropriate repository within the
# webapp.
lang = request.path_info.split("/")[1]
uri = "/depot/{0}/{1}/{2}".format(http_depot,
lang, uri)
scheme, netloc, path, params, query, \
fragment = urlparse(uri)
nparams = []
for name, val in six.iteritems(request.params):
if name in ("failed", "query_error"):
continue
nparams.append((name, val))
nparams.append(("failed", 1))
if query_error:
nparams.append(("qe", query_error))
qs = urlencode(nparams)
uri = urlunparse((scheme, netloc, path, params,
qs, fragment))
raise api_errors.RedirectException(uri)
rpp = criteria["rpp"]
result_count = 0
%>\
<div class="yui-b">
${self.display_search_form(criteria, request)}
% if not searched:
<p>Search Tips:</p>
<ul class="tips">
<li>All searches are case-insensitive.</li>
<li>To find packages that contain a specific
file, start your search criteria with a '/':<br/>
<kbd>/usr/bin/vim</kbd></li>
<li>To find packages based on a partial match,
use the wildcard characters '*' or '?':<br/>
<kbd>*.xhtm?</kbd></li>
<li>To find packages based on specific
matching characters use '[' and ']':<br/>
<kbd>/usr/bin/[ca]t</kbd></li>
</ul>
% endif
</div>
<div class="yui-b results">
% if searched and return_type == qp.Query.RETURN_PACKAGES:
## Showing packages.
<table summary="A list of packages from the repository catalog
that matched the specified search criteria.">
<tr class="first">
<th>Package</th>
<th>Install</th>
<th colspan="2">Manifest</th>
</tr>
% for v, return_type, vals in results:
<%
pfmri = vals
if result_count % 2:
rclass = ' class="odd"'
else:
rclass = ""
result_count += 1
if result_count > rpp:
break
stem = pfmri.pkg_name
pfmri_str = pfmri.get_fmri(anarchy=True,
include_scheme=False, include_build=False)
pfmri_uri = pfmri.get_fmri(anarchy=True,
include_scheme=False)
phref = self.shared.rpath(g_vars, "info/0/{0}".format(
quote(pfmri_uri, "")))
# XXX the .p5i extension is a bogus hack because
# packagemanager requires it and shouldn't.
p5ihref = self.shared.rpath(g_vars,
"p5i/0/{0}.p5i".format(quote(stem, "")))
mhref = self.shared.rpath(g_vars,
"manifest/0/{0}".format(pfmri.get_url_path()))
%>\
<tr${rclass}>
<td>
<a title="Package Information Summary"
href="${phref}">${pfmri_str}</a>
</td>
<td>
<a class="p5i"
title="Launch the Package Manager and install this package"
href="${p5ihref}">Install</a>
</td>
<td colspan="2">
<a title="Package Manifest"
href="${mhref}">Manifest</a>
</td>
</tr>
% endfor
${display_pagination(criteria, result_count, colspan=2)}
</table>
% elif searched and return_type == qp.Query.RETURN_ACTIONS:
<table summary="A list of actions from the repository that
matched the specified search criteria.">
<tr class="first">
<th>Index</th>
<th>Action</th>
<th>Value</th>
<th>Package</th>
</tr>
% for v, return_type, vals in results:
<%
pfmri, match, action = vals
a = actions.fromstr(action.rstrip())
action = a.name
if isinstance(a, actions.attribute.AttributeAction):
index = a.attrs.get(a.key_attr)
value = match
else:
index = match
value = a.attrs.get(a.key_attr)
%>
<%
if result_count % 2:
rclass = ' class="odd"'
else:
rclass = ""
result_count += 1
if result_count > rpp:
break
pfmri_str = pfmri.get_fmri(anarchy=True,
include_scheme=False)
phref = self.shared.rpath(g_vars, "info/0/{0}".format(
quote(pfmri_str, "")))
%>\
<tr${rclass}>
<td>${index | h}</td>
<td>${action | h}</td>
<td>${value | h}</td>
<td><a href="${phref}">${pfmri_str}</a></td>
</tr>
% endfor
${display_pagination(criteria, result_count, colspan=2)}
</table>
% elif query_error:
<%
token = criteria["token"]
%>
<p>Your search - <b>${token | h}</b> - is not a valid query.</p>
<p>Suggestions:</p>
<ul>
<li>Remove special characters ('(', ')', '<', '>')
from your tokens. Replace them with '*' or '?'.</li>
<li>Ensure that each branch of your boolean query is
returning the same kind of information. 'foo AND < bar >' will not work.</li>
</ul>
<p>Details:</p>
${query_error}
% elif failed:
<%
token = criteria["token"]
%>
<p>Your search - <b>${token | h}</b> - did not match any
packages.</p>
<p>Suggestions:</p>
<ul>
<li>Ensure that all words are spelled correctly.</li>
<li>Try different keywords.</li>
<li>Try more general keywords.</li>
</ul>
% endif
</div>
% endif
</div>