diff --git a/content.py b/content.py index f73198d1e..8412964ab 100644 --- a/content.py +++ b/content.py @@ -948,7 +948,8 @@ def saveMediaInFormPOST(mediaBytes, debug: bool, 'mp4': 'video/mp4', 'ogv': 'video/ogv', 'mp3': 'audio/mpeg', - 'ogg': 'audio/ogg' + 'ogg': 'audio/ogg', + 'zip': 'application/zip' } detectedExtension = None for extension, contentType in extensionList.items(): @@ -960,7 +961,8 @@ def saveMediaInFormPOST(mediaBytes, debug: bool, extension = 'jpg' elif extension == 'mpeg': extension = 'mp3' - filename = filenameBase + '.' + extension + if filenameBase: + filename = filenameBase + '.' + extension attachmentMediaType = \ searchStr.decode().split('/')[0].replace('Content-Type: ', '') detectedExtension = extension @@ -979,16 +981,17 @@ def saveMediaInFormPOST(mediaBytes, debug: bool, break # remove any existing image files with a different format - extensionTypes = getImageExtensions() - for ex in extensionTypes: - if ex == detectedExtension: - continue - possibleOtherFormat = \ - filename.replace('.temp', '').replace('.' + - detectedExtension, '.' + - ex) - if os.path.isfile(possibleOtherFormat): - os.remove(possibleOtherFormat) + if detectedExtension != 'zip': + extensionTypes = getImageExtensions() + for ex in extensionTypes: + if ex == detectedExtension: + continue + possibleOtherFormat = \ + filename.replace('.temp', '').replace('.' + + detectedExtension, '.' + + ex) + if os.path.isfile(possibleOtherFormat): + os.remove(possibleOtherFormat) fd = open(filename, 'wb') if not fd: diff --git a/daemon.py b/daemon.py index 60fc2a744..fee03986e 100644 --- a/daemon.py +++ b/daemon.py @@ -256,6 +256,7 @@ from cache import checkForChangedActor from cache import storePersonInCache from cache import getPersonFromCache from httpsig import verifyPostHeaders +from theme import importTheme from theme import exportTheme from theme import isNewsThemeName from theme import getTextModeBanner @@ -4042,7 +4043,8 @@ class PubServer(BaseHTTPRequestHandler): profileMediaTypes = ('avatar', 'image', 'banner', 'search_banner', 'instanceLogo', - 'left_col_image', 'right_col_image') + 'left_col_image', 'right_col_image', + 'submitImportTheme') profileMediaTypesUploaded = {} for mType in profileMediaTypes: # some images can only be changed by the admin @@ -4054,18 +4056,18 @@ class PubServer(BaseHTTPRequestHandler): if debug: print('DEBUG: profile update extracting ' + mType + - ' image or font from POST') + ' image, zip or font from POST') mediaBytes, postBytes = \ extractMediaInFormPOST(postBytes, boundary, mType) if mediaBytes: if debug: print('DEBUG: profile update ' + mType + - ' image or font was found. ' + + ' image, zip or font was found. ' + str(len(mediaBytes)) + ' bytes') else: if debug: print('DEBUG: profile update, no ' + mType + - ' image or font was found in POST') + ' image, zip or font was found in POST') continue # Note: a .temp extension is used here so that at no @@ -4074,6 +4076,13 @@ class PubServer(BaseHTTPRequestHandler): if mType == 'instanceLogo': filenameBase = \ 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: filenameBase = \ baseDir + '/accounts/' + \ @@ -4085,10 +4094,19 @@ class PubServer(BaseHTTPRequestHandler): filenameBase) if filename: print('Profile update POST ' + mType + - ' media or font filename is ' + filename) + ' media, zip or font filename is ' + filename) else: 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 postImageFilename = filename.replace('.temp', '') @@ -4108,7 +4126,8 @@ class PubServer(BaseHTTPRequestHandler): filename, postImageFilename, city) if os.path.isfile(postImageFilename): print('profile update POST ' + mType + - ' image or font saved to ' + postImageFilename) + ' image, zip or font saved to ' + + postImageFilename) if mType != 'instanceLogo': lastPartOfImageFilename = \ postImageFilename.split('/')[-1] diff --git a/theme.py b/theme.py index 894ee0cc0..42f7c0642 100644 --- a/theme.py +++ b/theme.py @@ -10,11 +10,56 @@ import os from utils import loadJson from utils import saveJson from utils import getImageExtensions +from utils import copytree from shutil import copyfile from shutil import make_archive +from shutil import unpack_archive 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: """Exports a theme as a zip file """ diff --git a/theme/blue/name.txt b/theme/blue/name.txt new file mode 100644 index 000000000..24560d9b8 --- /dev/null +++ b/theme/blue/name.txt @@ -0,0 +1 @@ +blue diff --git a/theme/debian/name.txt b/theme/debian/name.txt new file mode 100644 index 000000000..2dee1753e --- /dev/null +++ b/theme/debian/name.txt @@ -0,0 +1 @@ +debian diff --git a/theme/default/name.txt b/theme/default/name.txt new file mode 100644 index 000000000..4ad96d515 --- /dev/null +++ b/theme/default/name.txt @@ -0,0 +1 @@ +default diff --git a/theme/hacker/name.txt b/theme/hacker/name.txt new file mode 100644 index 000000000..52b4d7301 --- /dev/null +++ b/theme/hacker/name.txt @@ -0,0 +1 @@ +hacker diff --git a/theme/henge/name.txt b/theme/henge/name.txt new file mode 100644 index 000000000..ae57110cf --- /dev/null +++ b/theme/henge/name.txt @@ -0,0 +1 @@ +henge diff --git a/theme/indymediaclassic/name.txt b/theme/indymediaclassic/name.txt new file mode 100644 index 000000000..eacded40b --- /dev/null +++ b/theme/indymediaclassic/name.txt @@ -0,0 +1 @@ +indymediaclassic diff --git a/theme/indymediamodern/name.txt b/theme/indymediamodern/name.txt new file mode 100644 index 000000000..aaa9e5577 --- /dev/null +++ b/theme/indymediamodern/name.txt @@ -0,0 +1 @@ +indymediamodern diff --git a/theme/lcd/name.txt b/theme/lcd/name.txt new file mode 100644 index 000000000..b57d509d9 --- /dev/null +++ b/theme/lcd/name.txt @@ -0,0 +1 @@ +lcd diff --git a/theme/light/name.txt b/theme/light/name.txt new file mode 100644 index 000000000..162faa69f --- /dev/null +++ b/theme/light/name.txt @@ -0,0 +1 @@ +light diff --git a/theme/night/name.txt b/theme/night/name.txt new file mode 100644 index 000000000..41feb5b6d --- /dev/null +++ b/theme/night/name.txt @@ -0,0 +1 @@ +night diff --git a/theme/pixel/name.txt b/theme/pixel/name.txt new file mode 100644 index 000000000..ae0edb173 --- /dev/null +++ b/theme/pixel/name.txt @@ -0,0 +1 @@ +pixel diff --git a/theme/purple/name.txt b/theme/purple/name.txt new file mode 100644 index 000000000..08ec89e7f --- /dev/null +++ b/theme/purple/name.txt @@ -0,0 +1 @@ +purple diff --git a/theme/rc3/name.txt b/theme/rc3/name.txt new file mode 100644 index 000000000..c215c765c --- /dev/null +++ b/theme/rc3/name.txt @@ -0,0 +1 @@ +rc3 diff --git a/theme/solidaric/name.txt b/theme/solidaric/name.txt new file mode 100644 index 000000000..5ff20cbee --- /dev/null +++ b/theme/solidaric/name.txt @@ -0,0 +1 @@ +solidaric diff --git a/theme/starlight/name.txt b/theme/starlight/name.txt new file mode 100644 index 000000000..8fb456919 --- /dev/null +++ b/theme/starlight/name.txt @@ -0,0 +1 @@ +starlight diff --git a/theme/zen/name.txt b/theme/zen/name.txt new file mode 100644 index 000000000..d88970a7c --- /dev/null +++ b/theme/zen/name.txt @@ -0,0 +1 @@ +zen diff --git a/webapp_profile.py b/webapp_profile.py index 19d8ce03e..ca01cbb9a 100644 --- a/webapp_profile.py +++ b/webapp_profile.py @@ -1317,9 +1317,12 @@ def htmlEditProfile(cssCache: {}, translate: {}, baseDir: str, path: str, themesDropdown += '
' if os.path.isfile(baseDir + '/fonts/custom.woff') or \ os.path.isfile(baseDir + '/fonts/custom.woff2') or \ @@ -1340,7 +1343,7 @@ def htmlEditProfile(cssCache: {}, translate: {}, baseDir: str, path: str, ' \n' graphicsStr += ' \n' graphicsStr += \ '