diff --git a/app/assets/javascripts/discourse/components/utilities.js b/app/assets/javascripts/discourse/components/utilities.js index 8da0b1cf930..80ebbb73610 100644 --- a/app/assets/javascripts/discourse/components/utilities.js +++ b/app/assets/javascripts/discourse/components/utilities.js @@ -158,7 +158,6 @@ Discourse.Utilities = { } }, - /** Validate a list of files to be uploaded @@ -169,18 +168,19 @@ Discourse.Utilities = { if (files) { // can only upload one file at a time if (files.length > 1) { - bootbox.alert(Em.String.i18n('post.errors.upload_too_many_images')); + bootbox.alert(Em.String.i18n('post.errors.too_many_uploads')); return false; } else if (files.length > 0) { - // check that the uploaded file is an image - // TODO: we should provide support for other types of file - if (files[0].type && files[0].type.indexOf('image/') !== 0) { - bootbox.alert(Em.String.i18n('post.errors.only_images_are_supported')); + var upload = files[0]; + // check that the uploaded file is authorized + if (!Discourse.Utilities.isAuthorizedUpload(upload)) { + var extensions = Discourse.SiteSettings.authorized_extensions.replace(/\|/g, ", "); + bootbox.alert(Em.String.i18n('post.errors.upload_not_authorized', { authorized_extensions: extensions })); return false; } // check file size - if (files[0].size && files[0].size > 0) { - var fileSizeInKB = files[0].size / 1024; + if (upload.size && upload.size > 0) { + var fileSizeInKB = upload.size / 1024; if (fileSizeInKB > Discourse.SiteSettings.max_upload_size_kb) { bootbox.alert(Em.String.i18n('post.errors.upload_too_large', { max_size_kb: Discourse.SiteSettings.max_upload_size_kb })); return false; @@ -192,6 +192,19 @@ Discourse.Utilities = { } // there has been an error return false; + }, + + /** + Check the extension of the file against the list of authorized extensions + + @method isAuthorizedUpload + @param {File} files The file we want to upload + **/ + isAuthorizedUpload: function(file) { + var extensions = Discourse.SiteSettings.authorized_extensions; + if (!extensions) return false; + var regexp = new RegExp("\\.(" + extensions.replace(/\./g, "") + ")$", "i"); + return file && file.name ? file.name.match(regexp) : false; } }; diff --git a/app/assets/javascripts/discourse/templates/composer.js.handlebars b/app/assets/javascripts/discourse/templates/composer.js.handlebars index 01556f05e54..5449295de0a 100644 --- a/app/assets/javascripts/discourse/templates/composer.js.handlebars +++ b/app/assets/javascripts/discourse/templates/composer.js.handlebars @@ -75,9 +75,9 @@ {{#if currentUser}} {{{content.toggleText}}}
- {{#if view.loadingImage}} -
- {{i18n image_selector.uploading_image}} {{view.uploadProgress}}% {{i18n cancel}} + {{#if view.isUploading}} +
+ {{i18n image_selector.uploading_image}} {{view.uploadProgress}}% {{i18n cancel}}
{{/if}} {{/if}} diff --git a/app/assets/javascripts/discourse/views/composer_view.js b/app/assets/javascripts/discourse/views/composer_view.js index c531dfa8591..ab2ccec26f3 100644 --- a/app/assets/javascripts/discourse/views/composer_view.js +++ b/app/assets/javascripts/discourse/views/composer_view.js @@ -249,20 +249,20 @@ Discourse.ComposerView = Discourse.View.extend({ $uploadTarget.on('fileuploadsubmit', function (e, data) { var result = Discourse.Utilities.validateFilesForUpload(data.files); // reset upload status when everything is ok - if (result) composerView.setProperties({ uploadProgress: 0, loadingImage: true }); + if (result) composerView.setProperties({ uploadProgress: 0, isUploading: true }); return result; }); // send - this event is triggered when the upload request is about to start $uploadTarget.on('fileuploadsend', function (e, data) { - // hide the "image selector" modal + // hide the "file selector" modal composerView.get('controller').send('closeModal'); // cf. https://github.com/blueimp/jQuery-File-Upload/wiki/API#how-to-cancel-an-upload var jqXHR = data.xhr(); // need to wait for the link to show up in the DOM Em.run.schedule('afterRender', function() { // bind on the click event on the cancel link - $('#cancel-image-upload').on('click', function() { + $('#cancel-file-upload').on('click', function() { // cancel the upload // NOTE: this will trigger a 'fileuploadfail' event with status = 0 if (jqXHR) jqXHR.abort(); @@ -283,13 +283,13 @@ Discourse.ComposerView = Discourse.View.extend({ var upload = data.result; var html = ""; composerView.addMarkdown(html); - composerView.set('loadingImage', false); + composerView.set('isUploading', false); }); // fail $uploadTarget.on('fileuploadfail', function (e, data) { // hide upload status - composerView.set('loadingImage', false); + composerView.set('isUploading', false); // deal with meaningful errors first if (data.jqXHR) { switch (data.jqXHR.status) { @@ -299,9 +299,10 @@ Discourse.ComposerView = Discourse.View.extend({ case 413: bootbox.alert(Em.String.i18n('post.errors.upload_too_large', {max_size_kb: Discourse.SiteSettings.max_upload_size_kb})); return; - // 415 == media type not recognized (ie. not an image) + // 415 == media type not authorized case 415: - bootbox.alert(Em.String.i18n('post.errors.only_images_are_supported')); + var extensions = Discourse.SiteSettings.authorized_extensions.replace(/\|/g, ", "); + bootbox.alert(Em.String.i18n('post.errors.upload_not_authorized', { authorized_extensions: extensions })); return; // 422 == there has been an error on the server (mostly due to FastImage) case 422: diff --git a/app/assets/stylesheets/application/compose.css.scss b/app/assets/stylesheets/application/compose.css.scss index f837593a8ff..27cbad424cc 100644 --- a/app/assets/stylesheets/application/compose.css.scss +++ b/app/assets/stylesheets/application/compose.css.scss @@ -104,7 +104,7 @@ } #reply-control { - .toggle-preview, #draft-status, #image-uploading { + .toggle-preview, #draft-status, #file-uploading { position: absolute; bottom: -31px; margin-top: 0px; @@ -113,7 +113,7 @@ right: 5px; text-decoration: underline; } - #image-uploading { + #file-uploading { left: 51%; font-size: 12px; color: darken($gray, 40); diff --git a/app/models/site_setting.rb b/app/models/site_setting.rb index b814c357e75..ac303ca7b7f 100644 --- a/app/models/site_setting.rb +++ b/app/models/site_setting.rb @@ -51,6 +51,7 @@ class SiteSetting < ActiveRecord::Base setting(:title_prettify, true) client_setting(:max_upload_size_kb, 1024) + client_setting(:authorized_extensions, '.jpg|.jpeg|.png|.gif') # settings only available server side setting(:auto_track_topics_after, 240000) diff --git a/app/models/upload.rb b/app/models/upload.rb index 20edfa37e8d..81731a89c3d 100644 --- a/app/models/upload.rb +++ b/app/models/upload.rb @@ -69,7 +69,7 @@ class Upload < ActiveRecord::Base # make sure we're at the beginning of the file (FastImage is moving the pointer) file.rewind # store the file and update its url - upload.url = Upload.store_file(file, sha1, image_info, upload.id) + upload.url = Upload.store_file(file, sha1, image_info, upload.id) # save the url upload.save end diff --git a/config/locales/client.cs.yml b/config/locales/client.cs.yml index 6ce7c6d50a0..936582da6e0 100644 --- a/config/locales/client.cs.yml +++ b/config/locales/client.cs.yml @@ -773,8 +773,6 @@ cs: edit: "Bohužel nastala chyba při editaci příspěvku. Prosím zkuste to znovu." upload: "Bohužel nastala chyba při nahrávání příspěvku. Prosím zkuste to znovu." upload_too_large: "Soubor, který se snažíte nahrát je bohužel příliš velký (maximální velikost je {{max_size_kb}}kb). Prosím zmenšete ho zkuste to znovu." - upload_too_many_images: "Bohužel, najednou smíte nahrát pouze jeden obrázek." - only_images_are_supported: "Bohužel, smíte nahrávat pouze obrázky." abandon: "Opravdu chcete opustit váš příspěvek?" diff --git a/config/locales/client.de.yml b/config/locales/client.de.yml index d3b88c9fdd2..2ac51c2af48 100644 --- a/config/locales/client.de.yml +++ b/config/locales/client.de.yml @@ -744,8 +744,6 @@ de: edit: "Entschuldige, es gab einen Fehler beim Bearbeiten des Beitrags. Bitte versuche es noch einmal." upload: "Entschuldige, es gab einen Fehler beim Hochladen der Datei. Bitte versuche es noch einmal." upload_too_large: "Entschuldige, die Datei die du hochladen wolltest ist zu groß (Maximalgröße {{max_size_kb}}kb), bitte reduziere die Dateigröße und versuche es nochmal." - upload_too_many_images: "Entschuldige, du kannst nur ein Bild gleichzeitig hochladen." - only_images_are_supported: "Entschuldige, du kannst nur Bilder hochladen." abandon: "Willst Du diesen Beitrag wirklich verwerfen?" diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 6a4898438bc..f2e4326cb84 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -744,8 +744,8 @@ en: edit: "Sorry, there was an error editing your post. Please try again." upload: "Sorry, there was an error uploading that file. Please try again." upload_too_large: "Sorry, the file you are trying to upload is too big (maximum size is {{max_size_kb}}kb), please resize it and try again." - upload_too_many_images: "Sorry, you can only upload one image at a time." - only_images_are_supported: "Sorry, you can only upload images." + too_many_uploads: "Sorry, you can only upload one file at a time." + upload_not_authorized: "Sorry, the file you are trying to upload is not authorized (authorized extension: {{authorized_extensions}})." abandon: "Are you sure you want to abandon your post?" diff --git a/config/locales/client.fr.yml b/config/locales/client.fr.yml index 73a0df775a6..ccc0e2189a8 100644 --- a/config/locales/client.fr.yml +++ b/config/locales/client.fr.yml @@ -727,8 +727,8 @@ fr: edit: "Désolé, il y a eu une erreur lors de l'édition de votre message. Merci de réessayer." upload: "Désolé, il y a eu une erreur lors de l'envoi du fichier. Merci de réessayer." upload_too_large: "Désolé, le fichier que vous êtes en train d'envoyer est trop grand (maximum {{max_size_kb}}Kb). Merci de le redimensionner et de réessayer." - upload_too_many_images: "Désolé, vous ne pouvez envoyer qu'une seule image à la fois." - only_images_are_supported: "Désolé, seulement l'envoi d'image est supporté." + too_many_uploads: "Désolé, vous ne pouvez envoyer qu'un seul fichier à la fois." + upload_not_authorized: "Désole, le fichier que vous êtes en train d'uploader n'est pas autorisé (extensions autorisées : {{authorized_extensions}})." abandon: "Voulez-vous vraiment abandonner ce message ?" diff --git a/config/locales/client.it.yml b/config/locales/client.it.yml index b8b61080ef5..4db228d76d0 100644 --- a/config/locales/client.it.yml +++ b/config/locales/client.it.yml @@ -652,8 +652,7 @@ it: edit: "Spiacenti, si è verificato un errore durante la modifica del tuo post. Per favore, prova di nuovo." upload: "Spiacenti, si è verificato un errore durante il caricamento del file. Per favore, prova di nuovo." upload_too_large: "Spiacenti, il file che stai cercando di caricare è troppo grande (la dimensione massima è {{max_size_kb}}kb), per favore ridimensionalo e prova di nuovo." - upload_too_many_images: "Spiacenti, puoi caricare un'immagine per volta." - only_images_are_supported: "Spiacenti, puoi caricare solo immagini." + too_many_uploads: "Spiacenti, puoi caricare un'immagine per volta." abandon: "Sei sicuro di voler abbandonare il tuo post?" diff --git a/config/locales/client.nb_NO.yml b/config/locales/client.nb_NO.yml index dd02a8c2fed..cb6e8caa784 100644 --- a/config/locales/client.nb_NO.yml +++ b/config/locales/client.nb_NO.yml @@ -672,8 +672,7 @@ nb_NO: edit: "Sorry, there was an error editing your post. Please try again." upload: "Sorry, there was an error uploading that file. Please try again." upload_too_large: "Sorry, the file you are trying to upload is too big (maximum size is {{max_size_kb}}kb), please resize it and try again." - upload_too_many_images: "Sorry, you can only upload one image at a time." - only_images_are_supported: "Sorry, you can only upload images." + too_many_uploads: "Sorry, you can only upload one image at a time." abandon: "Are you sure you want to abandon your post?" diff --git a/config/locales/client.nl.yml b/config/locales/client.nl.yml index 7ff6149bb2c..792281395ef 100644 --- a/config/locales/client.nl.yml +++ b/config/locales/client.nl.yml @@ -748,8 +748,7 @@ nl: edit: "Sorry, er is iets misgegaan bij het bewerken van je bericht. Probeer het nog eens." upload: "Sorry, er is iets misgegaan bij het uploaden van je bestand. Probeer het nog eens." upload_too_large: "Sorry, het bestand dat je wil uploaden is te groot (maximum grootte is {{max_size_kb}}kb), verklein het bestand en probeer het opnieuw." - upload_too_many_images: "Sorry, je kan maar één afbeelding tegelijk uploaden." - only_images_are_supported: "Sorry, je kan alleen afbeeldingen uploaden." + too_many_uploads: "Sorry, je kan maar één afbeelding tegelijk uploaden." abandon: Weet je zeker dat je het schrijven van dit bericht wil afbreken? diff --git a/config/locales/client.pseudo.yml b/config/locales/client.pseudo.yml index 25ecb826c45..4324019f0ab 100644 --- a/config/locales/client.pseudo.yml +++ b/config/locales/client.pseudo.yml @@ -633,9 +633,8 @@ pseudo: ]]' upload_too_large: '[[ Šóřřý, ťĥé ƒíłé ýóů ářé ťřýíɳǧ ťó ůƿłóáď íš ťóó ƀíǧ (ɱáхíɱůɱ šížé íš {{max_size_kb}}ǩƀ), ƿłéášé řéšížé íť áɳď ťřý áǧáíɳ. ]]' - upload_too_many_images: '[[ Šóřřý, ýóů čáɳ óɳłý ůƿłóáď óɳé íɱáǧé áť á ťíɱé. + too_many_uploads: '[[ Šóřřý, ýóů čáɳ óɳłý ůƿłóáď óɳé íɱáǧé áť á ťíɱé. ]]' - only_images_are_supported: '[[ Šóřřý, ýóů čáɳ óɳłý ůƿłóáď íɱáǧéš. ]]' abandon: '[[ Ářé ýóů šůřé ýóů ŵáɳť ťó áƀáɳďóɳ ýóůř ƿóšť? ]]' archetypes: save: '[[ Šáνé Óƿťíóɳš ]]' diff --git a/config/locales/client.ru.yml b/config/locales/client.ru.yml index d125d7171b5..7c0319bb03e 100644 --- a/config/locales/client.ru.yml +++ b/config/locales/client.ru.yml @@ -729,8 +729,7 @@ ru: edit: К сожалению, не удалось изменить сообщение. Попробуйте еще раз. upload: К сожалению, не удалось загрузить файл. Попробуйте еще раз. upload_too_large: 'Превышен допустимый размер ({{max_size_kb}}kb) файла. Уменьшите размер изображения и повторите попытку.' - upload_too_many_images: К сожалению, за один раз можно загрузить только одно изображение. - only_images_are_supported: К сожалению, можно загружать только изображения. + too_many_uploads: К сожалению, за один раз можно загрузить только одно изображение. abandon: Удалить сохраненный черновик? archetypes: save: Параметры сохранения diff --git a/config/locales/client.sv.yml b/config/locales/client.sv.yml index 159bb0bfc7f..2e5cccda01f 100644 --- a/config/locales/client.sv.yml +++ b/config/locales/client.sv.yml @@ -570,8 +570,7 @@ sv: edit: "Tyvärr, det uppstod ett fel under ändringen av ditt inlägg. Var god försök igen." upload: "Tyvärr, det uppstod ett fel under uppladdandet av den filen. Vad god försök igen." upload_too_large: "Tyvärr, filen som du försöker ladda upp är för stor (maxstorlek är {{max_size_kb}}kb), var god ändra storlek och försök igen." - upload_too_many_images: "Tyvärr, du kan bara ladda upp en bild i taget." - only_images_are_supported: "Tyvärr, du kan bara ladda upp bilder." + too_many_uploads: "Tyvärr, du kan bara ladda upp en bild i taget." abandon: "Är du säker på att du vill överge ditt inlägg?" diff --git a/config/locales/client.zh_CN.yml b/config/locales/client.zh_CN.yml index 154d0708b70..d26ce90bdf0 100644 --- a/config/locales/client.zh_CN.yml +++ b/config/locales/client.zh_CN.yml @@ -711,8 +711,7 @@ zh_CN: edit: "抱歉,在编辑你的帖子时发生了错误。请重试。" upload: "抱歉,在上传文件时发生了错误。请重试。" upload_too_large: "抱歉,你上传的文件太大了(最大不能超过 {{max_size_kb}}kb),请调整文件大小后重新上传。" - upload_too_many_images: "抱歉, 你只能一次上传一张图片。" - only_images_are_supported: "抱歉,你只能上传图片。" + too_many_uploads: "抱歉, 你只能一次上传一张图片。" abandon: "你确定要丢弃你的帖子吗?" diff --git a/config/locales/client.zh_TW.yml b/config/locales/client.zh_TW.yml index 0a3534b3344..4fa756907e9 100644 --- a/config/locales/client.zh_TW.yml +++ b/config/locales/client.zh_TW.yml @@ -652,8 +652,7 @@ zh_TW: edit: "抱歉,在編輯你的帖子時發生了錯誤。請重試。" upload: "抱歉,在上傳文件時發生了錯誤。請重試。" upload_too_large: "抱歉,你上傳的文件太大了(最大不能超過 {{max_size_kb}}kb),請調整文件大小後重新上傳。" - upload_too_many_images: "抱歉, 你只能一次上傳一張圖片。" - only_images_are_supported: "抱歉,你只能上傳圖片。" + too_many_uploads: "抱歉, 你只能一次上傳一張圖片。" abandon: "你確定要丟棄你的帖子嗎?" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 954053debd3..1fa3fa20426 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -610,6 +610,7 @@ en: category_colors: "A pipe (|) separated list of hexadecimal color values allowed for categories" max_upload_size_kb: "The maximum size of files we allow users to upload in kB - be sure to configure the limit in nginx (client_max_body_size) / apache or proxy as well." + authorized_extensions: "A pipe (|) separated list of file extensions allowed for upload" max_similar_results: "How many similar topics to show a user while they are composing a new topic" title_prettify: "Prevent common title typos and errors, including all caps, lowercase first character, multiple ! and ?, extra . at end, etc." diff --git a/test/javascripts/components/utilities_test.js b/test/javascripts/components/utilities_test.js index 2f1d8ef1054..35078481eba 100644 --- a/test/javascripts/components/utilities_test.js +++ b/test/javascripts/components/utilities_test.js @@ -20,31 +20,43 @@ test("uploading one file", function() { this.stub(bootbox, "alert"); ok(!validUpload([1, 2])); - ok(bootbox.alert.calledOnce); + ok(bootbox.alert.calledWith(Em.String.i18n('post.errors.too_many_uploads'))); }); -test("ensures an image upload", function() { - var html = { type: "text/html" }; +test("ensures an authorized upload", function() { + var html = { name: "unauthorized.html" }; + var extensions = Discourse.SiteSettings.authorized_extensions.replace(/\|/g, ", "); this.stub(bootbox, "alert"); ok(!validUpload([html])); - ok(bootbox.alert.calledOnce); + ok(bootbox.alert.calledWith(Em.String.i18n('post.errors.upload_not_authorized', { authorized_extensions: extensions }))); }); test("prevents files that are too big from being uploaded", function() { - var image = { type: "image/png", size: 10 * 1024 }; + var image = { name: "image.png", size: 10 * 1024 }; Discourse.SiteSettings.max_upload_size_kb = 5; this.stub(bootbox, "alert"); ok(!validUpload([image])); - ok(bootbox.alert.calledOnce); + ok(bootbox.alert.calledWith(Em.String.i18n('post.errors.upload_too_large', { max_size_kb: 5 }))); }); test("allows valid uploads to go through", function() { - var image = { type: "image/png", size: 10 * 1024 }; + var image = { name: "image.png", size: 10 * 1024 }; Discourse.SiteSettings.max_upload_size_kb = 15; this.stub(bootbox, "alert"); ok(validUpload([image])); ok(!bootbox.alert.calledOnce); }); + +var isAuthorized = function (filename) { + return utils.isAuthorizedUpload({ name: filename }); +}; + +test("isAuthorizedUpload", function() { + ok(isAuthorized("image.png")); + ok(isAuthorized("image.jpg")); + ok(!isAuthorized("image.txt")); + ok(!isAuthorized("")); +}); diff --git a/test/javascripts/fixtures/site_settings_fixtures.js b/test/javascripts/fixtures/site_settings_fixtures.js index e2f7486e171..64df70b37d8 100644 --- a/test/javascripts/fixtures/site_settings_fixtures.js +++ b/test/javascripts/fixtures/site_settings_fixtures.js @@ -1,2 +1,2 @@ /*jshint maxlen:10000000 */ -Discourse.SiteSettings = {"title":"Discourse Meta","logo_url":"/assets/logo.png","logo_small_url":"/assets/logo-single.png","traditional_markdown_linebreaks":false,"top_menu":"latest|new|unread|read|favorited|categories","post_menu":"like|edit|flag|delete|share|bookmark|reply","share_links":"twitter|facebook|google+|email","track_external_right_clicks":false,"must_approve_users":false,"ga_tracking_code":"UA-33736483-2","ga_domain_name":"","enable_long_polling":true,"polling_interval":3000,"anon_polling_interval":30000,"min_post_length":20,"max_post_length":16000,"min_topic_title_length":15,"max_topic_title_length":255,"min_private_message_title_length":2,"allow_uncategorized_topics":true,"min_search_term_length":3,"flush_timings_secs":5,"suppress_reply_directly_below":true,"email_domains_blacklist":"mailinator.com","email_domains_whitelist":null,"version_checks":true,"min_title_similar_length":10,"min_body_similar_length":15,"category_colors":"BF1E2E|F1592A|F7941D|9EB83B|3AB54A|12A89D|25AAE2|0E76BD|652D90|92278F|ED207B|8C6238|231F20|808281|B3B5B4|283890","max_upload_size_kb":1024,"category_featured_topics":6,"favicon_url":"/assets/favicon.ico","dynamic_favicon":false,"uncategorized_name":"uncategorized","uncategorized_color":"AB9364","uncategorized_text_color":"FFFFFF","invite_only":false,"login_required":false,"enable_local_logins":true,"enable_local_account_create":true,"enable_google_logins":true,"enable_yahoo_logins":true,"enable_twitter_logins":true,"enable_facebook_logins":true,"enable_cas_logins":false,"enable_github_logins":true,"enable_persona_logins":true,"educate_until_posts":2,"topic_views_heat_low":1000,"topic_views_heat_medium":2000,"topic_views_heat_high":5000,"min_private_message_post_length":5,"faq_url":"","tos_url":"","privacy_policy_url":""}; +Discourse.SiteSettings = {"title":"Discourse Meta","logo_url":"/assets/logo.png","logo_small_url":"/assets/logo-single.png","traditional_markdown_linebreaks":false,"top_menu":"latest|new|unread|read|favorited|categories","post_menu":"like|edit|flag|delete|share|bookmark|reply","share_links":"twitter|facebook|google+|email","track_external_right_clicks":false,"must_approve_users":false,"ga_tracking_code":"UA-33736483-2","ga_domain_name":"","enable_long_polling":true,"polling_interval":3000,"anon_polling_interval":30000,"min_post_length":20,"max_post_length":16000,"min_topic_title_length":15,"max_topic_title_length":255,"min_private_message_title_length":2,"allow_uncategorized_topics":true,"min_search_term_length":3,"flush_timings_secs":5,"suppress_reply_directly_below":true,"email_domains_blacklist":"mailinator.com","email_domains_whitelist":null,"version_checks":true,"min_title_similar_length":10,"min_body_similar_length":15,"category_colors":"BF1E2E|F1592A|F7941D|9EB83B|3AB54A|12A89D|25AAE2|0E76BD|652D90|92278F|ED207B|8C6238|231F20|808281|B3B5B4|283890","max_upload_size_kb":1024,"category_featured_topics":6,"favicon_url":"/assets/favicon.ico","dynamic_favicon":false,"uncategorized_name":"uncategorized","uncategorized_color":"AB9364","uncategorized_text_color":"FFFFFF","invite_only":false,"login_required":false,"enable_local_logins":true,"enable_local_account_create":true,"enable_google_logins":true,"enable_yahoo_logins":true,"enable_twitter_logins":true,"enable_facebook_logins":true,"enable_cas_logins":false,"enable_github_logins":true,"enable_persona_logins":true,"educate_until_posts":2,"topic_views_heat_low":1000,"topic_views_heat_medium":2000,"topic_views_heat_high":5000,"min_private_message_post_length":5,"faq_url":"","tos_url":"","privacy_policy_url":"","authorized_extensions":".jpg|.jpeg|.png|.gif"};