# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt from __future__ import unicode_literals import frappe from frappe import _ import frappe.sessions from frappe.utils import cstr import mimetypes, json from six import iteritems from werkzeug.wrappers import Response from werkzeug.routing import Map, Rule, NotFound from frappe.website.context import get_context from frappe.website.utils import get_home_page, can_cache, delete_page_cache from frappe.website.router import clear_sitemap from frappe.translate import guess_language class PageNotFoundError(Exception): pass def render(path=None, http_status_code=None): """render html page""" path = resolve_path(path or frappe.local.request.path.strip('/ ')) data = None # if in list of already known 404s, send it if can_cache() and frappe.cache().hget('website_404', frappe.request.url): data = render_page('404') http_status_code = 404 else: try: data = render_page_by_language(path) except frappe.DoesNotExistError as e: doctype, name = get_doctype_from_path(path) if doctype and name: path = "printview" frappe.local.form_dict.doctype = doctype frappe.local.form_dict.name = name elif doctype: path = "list" frappe.local.form_dict.doctype = doctype else: # 404s are expensive, cache them! frappe.cache().hset('website_404', frappe.request.url, True) data = render_page('404') http_status_code = 404 if not data: try: data = render_page(path) except frappe.PermissionError as e: data, http_status_code = render_403(e, path) except frappe.PermissionError as e: data, http_status_code = render_403(e, path) except frappe.Redirect as e: return build_response(path, "", 301, { "Location": frappe.flags.redirect_location or (frappe.local.response or {}).get('location'), "Cache-Control": "no-store, no-cache, must-revalidate" }) except Exception: path = "error" data = render_page(path) http_status_code = 500 data = add_csrf_token(data) return build_response(path, data, http_status_code or 200) def build_response(path, data, http_status_code, headers=None): # build response response = Response() response.data = set_content_type(response, data, path) response.status_code = http_status_code response.headers[b"X-Page-Name"] = path.encode("utf-8") response.headers[b"X-From-Cache"] = frappe.local.response.from_cache or False if headers: for key, val in iteritems(headers): response.headers[bytes(key)] = val.encode("utf-8") return response def render_page_by_language(path): translated_languages = frappe.get_hooks("translated_languages_for_website") user_lang = guess_language(translated_languages) if translated_languages and user_lang in translated_languages: try: if path and path != "index": lang_path = '{0}/{1}'.format(user_lang, path) else: lang_path = user_lang # index return render_page(lang_path) except frappe.DoesNotExistError: return render_page(path) else: return render_page(path) def render_page(path): """get page html""" out = None if can_cache(): # return rendered page page_cache = frappe.cache().hget("website_page", path) if page_cache and frappe.local.lang in page_cache: out = page_cache[frappe.local.lang] if out: frappe.local.response.from_cache = True return out return build(path) def build(path): if not frappe.db: frappe.connect() try: return build_page(path) except frappe.DoesNotExistError: hooks = frappe.get_hooks() if hooks.website_catch_all: path = hooks.website_catch_all[0] return build_page(path) else: raise def build_page(path): if not getattr(frappe.local, "path", None): frappe.local.path = path context = get_context(path) if context.source: html = frappe.render_template(context.source, context) elif context.template: html = frappe.get_template(context.template).render(context) # html = frappe.get_template(context.base_template_path).render(context) if can_cache(context.no_cache): page_cache = frappe.cache().hget("website_page", path) or {} page_cache[frappe.local.lang] = html frappe.cache().hset("website_page", path, page_cache) return html def resolve_path(path): if not path: path = "index" if path.endswith('.html'): path = path[:-5] if path == "index": path = get_home_page() frappe.local.path = path if path != "index": path = resolve_from_map(path) return path def resolve_from_map(path): m = Map([Rule(r["from_route"], endpoint=r["to_route"], defaults=r.get("defaults")) for r in get_website_rules()]) urls = m.bind_to_environ(frappe.local.request.environ) try: endpoint, args = urls.match("/" + path) path = endpoint if args: # don't cache when there's a query string! frappe.local.no_cache = 1 frappe.local.form_dict.update(args) except NotFound: pass return path def get_website_rules(): '''Get website route rules from hooks and DocType route''' def _get(): rules = frappe.get_hooks("website_route_rules") for d in frappe.get_all('DocType', 'name, route', dict(has_web_view=1)): if d.route: rules.append(dict(from_route = '/' + d.route.strip('/'), to_route=d.name)) return rules return frappe.cache().get_value('website_route_rules', _get) def set_content_type(response, data, path): if isinstance(data, dict): response.mimetype = 'application/json' response.charset = 'utf-8' data = json.dumps(data) return data response.mimetype = 'text/html' response.charset = 'utf-8' if "." in path: content_type, encoding = mimetypes.guess_type(path) if content_type: response.mimetype = content_type if encoding: response.charset = encoding return data def clear_cache(path=None): '''Clear website caches :param path: (optional) for the given path''' frappe.cache().delete_value("website_generator_routes") delete_page_cache(path) frappe.cache().delete_value("website_404") if not path: clear_sitemap() frappe.clear_cache("Guest") for key in ('portal_menu_items', 'home_page', 'website_route_rules', 'doctypes_with_web_view'): frappe.cache().delete_value(key) for method in frappe.get_hooks("website_clear_cache"): frappe.get_attr(method)(path) def render_403(e, pathname): frappe.local.message = cstr(e.message) frappe.local.message_title = _("Not Permitted") frappe.local.response['context'] = dict( indicator_color = 'red', primary_action = '/login', primary_label = _('Login') ) return render_page("message"), e.http_status_code def get_doctype_from_path(path): doctypes = frappe.db.sql_list("select name from tabDocType") parts = path.split("/") doctype = parts[0] name = parts[1] if len(parts) > 1 else None if doctype in doctypes: return doctype, name # try scrubbed doctype = doctype.replace("_", " ").title() if doctype in doctypes: return doctype, name return None, None def add_csrf_token(data): if frappe.local.session: return data.replace("", ''.format( frappe.local.session.data.csrf_token)) else: return data