Fixed tons of XSS issues and escaping problems

This commit is contained in:
Tobias Reich 2015-09-05 23:02:58 +02:00
parent 9b833f89d1
commit 2e96f089a7
18 changed files with 234 additions and 138 deletions

BIN
dist/main.css vendored

Binary file not shown.

BIN
dist/main.js vendored

Binary file not shown.

BIN
dist/view.js vendored

Binary file not shown.

View File

@ -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);

View File

@ -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));

View File

@ -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;

View File

@ -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 = `<p>Are you sure you want to delete the album ${ albumTitle } and all of the photos it contains? This action can't be undone!</p>`
msg = lychee.html`<p>Are you sure you want to delete the album '$${ albumTitle }' and all of the photos it contains? This action can't be undone!</p>`
} else {
action.title = 'Delete Albums and Photos'
cancel.title = 'Keep Albums'
msg = `<p>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!</p>`
msg = lychee.html`<p>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!</p>`
}
@ -292,10 +292,10 @@ album.setTitle = function(albumIDs) {
}
let input = `<input class='text' name='title' type='text' maxlength='50' placeholder='Title' value='${ lychee.escapeHTML(oldTitle) }'>`
let input = lychee.html`<input class='text' name='title' type='text' maxlength='50' placeholder='Title' value='$${ oldTitle }'>`
if (albumIDs.length===1) msg = `<p>Enter a new title for this album: ${ input }</p>`
else msg = `<p>Enter a title for all ${ albumIDs.length } selected albums: ${ input }</p>`
if (albumIDs.length===1) msg = lychee.html`<p>Enter a new title for this album: ${ input }</p>`
else msg = lychee.html`<p>Enter a title for all $${ albumIDs.length } selected albums: ${ input }</p>`
basicModal.show({
body: msg,
@ -342,7 +342,7 @@ album.setDescription = function(albumID) {
}
basicModal.show({
body: `<p>Please enter a description for this album: <input class='text' name='description' type='text' maxlength='800' placeholder='Description' value='${ lychee.escapeHTML(oldDescription) }'></p>`,
body: lychee.html`<p>Please enter a description for this album: <input class='text' name='description' type='text' maxlength='800' placeholder='Description' value='$${ oldDescription }'></p>`,
buttons: {
action: {
title: 'Set Description',
@ -574,11 +574,11 @@ album.merge = function(albumIDs) {
if (!sTitle) sTitle = ''
sTitle = sTitle.replace(/'/g, '&apos;')
msg = `<p>Are you sure you want to merge the album '${ sTitle }' into the album '${ title }'?</p>`
msg = lychee.html`<p>Are you sure you want to merge the album '$${ sTitle }' into the album '$${ title }'?</p>`
} else {
msg = `<p>Are you sure you want to merge all selected albums into the album '${ title }'?</p>`
msg = lychee.html`<p>Are you sure you want to merge all selected albums into the album '$${ title }'?</p>`
}

View File

@ -7,54 +7,66 @@ build = {}
build.iconic = function(icon, classes = '') {
return `<svg class='iconic ${ classes }'><use xlink:href='#${ icon }' /></svg>`
let html = ''
html += lychee.html`<svg class='iconic $${ classes }'><use xlink:href='#$${ icon }' /></svg>`
return html
}
build.divider = function(title) {
return `<div class='divider'><h1>${ title }</h1></div>`
let html = ''
html += lychee.html`<div class='divider'><h1>$${ title }</h1></div>`
return html
}
build.editIcon = function(id) {
return `<div id='${ id }' class='edit'>${ build.iconic('pencil') }</div>`
let html = ''
html += lychee.html`<div id='$${ id }' class='edit'>${ build.iconic('pencil') }</div>`
return html
}
build.multiselect = function(top, left) {
return `<div id='multiselect' style='top: ${ top }px; left: ${ left }px;'></div>`
return lychee.html`<div id='multiselect' style='top: $${ top }px; left: $${ left }px;'></div>`
}
build.album = function(data) {
if (data==null) return ''
let html = ''
let { path: thumbPath, hasRetina: thumbRetina } = lychee.retinize(data.thumbs[0])
let html = `
<div class='album' data-id='${ data.id }'>
<img src='${ data.thumbs[2] }' width='200' height='200' alt='thumb' data-overlay='false'>
<img src='${ data.thumbs[1] }' width='200' height='200' alt='thumb' data-overlay='false'>
<img src='${ thumbPath }' width='200' height='200' alt='thumb' data-overlay='${ thumbRetina }'>
html += lychee.html`
<div class='album' data-id='$${ data.id }'>
<img src='$${ data.thumbs[2] }' width='200' height='200' alt='thumb' data-overlay='false'>
<img src='$${ data.thumbs[1] }' width='200' height='200' alt='thumb' data-overlay='false'>
<img src='$${ thumbPath }' width='200' height='200' alt='thumb' data-overlay='$${ thumbRetina }'>
<div class='overlay'>
<h1 title='${ data.title }'>${ data.title }</h1>
<a>${ data.sysdate }</a>
<h1 title='$${ data.title }'>$${ data.title }</h1>
<a>$${ data.sysdate }</a>
</div>
`
if (lychee.publicMode===false) {
html += `
html += lychee.html`
<div class='badges'>
<a class='badge ${ (data.star==='1' ? 'badge--visible' : '') } icn-star'>${ build.iconic('star') }</a>
<a class='badge ${ (data.public==='1' ? 'badge--visible' : '') } icn-share'>${ build.iconic('eye') }</a>
<a class='badge ${ (data.unsorted==='1' ? 'badge--visible' : '') }'>${ build.iconic('list') }</a>
<a class='badge ${ (data.recent==='1' ? 'badge--visible' : '') }'>${ build.iconic('clock') }</a>
<a class='badge ${ (data.password==='1' ? 'badge--visible' : '') }'>${ build.iconic('lock-locked') }</a>
<a class='badge $${ (data.star==='1' ? 'badge--visible' : '') } icn-star'>${ build.iconic('star') }</a>
<a class='badge $${ (data.public==='1' ? 'badge--visible' : '') } icn-share'>${ build.iconic('eye') }</a>
<a class='badge $${ (data.unsorted==='1' ? 'badge--visible' : '') }'>${ build.iconic('list') }</a>
<a class='badge $${ (data.recent==='1' ? 'badge--visible' : '') }'>${ build.iconic('clock') }</a>
<a class='badge $${ (data.password==='1' ? 'badge--visible' : '') }'>${ build.iconic('lock-locked') }</a>
</div>
`
@ -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 = `
<div class='photo' data-album-id='${ data.album }' data-id='${ data.id }'>
<img src='${ thumbPath }' width='200' height='200' alt='thumb'>
html += lychee.html`
<div class='photo' data-album-id='$${ data.album }' data-id='$${ data.id }'>
<img src='$${ thumbPath }' width='200' height='200' alt='thumb'>
<div class='overlay'>
<h1 title='${ data.title }'>${ data.title }</h1>
<h1 title='$${ data.title }'>$${ data.title }</h1>
`
if (data.cameraDate==='1') html += `<a><span title='Camera Date'>${ build.iconic('camera-slr') }</span>${ data.sysdate }</a>`
else html += `<a>${ data.sysdate }</a>`
if (data.cameraDate==='1') html += lychee.html`<a><span title='Camera Date'>${ build.iconic('camera-slr') }</span>$${ data.sysdate }</a>`
else html += lychee.html`<a>$${ data.sysdate }</a>`
html += '</div>'
html += `</div>`
if (lychee.publicMode===false) {
html += `
html += lychee.html`
<div class='badges'>
<a class='badge ${ (data.star==='1' ? 'badge--visible' : '') } icn-star'>${ build.iconic('star') }</a>
<a class='badge ${ ((data.public==='1' && album.json.public!=='1') ? 'badge--visible' : '') } icn-share'>${ build.iconic('eye') }</a>
<a class='badge $${ (data.star==='1' ? 'badge--visible' : '') } icn-star'>${ build.iconic('star') }</a>
<a class='badge $${ ((data.public==='1' && album.json.public!=='1') ? 'badge--visible' : '') } icn-share'>${ build.iconic('eye') }</a>
</div>
`
}
html += '</div>'
html += `</div>`
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 += `<div id='image' style='background-image: url(${ data.url })'></div>`
else html += `<div id='image' style='background-image: url(${ data.url });' class='full'></div>`
if (visibleControls===true) html += lychee.html`<div id='image' style='background-image: url($${ data.url })'></div>`
else html += lychee.html`<div id='image' style='background-image: url($${ data.url });' class='full'></div>`
} else if (size==='medium') {
if (visibleControls===true) html += `<div id='image' style='background-image: url(${ data.medium })'></div>`
else html += `<div id='image' style='background-image: url(${ data.medium });' class='full'></div>`
if (visibleControls===true) html += lychee.html`<div id='image' style='background-image: url($${ data.medium })'></div>`
else html += lychee.html`<div id='image' style='background-image: url($${ data.medium });' class='full'></div>`
} else if (size==='small') {
if (visibleControls===true) html += `<div id='image' class='small' style='background-image: url(${ data.url }); width: ${ data.width }px; height: ${ data.height }px; margin-top: -${ parseInt(data.height/2-20) }px; margin-left: -${ data.width/2 }px;'></div>`
else html += `<div id='image' class='small' style='background-image: url(${ data.url }); width: ${ data.width }px; height: ${ data.height }px; margin-top: -${ parseInt(data.height/2) }px; margin-left: -${ data.width/2 }px;'></div>`
if (visibleControls===true) html += lychee.html`<div id='image' class='small' style='background-image: url($${ data.url }); width: $${ data.width }px; height: $${ data.height }px; margin-top: -$${ parseInt(data.height/2-20) }px; margin-left: -$${ data.width/2 }px;'></div>`
else html += lychee.html`<div id='image' class='small' style='background-image: url($${ data.url }); width: $${ data.width }px; height: $${ data.height }px; margin-top: -$${ parseInt(data.height/2) }px; margin-left: -$${ data.width/2 }px;'></div>`
}
@ -135,27 +145,29 @@ build.imageview = function(data, size, visibleControls) {
build.no_content = function(typ) {
let html = `
let html = ''
html += `
<div class='no_content fadeIn'>
${ build.iconic(typ) }
`
switch (typ) {
case 'magnifying-glass':
html += '<p>No results</p>'
html += `<p>No results</p>`
break
case 'eye':
html += '<p>No public albums</p>'
html += `<p>No public albums</p>`
break
case 'cog':
html += '<p>No configuration</p>'
html += `<p>No configuration</p>`
break
case 'question-mark':
html += '<p>Photo not found</p>'
html += `<p>Photo not found</p>`
break
}
html += '</div>'
html += `</div>`
return html
@ -163,8 +175,10 @@ build.no_content = function(typ) {
build.uploadModal = function(title, files) {
let html = `
<h1>${ title }</h1>
let html = ''
html += lychee.html`
<h1>$${ title }</h1>
<div class='rows'>
`
@ -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`
<div class='row'>
<a class='name'>${ lychee.escapeHTML(file.name) }</a>
<a class='name'>$${ file.name }</a>
`
if (file.supported===true) html += `<a class='status'></a>`
@ -193,7 +207,7 @@ build.uploadModal = function(title, files) {
}
html += '</div>'
html += `</div>`
return html
@ -208,7 +222,7 @@ build.tags = function(tags) {
tags = tags.split(',')
tags.forEach(function(tag, index, array) {
html += `<a class='tag'>${ tag }<span data-index='${ index }'>${ build.iconic('x') }</span></a>`
html += lychee.html`<a class='tag'>$${ tag }<span data-index='$${ index }'>${ build.iconic('x') }</span></a>`
})
} else {

View File

@ -101,7 +101,7 @@ contextMenu.albumTitle = function(albumID, e) {
if (!this.thumbs[0]) this.thumbs[0] = 'src/images/no_cover.svg'
let title = `<img class='cover' width='16' height='16' src='${ this.thumbs[0] }'><div class='title'>${ this.title }</div>`
let title = lychee.html`<img class='cover' width='16' height='16' src='$${ this.thumbs[0] }'><div class='title'>$${ this.title }</div>`
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 = `<img class='cover' width='16' height='16' src='${ this.thumbs[0] }'><div class='title'>${ this.title }</div>`
let title = lychee.html`<img class='cover' width='16' height='16' src='$${ this.thumbs[0] }'><div class='title'>$${ this.title }</div>`
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 = `<img class='cover' width='16' height='16' src='${ this.thumbUrl }'><div class='title'>${ this.title }</div>`
let title = lychee.html`<img class='cover' width='16' height='16' src='$${ this.thumbUrl }'><div class='title'>$${ this.title }</div>`
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 = `<img class='cover' width='16' height='16' src='${ this.thumbs[0] }'><div class='title'>${ this.title }</div>`
let title = lychee.html`<img class='cover' width='16' height='16' src='$${ this.thumbs[0] }'><div class='title'>$${ this.title }</div>`
if (this.id!=album.getID()) items.push({ type: 'item', title, fn: () => photo.setAlbum(photoIDs, this.id) })

View File

@ -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

View File

@ -119,12 +119,12 @@ lychee.login = function(data) {
lychee.loginDialog = function() {
let msg = `
let msg = lychee.html`
<p class='signIn'>
<input class='text' name='username' autocomplete='username' type='text' value='' placeholder='username' autocapitalize='off' autocorrect='off'>
<input class='text' name='password' autocomplete='current-password' type='password' value='' placeholder='password'>
</p>
<p class='version'>Lychee ${ lychee.version }<span> &#8211; <a target='_blank' href='${ lychee.updateURL }'>Update available!</a><span></p>
<p class='version'>Lychee $${ lychee.version }<span> &#8211; <a target='_blank' href='$${ lychee.updateURL }'>Update available!</a><span></p>
`
basicModal.show({
@ -387,11 +387,46 @@ lychee.escapeHTML = function(html = '') {
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;')
.replace(/`/g, '&#96;')
return html
}
lychee.html = function(literalSections, ...substs) {
// Use raw literal sections: we dont 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({

View File

@ -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 = `

View File

@ -244,14 +244,14 @@ photo.delete = function(photoIDs) {
action.title = 'Delete Photo'
cancel.title = 'Keep Photo'
msg = `<p>Are you sure you want to delete the photo '${ photoTitle }'? This action can't be undone!</p>`
msg = lychee.html`<p>Are you sure you want to delete the photo '$${ photoTitle }'? This action can't be undone!</p>`
} else {
action.title = 'Delete Photo'
cancel.title = 'Keep Photo'
msg = `<p>Are you sure you want to delete all ${ photoIDs.length } selected photo? This action can't be undone!</p>`
msg = lychee.html`<p>Are you sure you want to delete all $${ photoIDs.length } selected photo? This action can't be undone!</p>`
}
@ -317,10 +317,10 @@ photo.setTitle = function(photoIDs) {
}
let input = `<input class='text' name='title' type='text' maxlength='50' placeholder='Title' value='${ lychee.escapeHTML(oldTitle) }'>`
let input = lychee.html`<input class='text' name='title' type='text' maxlength='50' placeholder='Title' value='$${ oldTitle }'>`
if (photoIDs.length===1) msg = `<p>Enter a new title for this photo: ${ input }</p>`
else msg = `<p>Enter a title for all ${ photoIDs.length } selected photos: ${ input }</p>`
if (photoIDs.length===1) msg = lychee.html`<p>Enter a new title for this photo: ${ input }</p>`
else msg = lychee.html`<p>Enter a title for all $${ photoIDs.length } selected photos: ${ input }</p>`
basicModal.show({
body: msg,
@ -488,7 +488,7 @@ photo.setDescription = function(photoID) {
}
basicModal.show({
body: `<p>Enter a description for this photo: <input class='text' name='description' type='text' maxlength='800' placeholder='Description' value='${ lychee.escapeHTML(oldDescription) }'></p>`,
body: lychee.html`<p>Enter a description for this photo: <input class='text' name='description' type='text' maxlength='800' placeholder='Description' value='$${ oldDescription }'></p>`,
buttons: {
action: {
title: 'Set Description',
@ -534,10 +534,10 @@ photo.editTags = function(photoIDs) {
}
let input = `<input class='text' name='tags' type='text' maxlength='800' placeholder='Tags' value='${ oldTags }'>`
let input = lychee.html`<input class='text' name='tags' type='text' maxlength='800' placeholder='Tags' value='$${ oldTags }'>`
if (photoIDs.length===1) msg = `<p>Enter your tags for this photo. You can add multiple tags by separating them with a comma: ${ input }</p>`
else msg = `<p>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 }</p>`
if (photoIDs.length===1) msg = lychee.html`<p>Enter your tags for this photo. You can add multiple tags by separating them with a comma: ${ input }</p>`
else msg = lychee.html`<p>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 }</p>`
basicModal.show({
body: msg,

View File

@ -404,10 +404,10 @@ settings.setDropboxKey = function(callback) {
}
let msg = `
let msg = lychee.html`
<p>
In order to import photos from your Dropbox, you need a valid drop-ins app key from <a href='https://www.dropbox.com/developers/apps/create'>their website</a>. Generate yourself a personal key and enter it below:
<input class='text' name='key' type='text' placeholder='Dropbox API Key' value='${ lychee.dropboxKey }'>
<input class='text' name='key' type='text' placeholder='Dropbox API Key' value='$${ lychee.dropboxKey }'>
</p>
`

View File

@ -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 = `<span class='attr_${ row.title.toLowerCase() }'>${ value }</span>`
value = lychee.html`<span class='attr_$${ row.title.toLowerCase() }'>$${ value }</span>`
// Add edit-icon to the value when editable
if (row.editable===true) value += ' ' + build.editIcon('edit_' + row.title.toLowerCase())
_html += `
_html += lychee.html`
<tr>
<td>${ row.title }</td>
<td>$${ row.title }</td>
<td>${ value }</td>
</tr>
`
@ -363,20 +367,19 @@ sidebar.render = function(structure) {
let renderTags = function(section) {
let _html = ''
_html += `
<div class='divider'>
<h1>${ section.title }</h1>
</div>
<div id='tags'>
<div class='attr_${ section.title.toLowerCase() }'>${ section.value }</div>
`
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`
<div class='divider'>
<h1>$${ section.title }</h1>
</div>
<div id='tags'>
<div class='attr_$${ section.title.toLowerCase() }'>${ section.value }</div>
${ editable }
</div>
`

View File

@ -338,7 +338,7 @@ upload.start = {
}
basicModal.show({
body: `<p>Please enter the direct link to a photo to import it: <input class='text' name='link' type='text' placeholder='http://' value='${ url }'></p>`,
body: lychee.html`<p>Please enter the direct link to a photo to import it: <input class='text' name='link' type='text' placeholder='http://' value='$${ url }'></p>`,
buttons: {
action: {
title: 'Import',
@ -444,7 +444,7 @@ upload.start = {
}
basicModal.show({
body: `<p>This action will import all photos, folders and sub-folders which are located in the following directory. The <b>original files will be deleted</b> after the import when possible. <input class='text' name='path' type='text' maxlength='100' placeholder='Absolute path to directory' value='${ lychee.location }uploads/import/'></p>`,
body: lychee.html`<p>This action will import all photos, folders and sub-folders which are located in the following directory. The <b>original files will be deleted</b> after the import when possible. <input class='text' name='path' type='text' maxlength='100' placeholder='Absolute path to directory' value='$${ lychee.location }uploads/import/'></p>`,
buttons: {
action: {
title: 'Import',

View File

@ -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 }")`)
}

View File

@ -3,18 +3,74 @@
* @copyright 2015 by Tobias Reich
*/
let lychee = {
content: $('#content'),
getEventName() {
// Sub-implementation of Lychee -------------------------------------------------------------- //
let lychee = {}
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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;')
.replace(/`/g, '&#96;')
return html
}
lychee.html = function(literalSections, ...substs) {
// Use raw literal sections: we dont 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)