From 67565809f3989e3b12b6f3177857096c7f0945b3 Mon Sep 17 00:00:00 2001 From: Tobias Reich Date: Tue, 11 Aug 2015 09:19:18 +0200 Subject: [PATCH 01/18] Updated basicModal --- dist/main.css | Bin 33047 -> 33110 bytes src/package.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/main.css b/dist/main.css index f1aada543c87eee67095ca80b4a3438731a77a1b..3fd2271b29431a4c0b35e6d08dfca0b42b2fc665 100755 GIT binary patch delta 49 zcmbQ<#B{BRX@djLWF27vA#+vh+{6mqjMU8Zj1ntzgR+dtkCT9x^Fqx}ve#lc=0sx((3LyXh diff --git a/src/package.json b/src/package.json index 231cd19..6c4635d 100644 --- a/src/package.json +++ b/src/package.json @@ -11,7 +11,7 @@ }, "devDependencies": { "basiccontext": "^3.3.0", - "basicmodal": "^3.1.1", + "basicmodal": "^3.1.2", "gulp": "^3.9.0", "gulp-autoprefixer": "2.3.1", "gulp-babel": "^5.2.0", From d3a87fbfbbb8595aef880004e8042851d1e37ee7 Mon Sep 17 00:00:00 2001 From: Tobias Reich Date: Tue, 11 Aug 2015 13:43:55 +0200 Subject: [PATCH 02/18] Fixed error when trying to download album with ampersand in password #356 --- dist/main.js | Bin 181296 -> 181334 bytes src/scripts/album.js | 2 +- src/scripts/photo.js | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/main.js b/dist/main.js index 9c066d8f1310ed62976311cf3ae587a1e5273c6c..724a40aef4c8192cffe909494888b1649d27f0ce 100644 GIT binary patch delta 692 zcma)3&ubGw6lOMUjY*@RMU4o?#iqMS+_vIxx{1}gPHAY%qQIdla9AThI_Ulqp%=v_IS zgco&Er14y*yc10ISOg*BK0CgKFu!-=Cv@=Ysl^sA&dkqCP8#!*>I&+>nQ&M#)tqF) z1U(!+q(kL*e8>avp>umYK@!8NXq%Nvfr`=Ih(%{Z1G+x2Iz8@9ETC&psR#yW)M_dc4o31)6n2rlEF;QOwsbp@zCo zV-`Ti#uo)p{QYu|(58jMMABS&GW6HwBa@>jpe2s{f0DirviU&fxv!~o zDve!V{^^!%_eK78Gcce+%vG4OeLB!s&))0rd|2t6w98Dy-<_N~~N|o4(1c7rX^5E;ijw)@|$mPd2eqhlPeVj%|g;2%GGf-5RnXyGd)P zckyIgv9}&Pt4OCbmm)oRksx@mhzLp%@hJ9a(U(RF3d79%`QE(md++^y|4TTR{fM~3 zez6l%MZm7*pF-Gu?LXOrsjO2+%Nc?wN`!TFf9^#p`xd&10FbC2X1DlB4r-T=C*ekY z6cLv16ZgPVKOI3xbyv@Cj)3jZa@g0g^Z|bg_~)Gm4@R_B{OrWV4aXD_>h_HjQ1M z@nT1|yJkFSJ33^DxdL5w&jecIIe6!T51t>DcU%a|cBx=G0?&)GZIvCNCdxCGS!@NM z5zw6bPGG*xH(TQ>m93JKFI$z8(zZ>jD1qfX9sT0{n@8TBA^PoG$$2dX;lR`Hjbd=UPKy$ z18Ak(Q_^gE)++0ARV!3z!}}CHBnz=k0) link = location.href.replace(location.hash, '').replace('index.html', url) else link = location.href.replace(location.hash, '') + url - if (lychee.publicMode===true) link += `&password=${ password.value }` + if (lychee.publicMode===true) link += `&password=${ encodeURIComponent(password.value) }` location.href = link diff --git a/src/scripts/photo.js b/src/scripts/photo.js index 4ebdc26..b1cc1f0 100644 --- a/src/scripts/photo.js +++ b/src/scripts/photo.js @@ -684,7 +684,7 @@ photo.getArchive = function(photoID) { if (location.href.indexOf('index.html')>0) link = location.href.replace(location.hash, '').replace('index.html', url) else link = location.href.replace(location.hash, '') + url - if (lychee.publicMode===true) link += '&password=' + password.value + if (lychee.publicMode===true) link += `&password=${ encodeURIComponent(password.value) }` location.href = link From 69530c66b360aa1a5ae12e61355ee803caf03f8b Mon Sep 17 00:00:00 2001 From: Tobias Reich Date: Fri, 21 Aug 2015 21:57:35 +0200 Subject: [PATCH 03/18] Reordered buttons --- index.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.html b/index.html index a5088c2..4b591ac 100644 --- a/index.html +++ b/index.html @@ -86,12 +86,12 @@ - - - + + + From 35586fd5f750d8885b5cdfeab4b0292685797481 Mon Sep 17 00:00:00 2001 From: Tobias Reich Date: Fri, 21 Aug 2015 21:57:41 +0200 Subject: [PATCH 04/18] Updated deps --- src/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/package.json b/src/package.json index 6c4635d..ae7cf61 100644 --- a/src/package.json +++ b/src/package.json @@ -14,7 +14,7 @@ "basicmodal": "^3.1.2", "gulp": "^3.9.0", "gulp-autoprefixer": "2.3.1", - "gulp-babel": "^5.2.0", + "gulp-babel": "^5.2.1", "gulp-concat": "^2.6.0", "gulp-inject": "^1.5.0", "gulp-load-plugins": "^1.0.0-rc", From 47c3139b02e136dee7d4d26ebcd5167645b65d27 Mon Sep 17 00:00:00 2001 From: Tobias Reich Date: Fri, 21 Aug 2015 21:58:31 +0200 Subject: [PATCH 05/18] Sahre photo now shares view.php link #392 --- src/scripts/contextMenu.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/scripts/contextMenu.js b/src/scripts/contextMenu.js index f78d156..eca5995 100644 --- a/src/scripts/contextMenu.js +++ b/src/scripts/contextMenu.js @@ -281,8 +281,6 @@ contextMenu.sharePhoto = function(photoID, e) { let link = photo.getViewLink(photoID), iconClass = 'ionicons' - if (photo.json.public==='2') link = location.href - let items = [ { type: 'item', title: ``, fn: () => {}, class: 'noHover' }, { type: 'separator' }, From 233a29a63762046d67799e88dd0b52159e7e0006 Mon Sep 17 00:00:00 2001 From: Tobias Reich Date: Fri, 21 Aug 2015 22:00:14 +0200 Subject: [PATCH 06/18] Rebuild --- dist/main.js | Bin 181334 -> 181284 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/dist/main.js b/dist/main.js index 724a40aef4c8192cffe909494888b1649d27f0ce..2d2a86a2cbc1401141399df10f015c0da0ef6779 100644 GIT binary patch delta 43 zcmV+`0M!51hzq2M3xKo%pxgp1X_sK&0Xeq~-2saimpt?V6PK*?0Z0J~xAgP@V$YEU B5_JFo delta 67 zcmV-J0KEUChzr(;3xKo%pxgmFmk_%G8W<)fC~iG$Z)0I}X>V>WXmVv{DLZyyav-;% Z+yTWG0ScE<^#K!?dGrBDx5D%RV$V^r8Z`g_ From 363e39434b6d9f9bb32461204875904d5ae4b7f5 Mon Sep 17 00:00:00 2001 From: Tobias Reich Date: Fri, 21 Aug 2015 22:13:58 +0200 Subject: [PATCH 07/18] Added link to #384 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7908538..437e06a 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ In order to use the Dropbox import from your server, you need a valid drop-ins a ### Twitter Cards -Lychee supports [Twitter Cards](https://dev.twitter.com/docs/cards) and [Open Graph](http://opengraphprotocol.org) for shared images (not albums). In order to use Twitter Cards you need to request an approval for your domain. Simply share an image with Lychee, copy its link and paste it in [Twitters Card Validator](https://dev.twitter.com/docs/cards/validation/validator). +Lychee supports [Twitter Cards](https://dev.twitter.com/docs/cards) and [Open Graph](http://opengraphprotocol.org) for shared images ([not albums](https://github.com/electerious/Lychee/issues/384)). In order to use Twitter Cards you need to request an approval for your domain. Simply share an image with Lychee, copy its link and paste it in [Twitters Card Validator](https://dev.twitter.com/docs/cards/validation/validator). ### Imagick From bfedfa3f9491dc5d3db9f6cb3eb21b1afc4ea3fc Mon Sep 17 00:00:00 2001 From: Tobias Reich Date: Sat, 29 Aug 2015 14:54:20 +0200 Subject: [PATCH 08/18] Updated deps --- src/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/package.json b/src/package.json index ae7cf61..70a68be 100644 --- a/src/package.json +++ b/src/package.json @@ -18,10 +18,10 @@ "gulp-concat": "^2.6.0", "gulp-inject": "^1.5.0", "gulp-load-plugins": "^1.0.0-rc", - "gulp-minify-css": "^1.2.0", + "gulp-minify-css": "^1.2.1", "gulp-rimraf": "^0.1.1", "gulp-sass": "^2.0.4", - "gulp-uglify": "^1.2.0", + "gulp-uglify": "^1.4.0", "jquery": "^2.1.4", "mousetrap": "^1.5.3" } From eef971e3ebe5fcba33e01d44bd489ff3a2312ab8 Mon Sep 17 00:00:00 2001 From: Tobias Reich Date: Sat, 29 Aug 2015 21:55:32 +0200 Subject: [PATCH 09/18] Catch upload errors #393 --- php/modules/Import.php | 1 + php/modules/Photo.php | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/php/modules/Import.php b/php/modules/Import.php index 381e9b1..1872bbe 100644 --- a/php/modules/Import.php +++ b/php/modules/Import.php @@ -42,6 +42,7 @@ class Import extends Module { $nameFile[0]['tmp_name'] = $path; $nameFile[0]['error'] = 0; $nameFile[0]['size'] = $size; + $nameFile[0]['error'] = UPLOAD_ERR_OK; if (!$photo->add($nameFile, $albumID, $description, $tags, true)) return false; return true; diff --git a/php/modules/Photo.php b/php/modules/Photo.php index 402c42f..27bc1ff 100755 --- a/php/modules/Photo.php +++ b/php/modules/Photo.php @@ -88,6 +88,41 @@ class Photo extends Module { foreach ($files as $file) { + # Check if file exceeds the upload_max_filesize directive + if ($file['error']===UPLOAD_ERR_INI_SIZE) { + Log::error($this->database, __METHOD__, __LINE__, 'The uploaded file exceeds the upload_max_filesize directive in php.ini'); + if ($returnOnError===true) return false; + exit('Error: The uploaded file exceeds the upload_max_filesize directive in php.ini!'); + } + + # Check if file was only partially uploaded + if ($file['error']===UPLOAD_ERR_PARTIAL) { + Log::error($this->database, __METHOD__, __LINE__, 'The uploaded file was only partially uploaded'); + if ($returnOnError===true) return false; + exit('Error: The uploaded file was only partially uploaded!'); + } + + # Check if writing file to disk failed + if ($file['error']===UPLOAD_ERR_CANT_WRITE) { + Log::error($this->database, __METHOD__, __LINE__, 'Failed to write photo to disk'); + if ($returnOnError===true) return false; + exit('Error: Failed to write photo to disk!'); + } + + # Check if a extension stopped the file upload + if ($file['error']===UPLOAD_ERR_EXTENSION) { + Log::error($this->database, __METHOD__, __LINE__, 'A PHP extension stopped the file upload'); + if ($returnOnError===true) return false; + exit('Error: A PHP extension stopped the file upload!'); + } + + # Check if the upload was successful + if ($file['error']!==UPLOAD_ERR_OK) { + Log::error($this->database, __METHOD__, __LINE__, 'Upload contains an error (' . $file['error'] . ')'); + if ($returnOnError===true) return false; + exit('Error: Upload failed!'); + } + # Verify extension $extension = getExtension($file['name']); if (!in_array(strtolower($extension), Photo::$validExtensions, true)) { From 2a01b7b15c7f55a0794aa867627230d792d51c7d Mon Sep 17 00:00:00 2001 From: Tobias Reich Date: Sat, 29 Aug 2015 21:56:11 +0200 Subject: [PATCH 10/18] Rebuild --- dist/main.css | Bin 33110 -> 33208 bytes dist/main.js | Bin 181284 -> 181360 bytes dist/view.js | Bin 100553 -> 100577 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/dist/main.css b/dist/main.css index 3fd2271b29431a4c0b35e6d08dfca0b42b2fc665..ed6025d1d995d7b29c0e170bdc7bce0f8aab8ced 100755 GIT binary patch delta 1090 zcmZ`&OKcle6xH*LohqrLICf&2IQHb@xcRsp(>R}*lBNhHC8}tnCnM_r81Xd~?}z_jS)k96$OSQCF+p4!=cwC3<$=bl47231+1$l4{T3h)=;uImrfP znMhd<9P@^~{OnxAc$I>LFM$ud$NtN(gs;hiH`>S8=)Sy+ZmAb{+iTgK{Y|7QT`7be z(uOx93a;%BqFe~D)3vE;+>BV#pzW}5^x!|rEfVBicA`uIZ1SnE2>y)4@ug9N?wB7t z3#Ob1KNW`fIcf3$Z3_H20(28N!W@6zM^{xrqzM`FZrQ3#Rc!vnKavZAZMV z5^TpOQHp1tMA>$Hl~kbTl?gXsTgvA$dzjouaI`CdTL-FnN7blHS$IDcLu2O=-0H4o z4^n&<-*yc>A$pKH@INiH%iW*IVhCu-gb`NT-T0!~@8uXD{JnG(zaFZ=59-h()u4Nx z&SYhwz*I8IZf7167yauRmC0&L3`#8x3)I)i`y*g8QyTa5iI-qSyh5M5~w{wPJ zT60>~UKSYpeQZqf3g>l{Pc*RU7ZQZ8$-Si@K0V*SHct-7elF8Xrs3qpK-hN^5wHBT z_%F_Au}YA6DeO%Pi1oev6Typ9*WDNGO}!`8*fR=e+*)||%y0E>DR&Nw<_Z<9;nJxP zP6R^u*srjkrw1gT&?tv-=u`uSjjQf*Us;P_eXR|<#su4>pVj=!rHkhxZ1vUWWb9_6 z*gbzkxV^D-y`ImYU|KZ&n4x;LPBC<5=e*#29b~u6Lj3z*#tv8*gC%F z^XGHU_sKV3X1;!j+4g>LaSI8n`AT@bgp1)4&f#S(q2^T#dp37{T7Z7Or@m+dKGg<6 z<{ktQU;nuaw!2Qk8taA;R_Ef8LivhcfD~s38yA2DpB4UM@1b#UoN#xHQ+BW>MWEj9 zg>QT!%v+BC4@DZ5`-p@));aQBmml>!DybH#d;6~NPMC3r>I>~qw)13xAG5+B?}yi& zDknt?Wnyt1Mnrdii(o>u!lr103*IyBhgak@o>rma5#hEt3A% zsRmWgesFnh(CzJ~*MoOoWvF=S#3Dtg6}awumO7OFbx5xJs;I>e2hOxm!_rIM2;AUc zDRv|dMLSw+L)QbAtEG;O^o7vO#@(5KYb@K5R$EOz=e{{eXVb1gp}h0m!=&&zMIa#-`XfjbnOb z&ZMr6_)Us@5-%eg<&s&fWU^X9ii4btlk)gQ7RAB7<|2P4HxZyzFU&Aj_-*1>v%`-^ z?;^8#t>Pk$l+IY11Fg}@edNVs5f<3z`+A#=jhN(|l2H@OSk-V5Zk%*MZnl$Hr?$9I zbLEn&axtT*iDn1Caf|pP$%33tD{3Y&+BzifpMHup$3^yLM-iEtdz$NRmM3*3t!1fp zx>0XG7#85XLm;#Axt1n)FZ~YPUMt+Hx!_Ga54z05 zVzm=Impf4vW^jmnk3WCxk+4zJ4Xo^dL)C6LJ=sUvi#!LX<^+_4-i?opseQGvBQ%4| zDJl*>91|VqCH<9V0ck7WY0cuNODl{Ng}uf7|M2%(Wy~o1tkPiM)7k;jcKKr@8M!^& h-mH>NWA#6us2yBg&uNPBr-8{e0i2T#B5Ykle*+KpiopN? diff --git a/dist/main.js b/dist/main.js index 2d2a86a2cbc1401141399df10f015c0da0ef6779..8182d5a75307fb121c58021beb7b0d20b7434526 100644 GIT binary patch delta 302 zcmZ3|!Tq6wd&9DKGWi81nfZCedMTwvi6EAJNk(R|UO|4bZHevVL+`w~k@%Z=-xqLi zuKV?2!E~cJjOx=@%x099 z(wA~I=U=hK{H9hbvqww^~FN_}B4}D=Qs-5m=%Op0vX9=U! z_S7P#IXu%hE@c$su+P^kNlY)coi1O`y9NVYOVPuV%F7%C2 zc>4M;j2?_!+rNKdEUIPXn(o-oC^pTCNpky)BBr@K?D={niRs0*(*<`hit*Z4mt>aY zq*^J30L7*UwlIlKFR5qJ5~{A%$=6G*$V{^ZIz-XdHqUgA(k?r9`whhbP$>bN5Waj4;>!p+yC4yM?B^jB;dIkB#wk5Wc554o|M&fVgeP6)6 zx$f7=+UXB`8O1p4GxbUm(~E7Vr&cg}t3x@}wK_@$N{Y6&c|hsZip(_I>XOWooK!0% j=fvF9qQvQi6^x?O`O6u_rvI*Be8CP?wEb}<<1;=0l14j~ delta 83 zcmV-Z0IdJvkp{_;2C$~?25@h4J#;;jq3#xw!tP7681HZkvxoZ1eFteSbYW+6J(p2< p0T-8VcmYcidwncvE@gOWW<4S>A|X9JZa=rvcmW{+xBGbk<_;miB3u9f From 39b8df7c4fed6173922020e24f874e8639746f2e Mon Sep 17 00:00:00 2001 From: Tobias Reich Date: Sat, 29 Aug 2015 22:08:14 +0200 Subject: [PATCH 11/18] Updated version --- src/package.json | 2 +- src/scripts/lychee.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/package.json b/src/package.json index 70a68be..18675b2 100644 --- a/src/package.json +++ b/src/package.json @@ -1,6 +1,6 @@ { "name": "Lychee", - "version": "3.0.5", + "version": "3.0.6", "description": "Self-hosted photo-management done right.", "authors": "Tobias Reich ", "license": "MIT", diff --git a/src/scripts/lychee.js b/src/scripts/lychee.js index a07743a..a60bbb8 100644 --- a/src/scripts/lychee.js +++ b/src/scripts/lychee.js @@ -6,8 +6,8 @@ lychee = { title : document.title, - version : '3.0.5', - version_code : '030005', + version : '3.0.6', + version_code : '030006', update_path : 'http://lychee.electerious.com/version/index.php', updateURL : 'https://github.com/electerious/Lychee', From d6b51d9cbeca11bcd28f4bd76a2a19cf37d47944 Mon Sep 17 00:00:00 2001 From: Tobias Reich Date: Sat, 29 Aug 2015 22:15:44 +0200 Subject: [PATCH 12/18] Rebuild --- dist/main.js | Bin 181360 -> 181360 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/dist/main.js b/dist/main.js index 8182d5a75307fb121c58021beb7b0d20b7434526..1f82483352d108fa688a6c268f9a2bd0ca9ec45f 100644 GIT binary patch delta 43 ycmey+!Tq6wyP<_~3lkTMxS5hpS!z*nW`16La(+sxm6Cz6fq{Y9c10E@{{sL@Bn{mF delta 43 ycmey+!Tq6wyP<_~3lkTMxT%s(S!z*nW`16La(+sxm6Cz6fq{YPc10E@{{sL@01ev! From 2ba18242796e5cdedb20f031d11be91d4fe1b6e7 Mon Sep 17 00:00:00 2001 From: Tobias Reich Date: Sat, 5 Sep 2015 14:04:19 +0200 Subject: [PATCH 13/18] Improved var naming for albums without an overlay --- src/scripts/build.js | 6 +++--- src/styles/_content.scss | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/scripts/build.js b/src/scripts/build.js index 1da00dc..20643eb 100644 --- a/src/scripts/build.js +++ b/src/scripts/build.js @@ -37,9 +37,9 @@ build.album = function(data) { let html = `
- thumb - thumb - thumb + thumb + thumb + thumb

${ data.title }

${ data.sysdate } diff --git a/src/styles/_content.scss b/src/styles/_content.scss index 520b4cd..099fb5e 100644 --- a/src/styles/_content.scss +++ b/src/styles/_content.scss @@ -112,7 +112,7 @@ } // No overlay for empty albums - .album img[data-retina='false'] + .overlay { + .album img[data-overlay='false'] + .overlay { background: none; } @@ -154,8 +154,8 @@ filter: drop-shadow(0 1px 3px black(.4)); } - .album img[data-retina='false'] + .overlay h1, - .album img[data-retina='false'] + .overlay a { text-shadow: none; } + .album img[data-overlay='false'] + .overlay h1, + .album img[data-overlay='false'] + .overlay a { text-shadow: none; } /* Badges ------------------------------------------------*/ .album .badges, From 9b833f89d14d55a67f94df7603823410533b01e8 Mon Sep 17 00:00:00 2001 From: Tobias Reich Date: Sat, 5 Sep 2015 14:08:14 +0200 Subject: [PATCH 14/18] Improved escaping --- src/scripts/album.js | 11 ++--------- src/scripts/lychee.js | 24 ++++++++++-------------- src/scripts/photo.js | 16 +++------------- 3 files changed, 15 insertions(+), 36 deletions(-) diff --git a/src/scripts/album.js b/src/scripts/album.js index c3e94e1..8613a2f 100644 --- a/src/scripts/album.js +++ b/src/scripts/album.js @@ -247,7 +247,6 @@ album.setTitle = function(albumIDs) { else if (albums.json) oldTitle = albums.getByID(albumIDs).title if (!oldTitle) oldTitle = '' - oldTitle = oldTitle.replace(/'/g, ''') } @@ -257,9 +256,6 @@ album.setTitle = function(albumIDs) { basicModal.close() - // Remove html from input - newTitle = lychee.removeHTML(newTitle) - // Set title to Untitled when empty newTitle = (newTitle==='') ? 'Untitled' : newTitle @@ -296,7 +292,7 @@ album.setTitle = function(albumIDs) { } - let input = `` + let input = `` if (albumIDs.length===1) msg = `

Enter a new title for this album: ${ input }

` else msg = `

Enter a title for all ${ albumIDs.length } selected albums: ${ input }

` @@ -327,9 +323,6 @@ album.setDescription = function(albumID) { basicModal.close() - // Remove html from input - description = lychee.removeHTML(description) - if (visible.album()) { album.json.description = description view.album.description() @@ -349,7 +342,7 @@ album.setDescription = function(albumID) { } basicModal.show({ - body: `

Please enter a description for this album:

`, + body: `

Please enter a description for this album:

`, buttons: { action: { title: 'Set Description', diff --git a/src/scripts/lychee.js b/src/scripts/lychee.js index a60bbb8..a92e62d 100644 --- a/src/scripts/lychee.js +++ b/src/scripts/lychee.js @@ -312,15 +312,6 @@ lychee.animate = function(obj, animation) { } -lychee.escapeHTML = function(s) { - - return s.replace(/&/g, '&') - .replace(/"/g, '"') - .replace(//g, '>') - -} - lychee.retinize = function(path = '') { let pixelRatio = window.devicePixelRatio, @@ -385,14 +376,19 @@ lychee.getEventName = function() { } -lychee.removeHTML = function(html = '') { +lychee.escapeHTML = function(html = '') { - if (html==='') return html + // Ensure that html is a string + html += '' - let tmp = document.createElement('DIV') - tmp.innerHTML = html + // Escape all critical characters + html = html.replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') - return (tmp.textContent || tmp.innerText) + return html } diff --git a/src/scripts/photo.js b/src/scripts/photo.js index b1cc1f0..31c47d9 100644 --- a/src/scripts/photo.js +++ b/src/scripts/photo.js @@ -285,7 +285,6 @@ photo.setTitle = function(photoIDs) { // Get old title if only one photo is selected if (photo.json) oldTitle = photo.json.title else if (album.json) oldTitle = album.json.content[photoIDs].title - oldTitle = oldTitle.replace(/'/g, ''') } @@ -295,9 +294,6 @@ photo.setTitle = function(photoIDs) { let newTitle = data.title - // Remove html from input - newTitle = lychee.removeHTML(newTitle) - if (visible.photo()) { photo.json.title = (newTitle==='' ? 'Untitled' : newTitle) view.photo.title() @@ -321,7 +317,7 @@ photo.setTitle = function(photoIDs) { } - let input = `` + let input = `` if (photoIDs.length===1) msg = `

Enter a new title for this photo: ${ input }

` else msg = `

Enter a title for all ${ photoIDs.length } selected photos: ${ input }

` @@ -465,7 +461,7 @@ photo.setPublic = function(photoID, e) { photo.setDescription = function(photoID) { - let oldDescription = photo.json.description.replace(/'/g, ''') + let oldDescription = photo.json.description const action = function(data) { @@ -473,9 +469,6 @@ photo.setDescription = function(photoID) { let description = data.description - // Remove html from input - description = lychee.removeHTML(description) - if (visible.photo()) { photo.json.description = description view.photo.description() @@ -495,7 +488,7 @@ photo.setDescription = function(photoID) { } basicModal.show({ - body: `

Enter a description for this photo:

`, + body: `

Enter a description for this photo:

`, buttons: { action: { title: 'Set Description', @@ -571,9 +564,6 @@ photo.setTags = function(photoIDs, tags) { tags = tags.replace(/(\ ,\ )|(\ ,)|(,\ )|(,{1,}\ {0,})|(,$|^,)/g, ',') tags = tags.replace(/,$|^,|(\ ){0,}$/g, '') - // Remove html from input - tags = lychee.removeHTML(tags) - if (visible.photo()) { photo.json.tags = tags view.photo.tags() From 2e96f089a759bd2970282103772928faa75a0f6b Mon Sep 17 00:00:00 2001 From: Tobias Reich Date: Sat, 5 Sep 2015 23:02:58 +0200 Subject: [PATCH 15/18] Fixed tons of XSS issues and escaping problems --- dist/main.css | Bin 33208 -> 33507 bytes dist/main.js | Bin 181360 -> 189708 bytes dist/view.js | Bin 100577 -> 104949 bytes php/modules/Album.php | 7 -- php/modules/Photo.php | 11 --- src/gulpfile.js | 8 +-- src/scripts/album.js | 16 ++--- src/scripts/build.js | 134 ++++++++++++++++++++----------------- src/scripts/contextMenu.js | 8 +-- src/scripts/header.js | 5 +- src/scripts/lychee.js | 39 ++++++++++- src/scripts/password.js | 2 + src/scripts/photo.js | 18 ++--- src/scripts/settings.js | 4 +- src/scripts/sidebar.js | 33 ++++----- src/scripts/upload.js | 4 +- src/scripts/view.js | 11 +-- src/scripts/view/main.js | 72 +++++++++++++++++--- 18 files changed, 234 insertions(+), 138 deletions(-) diff --git a/dist/main.css b/dist/main.css index ed6025d1d995d7b29c0e170bdc7bce0f8aab8ced..66ae00c39d484b5b43807c96fa2877036464ecf2 100755 GIT binary patch delta 326 zcmdnd%=EaGX#=;Kq;7d?Qg&vEZgOHyvWB67fvSS8f`xHGh34e{s%NCJ$r>9N0Hv)r zA5~-IVb3p1Ey_u(+J@dAF$!jJ^4-=}TFxmY9J|*?cZ|3JX?gQ?PVYL=iJ? z>B&c|5p^Nc4JeJ_7|lWStPJ&H!Mij+`OrZjU51l C5obF&qtmSpB7ZWcCv$;1a?+omPv6sN{&PhMyuI$6Pt vAEw02?4|5xhmanY&2J;Jm?xLVm~B26^No4)lZ5Z0n}3&`V%hwp`ac^0g54?3 diff --git a/dist/main.js b/dist/main.js index 1f82483352d108fa688a6c268f9a2bd0ca9ec45f..9374a12bf02c1e5c50b2e5fc192b1da1c3284f27 100644 GIT binary patch delta 15609 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
    ?8YJr(IKBNG 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 delta 10268 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 Wb?#tl3||UTK*+}8mFKuu)BZ08a#mdc diff --git a/dist/view.js b/dist/view.js index 22630f5d580b4c139e57a75bfb72593f9959821f..55e4ffaf43e54c5705b2a1a29734a311386b1d13 100644 GIT binary patch delta 14295 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 delta 10878 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 P%U9-ix_IZ^@LT^IWJCUO diff --git a/php/modules/Album.php b/php/modules/Album.php index d91ac04..a04dcac 100644 --- a/php/modules/Album.php +++ b/php/modules/Album.php @@ -483,9 +483,6 @@ class Album extends Module { # Call plugins $this->plugins(__METHOD__, 0, func_get_args()); - # Parse - if (strlen($title)>100) $title = substr($title, 0, 100); - # Execute query $query = Database::prepare($this->database, "UPDATE ? SET title = '?' WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $title, $this->albumIDs)); $result = $this->database->query($query); @@ -509,10 +506,6 @@ class Album extends Module { # Call plugins $this->plugins(__METHOD__, 0, func_get_args()); - # Parse - $description = htmlentities($description, ENT_COMPAT | ENT_HTML401, 'UTF-8'); - if (strlen($description)>1000) $description = substr($description, 0, 1000); - # Execute query $query = Database::prepare($this->database, "UPDATE ? SET description = '?' WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $description, $this->albumIDs)); $result = $this->database->query($query); diff --git a/php/modules/Photo.php b/php/modules/Photo.php index 27bc1ff..1035f21 100755 --- a/php/modules/Photo.php +++ b/php/modules/Photo.php @@ -896,9 +896,6 @@ class Photo extends Module { # Call plugins $this->plugins(__METHOD__, 0, func_get_args()); - # Parse - if (strlen($title)>100) $title = substr($title, 0, 100); - # Set title $query = Database::prepare($this->database, "UPDATE ? SET title = '?' WHERE id IN (?)", array(LYCHEE_TABLE_PHOTOS, $title, $this->photoIDs)); $result = $this->database->query($query); @@ -929,10 +926,6 @@ class Photo extends Module { # Call plugins $this->plugins(__METHOD__, 0, func_get_args()); - # Parse - $description = htmlentities($description, ENT_COMPAT | ENT_HTML401, 'UTF-8'); - if (strlen($description)>1000) $description = substr($description, 0, 1000); - # Set description $query = Database::prepare($this->database, "UPDATE ? SET description = '?' WHERE id IN ('?')", array(LYCHEE_TABLE_PHOTOS, $description, $this->photoIDs)); $result = $this->database->query($query); @@ -1122,10 +1115,6 @@ class Photo extends Module { # Parse tags $tags = preg_replace('/(\ ,\ )|(\ ,)|(,\ )|(,{1,}\ {0,})|(,$|^,)/', ',', $tags); $tags = preg_replace('/,$|^,|(\ ){0,}$/', '', $tags); - if (strlen($tags)>1000) { - Log::notice($this->database, __METHOD__, __LINE__, 'Length of tags higher than 1000'); - return false; - } # Set tags $query = Database::prepare($this->database, "UPDATE ? SET tags = '?' WHERE id IN (?)", array(LYCHEE_TABLE_PHOTOS, $tags, $this->photoIDs)); diff --git a/src/gulpfile.js b/src/gulpfile.js index 8e70103..b313f03 100644 --- a/src/gulpfile.js +++ b/src/gulpfile.js @@ -40,9 +40,9 @@ gulp.task('view--js', function() { var stream = gulp.src(paths.view.js) - .pipe(plugins.babel()) - .on('error', catchError) .pipe(plugins.concat('_view--javascript.js', {newLine: "\n"})) + .pipe(plugins.babel({ compact: true })) + .on('error', catchError) .pipe(gulp.dest('../dist/')); return stream; @@ -109,9 +109,9 @@ gulp.task('main--js', function() { var stream = gulp.src(paths.main.js) - .pipe(plugins.babel()) - .on('error', catchError) .pipe(plugins.concat('_main--javascript.js', {newLine: "\n"})) + .pipe(plugins.babel({ compact: true })) + .on('error', catchError) .pipe(gulp.dest('../dist/')); return stream; diff --git a/src/scripts/album.js b/src/scripts/album.js index 8613a2f..c813e3a 100644 --- a/src/scripts/album.js +++ b/src/scripts/album.js @@ -204,14 +204,14 @@ album.delete = function(albumIDs) { if (album.json) albumTitle = album.json.title else if (albums.json) albumTitle = albums.getByID(albumIDs).title - msg = `

    Are you sure you want to delete the album ${ albumTitle } and all of the photos it contains? This action can't be undone!

    ` + msg = lychee.html`

    Are you sure you want to delete the album '$${ albumTitle }' and all of the photos it contains? This action can't be undone!

    ` } else { action.title = 'Delete Albums and Photos' cancel.title = 'Keep Albums' - msg = `

    Are you sure you want to delete all ${ albumIDs.length } selected albums and all of the photos they contain? This action can't be undone!

    ` + msg = lychee.html`

    Are you sure you want to delete all $${ albumIDs.length } selected albums and all of the photos they contain? This action can't be undone!

    ` } @@ -292,10 +292,10 @@ album.setTitle = function(albumIDs) { } - let input = `` + let input = lychee.html`` - if (albumIDs.length===1) msg = `

    Enter a new title for this album: ${ input }

    ` - else msg = `

    Enter a title for all ${ albumIDs.length } selected albums: ${ input }

    ` + if (albumIDs.length===1) msg = lychee.html`

    Enter a new title for this album: ${ input }

    ` + else msg = lychee.html`

    Enter a title for all $${ albumIDs.length } selected albums: ${ input }

    ` basicModal.show({ body: msg, @@ -342,7 +342,7 @@ album.setDescription = function(albumID) { } basicModal.show({ - body: `

    Please enter a description for this album:

    `, + body: lychee.html`

    Please enter a description for this album:

    `, buttons: { action: { title: 'Set Description', @@ -574,11 +574,11 @@ album.merge = function(albumIDs) { if (!sTitle) sTitle = '' sTitle = sTitle.replace(/'/g, ''') - msg = `

    Are you sure you want to merge the album '${ sTitle }' into the album '${ title }'?

    ` + msg = lychee.html`

    Are you sure you want to merge the album '$${ sTitle }' into the album '$${ title }'?

    ` } else { - msg = `

    Are you sure you want to merge all selected albums into the album '${ title }'?

    ` + msg = lychee.html`

    Are you sure you want to merge all selected albums into the album '$${ title }'?

    ` } diff --git a/src/scripts/build.js b/src/scripts/build.js index 20643eb..3e25391 100644 --- a/src/scripts/build.js +++ b/src/scripts/build.js @@ -7,54 +7,66 @@ build = {} build.iconic = function(icon, classes = '') { - return `` + let html = '' + + html += lychee.html`` + + return html } build.divider = function(title) { - return `

    ${ title }

    ` + let html = '' + + html += lychee.html`

    $${ title }

    ` + + return html } build.editIcon = function(id) { - return `
    ${ build.iconic('pencil') }
    ` + let html = '' + + html += lychee.html`
    ${ build.iconic('pencil') }
    ` + + return html } build.multiselect = function(top, left) { - return `
    ` + return lychee.html`
    ` } build.album = function(data) { - if (data==null) return '' + let html = '' let { path: thumbPath, hasRetina: thumbRetina } = lychee.retinize(data.thumbs[0]) - let html = ` -
    - thumb - thumb - thumb -
    -

    ${ data.title }

    - ${ data.sysdate } -
    - ` + html += lychee.html` +
    + thumb + thumb + thumb +
    +

    $${ data.title }

    + $${ data.sysdate } +
    + ` if (lychee.publicMode===false) { - html += ` + html += lychee.html` ` @@ -68,34 +80,34 @@ build.album = function(data) { build.photo = function(data) { - if (data==null) return '' + let html = '' let { path: thumbPath, hasRetina: thumbRetina } = lychee.retinize(data.thumbUrl) - let html = ` -
    - thumb -
    -

    ${ data.title }

    - ` + html += lychee.html` +
    + thumb +
    +

    $${ data.title }

    + ` - if (data.cameraDate==='1') html += `${ build.iconic('camera-slr') }${ data.sysdate }` - else html += `${ data.sysdate }` + if (data.cameraDate==='1') html += lychee.html`${ build.iconic('camera-slr') }$${ data.sysdate }` + else html += lychee.html`$${ data.sysdate }` - html += '
    ' + html += `
    ` if (lychee.publicMode===false) { - html += ` + html += lychee.html` ` } - html += '
    ' + html += `
    ` return html @@ -103,24 +115,22 @@ build.photo = function(data) { build.imageview = function(data, size, visibleControls) { - if (data==null) return '' - let html = '' if (size==='big') { - if (visibleControls===true) html += `
    ` - else html += `
    ` + if (visibleControls===true) html += lychee.html`
    ` + else html += lychee.html`
    ` } else if (size==='medium') { - if (visibleControls===true) html += `
    ` - else html += `
    ` + if (visibleControls===true) html += lychee.html`
    ` + else html += lychee.html`
    ` } else if (size==='small') { - if (visibleControls===true) html += `
    ` - else html += `
    ` + if (visibleControls===true) html += lychee.html`
    ` + else html += lychee.html`
    ` } @@ -135,27 +145,29 @@ build.imageview = function(data, size, visibleControls) { build.no_content = function(typ) { - let html = ` -
    - ${ build.iconic(typ) } - ` + let html = '' + + html += ` +
    + ${ build.iconic(typ) } + ` switch (typ) { case 'magnifying-glass': - html += '

    No results

    ' + html += `

    No results

    ` break case 'eye': - html += '

    No public albums

    ' + html += `

    No public albums

    ` break case 'cog': - html += '

    No configuration

    ' + html += `

    No configuration

    ` break case 'question-mark': - html += '

    Photo not found

    ' + html += `

    Photo not found

    ` break } - html += '
    ' + html += `
    ` return html @@ -163,10 +175,12 @@ build.no_content = function(typ) { build.uploadModal = function(title, files) { - let html = ` -

    ${ title }

    -
    - ` + let html = '' + + html += lychee.html` +

    $${ title }

    +
    + ` let i = 0 @@ -176,9 +190,9 @@ build.uploadModal = function(title, files) { if (file.name.length>40) file.name = file.name.substr(0, 17) + '...' + file.name.substr(file.name.length-20, 20) - html += ` + html += lychee.html`
    - ${ lychee.escapeHTML(file.name) } + $${ file.name } ` if (file.supported===true) html += `` @@ -193,7 +207,7 @@ build.uploadModal = function(title, files) { } - html += '
    ' + html += `
    ` return html @@ -208,7 +222,7 @@ build.tags = function(tags) { tags = tags.split(',') tags.forEach(function(tag, index, array) { - html += `${ tag }${ build.iconic('x') }` + html += lychee.html`$${ tag }${ build.iconic('x') }` }) } else { diff --git a/src/scripts/contextMenu.js b/src/scripts/contextMenu.js index eca5995..b387bfa 100644 --- a/src/scripts/contextMenu.js +++ b/src/scripts/contextMenu.js @@ -101,7 +101,7 @@ contextMenu.albumTitle = function(albumID, e) { if (!this.thumbs[0]) this.thumbs[0] = 'src/images/no_cover.svg' - let title = `
    ${ this.title }
    ` + let title = lychee.html`
    $${ this.title }
    ` if (this.id!=albumID) items.push({ type: 'item', title, fn: () => lychee.goto(this.id) }) @@ -131,7 +131,7 @@ contextMenu.mergeAlbum = function(albumID, e) { if (!this.thumbs[0]) this.thumbs[0] = 'src/images/no_cover.svg' - let title = `
    ${ this.title }
    ` + let title = lychee.html`
    $${ this.title }
    ` if (this.id!=albumID) items.push({ type: 'item', title, fn: () => album.merge([albumID, this.id]) }) @@ -206,7 +206,7 @@ contextMenu.photoTitle = function(albumID, photoID, e) { // Generate list of albums $.each(data.content, function(index) { - let title = `
    ${ this.title }
    ` + let title = lychee.html`
    $${ this.title }
    ` if (this.id!=photoID) items.push({ type: 'item', title, fn: () => lychee.goto(albumID + '/' + this.id) }) @@ -254,7 +254,7 @@ contextMenu.move = function(photoIDs, e) { if (!this.thumbs[0]) this.thumbs[0] = 'src/images/no_cover.svg' - let title = `
    ${ this.title }
    ` + let title = lychee.html`
    $${ this.title }
    ` if (this.id!=album.getID()) items.push({ type: 'item', title, fn: () => photo.setAlbum(photoIDs, this.id) }) diff --git a/src/scripts/header.js b/src/scripts/header.js index 716a51c..e185fcc 100644 --- a/src/scripts/header.js +++ b/src/scripts/header.js @@ -107,9 +107,10 @@ header.hide = function(e, delay = 500) { header.setTitle = function(title = 'Untitled') { - let $title = header.dom('#title') + let $title = header.dom('#title'), + html = lychee.html`$${ title }${ build.iconic('caret-bottom') }` - $title.html(title + build.iconic('caret-bottom')) + $title.html(html) return true diff --git a/src/scripts/lychee.js b/src/scripts/lychee.js index a92e62d..506a28c 100644 --- a/src/scripts/lychee.js +++ b/src/scripts/lychee.js @@ -119,12 +119,12 @@ lychee.login = function(data) { lychee.loginDialog = function() { - let msg = ` + let msg = lychee.html` -

    Lychee ${ lychee.version }Update available!

    +

    Lychee $${ lychee.version }Update available!

    ` basicModal.show({ @@ -387,11 +387,46 @@ lychee.escapeHTML = function(html = '') { .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, ''') + .replace(/`/g, '`') return html } +lychee.html = function(literalSections, ...substs) { + + // Use raw literal sections: we don’t want + // backslashes (\n etc.) to be interpreted + let raw = literalSections.raw, + result = '' + + substs.forEach((subst, i) => { + + // Retrieve the literal section preceding + // the current substitution + let lit = raw[i] + + // If the substitution is preceded by a dollar sign, + // we escape special characters in it + if (lit.slice(-1)==='$') { + subst = lychee.escapeHTML(subst) + lit = lit.slice(0, -1) + } + + result += lit + result += subst + + }) + + // Take care of last literal section + // (Never fails, because an empty template string + // produces one literal section, an empty string) + result += raw[raw.length-1] + + return result + +} + lychee.error = function(errorThrown, params, data) { console.error({ diff --git a/src/scripts/password.js b/src/scripts/password.js index 7187d1f..6b28601 100644 --- a/src/scripts/password.js +++ b/src/scripts/password.js @@ -55,8 +55,10 @@ password.getDialog = function(albumID, callback) { const action = (data) => password.get(albumID, callback, data.password) const cancel = () => { + basicModal.close() if (visible.albums()===false) lychee.goto() + } let msg = ` diff --git a/src/scripts/photo.js b/src/scripts/photo.js index 31c47d9..5770b5c 100644 --- a/src/scripts/photo.js +++ b/src/scripts/photo.js @@ -244,14 +244,14 @@ photo.delete = function(photoIDs) { action.title = 'Delete Photo' cancel.title = 'Keep Photo' - msg = `

    Are you sure you want to delete the photo '${ photoTitle }'? This action can't be undone!

    ` + msg = lychee.html`

    Are you sure you want to delete the photo '$${ photoTitle }'? This action can't be undone!

    ` } else { action.title = 'Delete Photo' cancel.title = 'Keep Photo' - msg = `

    Are you sure you want to delete all ${ photoIDs.length } selected photo? This action can't be undone!

    ` + msg = lychee.html`

    Are you sure you want to delete all $${ photoIDs.length } selected photo? This action can't be undone!

    ` } @@ -317,10 +317,10 @@ photo.setTitle = function(photoIDs) { } - let input = `` + let input = lychee.html`` - if (photoIDs.length===1) msg = `

    Enter a new title for this photo: ${ input }

    ` - else msg = `

    Enter a title for all ${ photoIDs.length } selected photos: ${ input }

    ` + if (photoIDs.length===1) msg = lychee.html`

    Enter a new title for this photo: ${ input }

    ` + else msg = lychee.html`

    Enter a title for all $${ photoIDs.length } selected photos: ${ input }

    ` basicModal.show({ body: msg, @@ -488,7 +488,7 @@ photo.setDescription = function(photoID) { } basicModal.show({ - body: `

    Enter a description for this photo:

    `, + body: lychee.html`

    Enter a description for this photo:

    `, buttons: { action: { title: 'Set Description', @@ -534,10 +534,10 @@ photo.editTags = function(photoIDs) { } - let input = `` + let input = lychee.html`` - if (photoIDs.length===1) msg = `

    Enter your tags for this photo. You can add multiple tags by separating them with a comma: ${ input }

    ` - else msg = `

    Enter your tags for all ${ photoIDs.length } selected photos. Existing tags will be overwritten. You can add multiple tags by separating them with a comma: ${ input }

    ` + if (photoIDs.length===1) msg = lychee.html`

    Enter your tags for this photo. You can add multiple tags by separating them with a comma: ${ input }

    ` + else msg = lychee.html`

    Enter your tags for all $${ photoIDs.length } selected photos. Existing tags will be overwritten. You can add multiple tags by separating them with a comma: ${ input }

    ` basicModal.show({ body: msg, diff --git a/src/scripts/settings.js b/src/scripts/settings.js index 89e054c..876ef8b 100644 --- a/src/scripts/settings.js +++ b/src/scripts/settings.js @@ -404,10 +404,10 @@ settings.setDropboxKey = function(callback) { } - let msg = ` + let msg = lychee.html`

    In order to import photos from your Dropbox, you need a valid drop-ins app key from their website. Generate yourself a personal key and enter it below: - +

    ` diff --git a/src/scripts/sidebar.js b/src/scripts/sidebar.js index 9cbd473..d301ef6 100644 --- a/src/scripts/sidebar.js +++ b/src/scripts/sidebar.js @@ -93,13 +93,17 @@ sidebar.setSelectable = function(selectable = true) { } -sidebar.changeAttr = function(attr, value = '-') { +sidebar.changeAttr = function(attr, value = '-', dangerouslySetInnerHTML = false) { if (attr==null || attr==='') return false // Set a default for the value if (value==null || value==='') value = '-' + // Escape value + if (dangerouslySetInnerHTML===false) value = lychee.escapeHTML(value) + + // Set new value sidebar.dom('.attr_' + attr).html(value) return true @@ -339,14 +343,14 @@ sidebar.render = function(structure) { if (value==='' || value==null) value = '-' // Wrap span-element around value for easier selecting on change - value = `${ value }` + value = lychee.html`$${ value }` // Add edit-icon to the value when editable if (row.editable===true) value += ' ' + build.editIcon('edit_' + row.title.toLowerCase()) - _html += ` + _html += lychee.html` - ${ row.title } + $${ row.title } ${ value } ` @@ -363,20 +367,19 @@ sidebar.render = function(structure) { let renderTags = function(section) { - let _html = '' - - _html += ` -
    -

    ${ section.title }

    -
    -
    -
    ${ section.value }
    - ` + let _html = '', + editable = '' // Add edit-icon to the value when editable - if (section.editable===true) _html += build.editIcon('edit_tags') + if (section.editable===true) editable = build.editIcon('edit_tags') - _html += ` + _html += lychee.html` +
    +

    $${ section.title }

    +
    +
    +
    ${ section.value }
    + ${ editable }
    ` diff --git a/src/scripts/upload.js b/src/scripts/upload.js index e536a75..2d473af 100755 --- a/src/scripts/upload.js +++ b/src/scripts/upload.js @@ -338,7 +338,7 @@ upload.start = { } basicModal.show({ - body: `

    Please enter the direct link to a photo to import it:

    `, + body: lychee.html`

    Please enter the direct link to a photo to import it:

    `, buttons: { action: { title: 'Import', @@ -444,7 +444,7 @@ upload.start = { } basicModal.show({ - body: `

    This action will import all photos, folders and sub-folders which are located in the following directory. The original files will be deleted after the import when possible.

    `, + body: lychee.html`

    This action will import all photos, folders and sub-folders which are located in the following directory. The original files will be deleted after the import when possible.

    `, buttons: { action: { title: 'Import', diff --git a/src/scripts/view.js b/src/scripts/view.js index 1fed949..af63fb5 100644 --- a/src/scripts/view.js +++ b/src/scripts/view.js @@ -72,6 +72,8 @@ view.albums = { let title = albums.getByID(albumID).title + title = lychee.escapeHTML(title) + $('.album[data-id="' + albumID + '"] .overlay h1') .html(title) .attr('title', title) @@ -164,6 +166,8 @@ view.album = { let title = album.json.content[photoID].title + title = lychee.escapeHTML(title) + $('.photo[data-id="' + photoID + '"] .overlay h1') .html(title) .attr('title', title) @@ -199,7 +203,6 @@ view.album = { if (!visible.albums()) { album.json.num-- view.album.num() - view.album.title() } }) @@ -396,7 +399,7 @@ view.photo = { tags: function() { - sidebar.changeAttr('tags', build.tags(photo.json.tags)) + sidebar.changeAttr('tags', build.tags(photo.json.tags), true) sidebar.bind() }, @@ -416,7 +419,7 @@ view.photo = { let nextPhotoID = album.json.content[photo.getID()].nextPhoto, nextPhoto = album.json.content[nextPhotoID] - $nextArrow.css('background-image', `linear-gradient(to bottom, rgba(0, 0, 0, .4), rgba(0, 0, 0, .4)), url("${ nextPhoto.thumbUrl }")`) + $nextArrow.css('background-image', lychee.html`linear-gradient(to bottom, rgba(0, 0, 0, .4), rgba(0, 0, 0, .4)), url("$${ nextPhoto.thumbUrl }")`) } @@ -426,7 +429,7 @@ view.photo = { let previousPhotoID = album.json.content[photo.getID()].previousPhoto, previousPhoto = album.json.content[previousPhotoID] - $previousArrow.css('background-image', `linear-gradient(to bottom, rgba(0, 0, 0, .4), rgba(0, 0, 0, .4)), url("${ previousPhoto.thumbUrl }")`) + $previousArrow.css('background-image', lychee.html`linear-gradient(to bottom, rgba(0, 0, 0, .4), rgba(0, 0, 0, .4)), url("$${ previousPhoto.thumbUrl }")`) } diff --git a/src/scripts/view/main.js b/src/scripts/view/main.js index 8335c47..7e85922 100644 --- a/src/scripts/view/main.js +++ b/src/scripts/view/main.js @@ -3,18 +3,74 @@ * @copyright 2015 by Tobias Reich */ -let lychee = { - content: $('#content'), - getEventName() { +// Sub-implementation of Lychee -------------------------------------------------------------- // - let touchendSupport = (/Android|iPhone|iPad|iPod/i).test(navigator.userAgent || navigator.vendor || window.opera) && ('ontouchend' in document.documentElement), - eventName = (touchendSupport===true ? 'touchend' : 'click') +let lychee = {} - return eventName +lychee.content = $('#content') + +lychee.getEventName = function() { + + let touchendSupport = (/Android|iPhone|iPad|iPod/i).test(navigator.userAgent || navigator.vendor || window.opera) && ('ontouchend' in document.documentElement), + eventName = (touchendSupport===true ? 'touchend' : 'click') + + return eventName - } } +lychee.escapeHTML = function(html = '') { + + // Ensure that html is a string + html += '' + + // Escape all critical characters + html = html.replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') + .replace(/`/g, '`') + + return html + +} + +lychee.html = function(literalSections, ...substs) { + + // Use raw literal sections: we don’t want + // backslashes (\n etc.) to be interpreted + let raw = literalSections.raw, + result = '' + + substs.forEach((subst, i) => { + + // Retrieve the literal section preceding + // the current substitution + let lit = raw[i] + + // If the substitution is preceded by a dollar sign, + // we escape special characters in it + if (lit.slice(-1)==='$') { + subst = lychee.escapeHTML(subst) + lit = lit.slice(0, -1) + } + + result += lit + result += subst + + }) + + // Take care of last literal section + // (Never fails, because an empty template string + // produces one literal section, an empty string) + result += raw[raw.length-1] + + return result + +} + +// Main -------------------------------------------------------------- // + let loadingBar = { show() {}, hide() {} }, imageview = $('#imageview') @@ -99,7 +155,7 @@ const loadPhotoInfo = function(photoID) { // Set title if (!data.title) data.title = 'Untitled' document.title = 'Lychee - ' + data.title - header.dom('#title').html(data.title) + header.dom('#title').html(lychee.escapeHTML(data.title)) let size = getPhotoSize(data) From 684ecb028ff436467e59feafa71f52d90f83e394 Mon Sep 17 00:00:00 2001 From: Tobias Reich Date: Sat, 5 Sep 2015 23:03:03 +0200 Subject: [PATCH 16/18] Updated deps --- src/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/package.json b/src/package.json index 18675b2..5ba167b 100644 --- a/src/package.json +++ b/src/package.json @@ -13,7 +13,7 @@ "basiccontext": "^3.3.0", "basicmodal": "^3.1.2", "gulp": "^3.9.0", - "gulp-autoprefixer": "2.3.1", + "gulp-autoprefixer": "3.0.1", "gulp-babel": "^5.2.1", "gulp-concat": "^2.6.0", "gulp-inject": "^1.5.0", From afd3c31896bbdcfd9bdd780c14fc6e32d9c043a6 Mon Sep 17 00:00:00 2001 From: Tobias Reich Date: Sun, 13 Sep 2015 14:38:53 +0200 Subject: [PATCH 17/18] Updated deps --- dist/main.js | Bin 189708 -> 189776 bytes src/package.json | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dist/main.js b/dist/main.js index 9374a12bf02c1e5c50b2e5fc192b1da1c3284f27..dcd6c068131e04f387bbb8d085e24abbac0ad3e0 100644 GIT binary patch delta 167 zcmeA<#eLxvcf%IO_ZiZeX&N~inzpvKiiVm+sU@XFd5VVCWr;-!CAQPgXEBP|Ybfa@ zB^GBUJLl(>q*j!GsKm^?)FLHKYlwP<61{@NqSU++*PPT`Af;E7nwwvi>YS08lcG^F zT_%&!bbC-H<6>S$uIXRP82z@plrwIt0djtQX3W{1{)JK3gE40N6dR@z6DDh}=^TYj L<=YzynRJ=mwrRJ5m=A`BVDZQf9 z-2Adsjp^~3jHZm5+oxnQF6NzXQO@Yc$hEz#oN-&tblWeCIY7?JFO0e#+ppU&m6%NT PEo3SO3LPnA>L~^Q@p&jx diff --git a/src/package.json b/src/package.json index 5ba167b..2ced175 100644 --- a/src/package.json +++ b/src/package.json @@ -10,7 +10,7 @@ "url": "https://github.com/electerious/Lychee.git" }, "devDependencies": { - "basiccontext": "^3.3.0", + "basiccontext": "^3.3.1", "basicmodal": "^3.1.2", "gulp": "^3.9.0", "gulp-autoprefixer": "3.0.1", @@ -21,7 +21,7 @@ "gulp-minify-css": "^1.2.1", "gulp-rimraf": "^0.1.1", "gulp-sass": "^2.0.4", - "gulp-uglify": "^1.4.0", + "gulp-uglify": "^1.4.1", "jquery": "^2.1.4", "mousetrap": "^1.5.3" } From a90e066729d38935f166c2fd698e3e35bd171367 Mon Sep 17 00:00:00 2001 From: Tobias Reich Date: Sun, 13 Sep 2015 14:44:32 +0200 Subject: [PATCH 18/18] Updated changelog --- docs/Changelog.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/Changelog.md b/docs/Changelog.md index 8bfd074..ffb192b 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -1,3 +1,12 @@ +## v3.0.6 + +Released September 13, 2015 + +- `Improved` Share photo now shares view.php link (#392) +- `Fixed` Incorrect error messages for failed uploads (#393) +- `Fixed` XSS issues and escaping problems +- `Fixed` Broken "Download album" when album has an ampersand in the password (#356) + ## v3.0.5 Released August 9, 2015