1"""
2Based on "TinyMCE Compressor PHP" from MoxieCode.
3
4http://tinymce.moxiecode.com/
5
6Copyright (c) 2008 Jason Davies
7Licensed under the terms of the MIT License (see LICENSE.txt)
8"""
9
10from datetime import datetime
11import json
12import logging
13import os
14import re
15
16from django.contrib.staticfiles import finders
17from django.core.cache import cache
18from django.http import HttpResponse
19from django.template.loader import render_to_string
20from django.utils.cache import patch_response_headers, patch_vary_headers
21from django.utils.http import http_date
22from django.utils.text import compress_string
23
24import tinymce.settings
25
26logger = logging.getLogger(__name__)
27
28safe_filename_re = re.compile("^[a-zA-Z][a-zA-Z0-9_/-]*$")
29
30
31def get_file_contents(filename, source=False):
32
33    file_path = finders.find(os.path.join("tinymce", f"{filename}.js"))
34    if not file_path:
35        file_path = finders.find(os.path.join("tinymce", f"{filename}.min.js"))
36
37    try:
38        with open(file_path) as fh:
39            return fh.read()
40    except (IOError, TypeError):
41        logger.error(f"Couldn't load file: {file_path} for {filename}")
42        return ""
43
44
45def split_commas(str):
46    if str == "":
47        return []
48    return str.split(",")
49
50
51def gzip_compressor(request):
52    plugins = split_commas(request.GET.get("plugins", ""))
53    languages = split_commas(request.GET.get("languages", ""))
54    themes = split_commas(request.GET.get("themes", ""))
55    files = split_commas(request.GET.get("files", ""))
56    source = request.GET.get("src", "") == "true"
57    isJS = request.GET.get("js", "") == "true"
58    compress = request.GET.get("compress", "true") == "true"
59    content = []
60
61    response = HttpResponse()
62    response["Content-Type"] = "text/javascript"
63
64    if not isJS:
65        response.write(
66            render_to_string(
67                "tinymce/tiny_mce_gzip.js", {"base_url": tinymce.settings.JS_BASE_URL}
68            )
69        )
70        return response
71
72    patch_vary_headers(response, ["Accept-Encoding"])
73
74    now = datetime.utcnow()
75    response["Date"] = now.strftime("%a, %d %b %Y %H:%M:%S GMT")
76
77    cacheKey = "|".join(plugins + languages + themes)
78    cacheData = cache.get(cacheKey)
79
80    if cacheData is not None:
81        if "ETag" in cacheData:
82            if_none_match = request.META.get("HTTP_IF_NONE_MATCH")
83            if if_none_match == cacheData["ETag"]:
84                response.status_code = 304
85                response.content = ""
86                response["Content-Length"] = "0"
87                return response
88
89        if "Last-Modified" in cacheData:
90            if_modified_since = request.META.get("HTTP_IF_MODIFIED_SINCE")
91            if if_modified_since == cacheData["Last-Modified"]:
92                response.status_code = 304
93                response.content = ""
94                response["Content-Length"] = "0"
95                return response
96
97    tinyMCEPreInit = {
98        "base": tinymce.settings.JS_BASE_URL,
99        "suffix": "",
100    }
101    content.append(f"var tinyMCEPreInit={json.dumps(tinyMCEPreInit)};")
102
103    # Add core
104    files = ["tinymce"]
105
106    # Add core languages
107    for lang in languages:
108        files.append(f"langs/{lang}")
109
110    # Add plugins
111    for plugin in plugins:
112        files.append(f"plugins/{plugin}/plugin")
113
114        for lang in languages:
115            files.append(f"plugins/{plugin}/langs/{lang}")
116
117    # Add themes
118    for theme in themes:
119        files.append(f"themes/{theme}/theme")
120
121        for lang in languages:
122            files.append(f"themes/{theme}/langs/{lang}")
123
124    for f in files:
125        # Check for unsafe characters
126        if not safe_filename_re.match(f):
127            continue
128        content.append(get_file_contents(f, source=source))
129
130    # Restore loading functions
131    content.append(
132        'tinymce.each("{}".split(",")'.format(",".join(files))
133        + ', function(f){tinymce.ScriptLoader.markDone(tinyMCE.baseURL+"/"+f+".js");});'
134    )
135
136    # Compress
137    if compress:
138        content = compress_string(b"".join([c.encode("utf-8") for c in content]))
139        response["Content-Encoding"] = "gzip"
140        response["Content-Length"] = str(len(content))
141
142    response.write(content)
143    timeout = 3600 * 24 * 10
144    patch_response_headers(response, timeout)
145    if not response.has_header("Last-Modified"):
146        response["Last-Modified"] = http_date()
147    cache.set(
148        cacheKey,
149        {"Last-Modified": response["Last-Modified"], "ETag": response.get("ETag", "")},
150    )
151    return response
152