Importing themes

merge-requests/30/head
Bob Mottram 2021-05-29 12:04:03 +01:00
parent a8351756d2
commit e7ae1c0561
20 changed files with 107 additions and 21 deletions

View File

@ -948,7 +948,8 @@ def saveMediaInFormPOST(mediaBytes, debug: bool,
'mp4': 'video/mp4', 'mp4': 'video/mp4',
'ogv': 'video/ogv', 'ogv': 'video/ogv',
'mp3': 'audio/mpeg', 'mp3': 'audio/mpeg',
'ogg': 'audio/ogg' 'ogg': 'audio/ogg',
'zip': 'application/zip'
} }
detectedExtension = None detectedExtension = None
for extension, contentType in extensionList.items(): for extension, contentType in extensionList.items():
@ -960,7 +961,8 @@ def saveMediaInFormPOST(mediaBytes, debug: bool,
extension = 'jpg' extension = 'jpg'
elif extension == 'mpeg': elif extension == 'mpeg':
extension = 'mp3' extension = 'mp3'
filename = filenameBase + '.' + extension if filenameBase:
filename = filenameBase + '.' + extension
attachmentMediaType = \ attachmentMediaType = \
searchStr.decode().split('/')[0].replace('Content-Type: ', '') searchStr.decode().split('/')[0].replace('Content-Type: ', '')
detectedExtension = extension detectedExtension = extension
@ -979,16 +981,17 @@ def saveMediaInFormPOST(mediaBytes, debug: bool,
break break
# remove any existing image files with a different format # remove any existing image files with a different format
extensionTypes = getImageExtensions() if detectedExtension != 'zip':
for ex in extensionTypes: extensionTypes = getImageExtensions()
if ex == detectedExtension: for ex in extensionTypes:
continue if ex == detectedExtension:
possibleOtherFormat = \ continue
filename.replace('.temp', '').replace('.' + possibleOtherFormat = \
detectedExtension, '.' + filename.replace('.temp', '').replace('.' +
ex) detectedExtension, '.' +
if os.path.isfile(possibleOtherFormat): ex)
os.remove(possibleOtherFormat) if os.path.isfile(possibleOtherFormat):
os.remove(possibleOtherFormat)
fd = open(filename, 'wb') fd = open(filename, 'wb')
if not fd: if not fd:

View File

@ -256,6 +256,7 @@ from cache import checkForChangedActor
from cache import storePersonInCache from cache import storePersonInCache
from cache import getPersonFromCache from cache import getPersonFromCache
from httpsig import verifyPostHeaders from httpsig import verifyPostHeaders
from theme import importTheme
from theme import exportTheme from theme import exportTheme
from theme import isNewsThemeName from theme import isNewsThemeName
from theme import getTextModeBanner from theme import getTextModeBanner
@ -4042,7 +4043,8 @@ class PubServer(BaseHTTPRequestHandler):
profileMediaTypes = ('avatar', 'image', profileMediaTypes = ('avatar', 'image',
'banner', 'search_banner', 'banner', 'search_banner',
'instanceLogo', 'instanceLogo',
'left_col_image', 'right_col_image') 'left_col_image', 'right_col_image',
'submitImportTheme')
profileMediaTypesUploaded = {} profileMediaTypesUploaded = {}
for mType in profileMediaTypes: for mType in profileMediaTypes:
# some images can only be changed by the admin # some images can only be changed by the admin
@ -4054,18 +4056,18 @@ class PubServer(BaseHTTPRequestHandler):
if debug: if debug:
print('DEBUG: profile update extracting ' + mType + print('DEBUG: profile update extracting ' + mType +
' image or font from POST') ' image, zip or font from POST')
mediaBytes, postBytes = \ mediaBytes, postBytes = \
extractMediaInFormPOST(postBytes, boundary, mType) extractMediaInFormPOST(postBytes, boundary, mType)
if mediaBytes: if mediaBytes:
if debug: if debug:
print('DEBUG: profile update ' + mType + print('DEBUG: profile update ' + mType +
' image or font was found. ' + ' image, zip or font was found. ' +
str(len(mediaBytes)) + ' bytes') str(len(mediaBytes)) + ' bytes')
else: else:
if debug: if debug:
print('DEBUG: profile update, no ' + mType + print('DEBUG: profile update, no ' + mType +
' image or font was found in POST') ' image, zip or font was found in POST')
continue continue
# Note: a .temp extension is used here so that at no # Note: a .temp extension is used here so that at no
@ -4074,6 +4076,13 @@ class PubServer(BaseHTTPRequestHandler):
if mType == 'instanceLogo': if mType == 'instanceLogo':
filenameBase = \ filenameBase = \
baseDir + '/accounts/login.temp' baseDir + '/accounts/login.temp'
elif mType == 'submitImportTheme':
if not os.path.isdir(baseDir + '/imports'):
os.mkdir(baseDir + '/imports')
filenameBase = \
baseDir + '/imports/newtheme.zip'
if os.path.isfile(filenameBase):
os.remove(filenameBase)
else: else:
filenameBase = \ filenameBase = \
baseDir + '/accounts/' + \ baseDir + '/accounts/' + \
@ -4085,10 +4094,19 @@ class PubServer(BaseHTTPRequestHandler):
filenameBase) filenameBase)
if filename: if filename:
print('Profile update POST ' + mType + print('Profile update POST ' + mType +
' media or font filename is ' + filename) ' media, zip or font filename is ' + filename)
else: else:
print('Profile update, no ' + mType + print('Profile update, no ' + mType +
' media or font filename in POST') ' media, zip or font filename in POST')
continue
if mType == 'submitImportTheme':
if nickname == adminNickname or \
isArtist(baseDir, nickname):
if importTheme(baseDir, filename):
print(nickname + ' uploaded a theme')
else:
print('Only admin or artist can import a theme')
continue continue
postImageFilename = filename.replace('.temp', '') postImageFilename = filename.replace('.temp', '')
@ -4108,7 +4126,8 @@ class PubServer(BaseHTTPRequestHandler):
filename, postImageFilename, city) filename, postImageFilename, city)
if os.path.isfile(postImageFilename): if os.path.isfile(postImageFilename):
print('profile update POST ' + mType + print('profile update POST ' + mType +
' image or font saved to ' + postImageFilename) ' image, zip or font saved to ' +
postImageFilename)
if mType != 'instanceLogo': if mType != 'instanceLogo':
lastPartOfImageFilename = \ lastPartOfImageFilename = \
postImageFilename.split('/')[-1] postImageFilename.split('/')[-1]

View File

@ -10,11 +10,56 @@ import os
from utils import loadJson from utils import loadJson
from utils import saveJson from utils import saveJson
from utils import getImageExtensions from utils import getImageExtensions
from utils import copytree
from shutil import copyfile from shutil import copyfile
from shutil import make_archive from shutil import make_archive
from shutil import unpack_archive
from content import dangerousCSS from content import dangerousCSS
def importTheme(baseDir: str, filename: str) -> bool:
"""Imports a theme
"""
if not os.path.isfile(filename):
return False
tempThemeDir = baseDir + '/imports/files'
if not os.path.isdir(tempThemeDir):
os.mkdir(tempThemeDir)
unpack_archive(filename, tempThemeDir, 'zip')
essentialThemeFiles = ('name.txt', 'theme.json')
for themeFile in essentialThemeFiles:
if not os.path.isfile(tempThemeDir + '/' + themeFile):
print('WARN: ' + themeFile +
' missing from imported theme')
return False
newThemeName = None
with open(tempThemeDir + '/name.txt', 'r') as fp:
newThemeName = fp.read().replace('\n', '').replace('\r', '')
if len(newThemeName) > 20:
print('WARN: Imported theme name is too long')
return False
if len(newThemeName) < 2:
print('WARN: Imported theme name is too short')
return False
newThemeName = newThemeName.lower()
forbiddenChars = (
' ', ';', '/', '\\', '?', '!', '#', '@',
':', '%', '&', '"', '+', '<', '>', '$'
)
for ch in forbiddenChars:
if ch in newThemeName:
print('WARN: theme name contains forbidden character')
return False
if not newThemeName:
return False
themeDir = baseDir + '/theme/' + newThemeName
if not os.path.isdir(themeDir):
os.mkdir(themeDir)
copytree(tempThemeDir, themeDir)
os.remove(tempThemeDir)
return os.path.isfile(themeDir + '/theme.json')
def exportTheme(baseDir: str, theme: str) -> bool: def exportTheme(baseDir: str, theme: str) -> bool:
"""Exports a theme as a zip file """Exports a theme as a zip file
""" """

View File

@ -0,0 +1 @@
blue

View File

@ -0,0 +1 @@
debian

View File

@ -0,0 +1 @@
default

View File

@ -0,0 +1 @@
hacker

View File

@ -0,0 +1 @@
henge

View File

@ -0,0 +1 @@
indymediaclassic

View File

@ -0,0 +1 @@
indymediamodern

View File

@ -0,0 +1 @@
lcd

View File

@ -0,0 +1 @@
light

View File

@ -0,0 +1 @@
night

View File

@ -0,0 +1 @@
pixel

View File

@ -0,0 +1 @@
purple

View File

@ -0,0 +1 @@
rc3

View File

@ -0,0 +1 @@
solidaric

View File

@ -0,0 +1 @@
starlight

View File

@ -0,0 +1 @@
zen

View File

@ -1317,9 +1317,12 @@ def htmlEditProfile(cssCache: {}, translate: {}, baseDir: str, path: str,
themesDropdown += ' <select id="themeDropdown" ' + \ themesDropdown += ' <select id="themeDropdown" ' + \
'name="themeDropdown" class="theme">' 'name="themeDropdown" class="theme">'
for themeName in themes: for themeName in themes:
translatedThemeName = themeName
if translate.get(themeName):
translatedThemeName = translate[themeName]
themesDropdown += ' <option value="' + \ themesDropdown += ' <option value="' + \
themeName.lower() + '">' + \ themeName.lower() + '">' + \
translate[themeName] + '</option>' translatedThemeName + '</option>'
themesDropdown += ' </select><br>' themesDropdown += ' </select><br>'
if os.path.isfile(baseDir + '/fonts/custom.woff') or \ if os.path.isfile(baseDir + '/fonts/custom.woff') or \
os.path.isfile(baseDir + '/fonts/custom.woff2') or \ os.path.isfile(baseDir + '/fonts/custom.woff2') or \
@ -1340,7 +1343,7 @@ def htmlEditProfile(cssCache: {}, translate: {}, baseDir: str, path: str,
' <label class="labels">' + \ ' <label class="labels">' + \
translate['Import Theme'] + '</label>\n' translate['Import Theme'] + '</label>\n'
graphicsStr += ' <input type="file" id="importTheme" ' graphicsStr += ' <input type="file" id="importTheme" '
graphicsStr += 'name="importTheme" ' graphicsStr += 'name="submitImportTheme" '
graphicsStr += 'accept="' + themeFormats + '">\n' graphicsStr += 'accept="' + themeFormats + '">\n'
graphicsStr += \ graphicsStr += \
' <label class="labels">' + \ ' <label class="labels">' + \