Sfoglia il codice sorgente

Merge branch 'master' of https://github.com/mpetroff/pannellum into master-build

pull/952/head
strarsis 4 anni fa
parent
commit
54a8dc65f8
9 ha cambiato i file con 149 aggiunte e 50 eliminazioni
  1. +1
    -1
      VERSION
  2. +31
    -0
      changelog.md
  3. +2
    -1
      doc/json-config-parameters.md
  4. +1
    -1
      package.json
  5. BIN
      paper/paper.pdf
  6. +0
    -1
      src/css/pannellum.css
  7. +4
    -3
      src/js/libpannellum.js
  8. +108
    -43
      src/js/pannellum.js
  9. +2
    -0
      utils/multires/generate.py

+ 1
- 1
VERSION Vedi File

@@ -1 +1 @@
2.5.3
2.5.6

+ 31
- 0
changelog.md Vedi File

@@ -2,6 +2,37 @@ Changelog
=========


Changes in Pannellum 2.5.6 (2019-11-26)
---------------------------------------

Security fixes:
- Extended partial fix in v2.5.5 for XSS vulnerability that allowed script
execution when hot spots were clicked (CVE-2019-16763)


Changes in Pannellum 2.5.5 (2019-11-21)
---------------------------------------

Bugfixes:
- Fixed device orientation permission request such that it works with iOS 13
- Extend yaw bounds range to allow restricted range that crosses +/-180 deg

Security fixes:
- Fixed XSS vulnerability that allowed script execution when hot spots
were clicked (CVE-2019-16763)


Changes in Pannellum 2.5.4 (2019-09-10)
---------------------------------------

Bugfixes:
- Fixed issue with loading large equirectangular panoramas on iOS
- Fixed issue with touch-based scrolling with `draggable` set to `false`

Improvements:
- Started requesting device orientation permission (untested)


Changes in Pannellum 2.5.3 (2019-08-21)
---------------------------------------



+ 2
- 1
doc/json-config-parameters.md Vedi File

@@ -81,7 +81,8 @@ a link and visit this URL if Pannellum fails to work.

If set to `true`, device orientation control will be used when the panorama is
loaded, if the device supports it. If false, device orientation control needs
to be activated by pressing a button. Defaults to `false`.
to be activated by pressing a button. Defaults to `false`. Note that a secure
HTTPS connection is required for device orientation access in most browsers.


### `showZoomCtrl` (boolean)


+ 1
- 1
package.json Vedi File

@@ -1,7 +1,7 @@
{
"name": "pannellum",
"description": "Pannellum is a lightweight, free, and open source panorama viewer for the web.",
"version": "2.5.3",
"version": "2.5.6",
"bugs": {
"url": "https://github.com/mpetroff/pannellum/issues"
},


BIN
paper/paper.pdf Vedi File


+ 0
- 1
src/css/pannellum.css Vedi File

@@ -17,7 +17,6 @@
outline: 0;
line-height: 1.4;
contain: content;
touch-action: none;
}

.pnlm-container * {


+ 4
- 3
src/js/libpannellum.js Vedi File

@@ -435,7 +435,7 @@ function Renderer(container) {

// Draw image on canvas
var cropCanvas = document.createElement('canvas');
cropCanvas.width = image.width;
cropCanvas.width = image.width / 2;
cropCanvas.height = image.height;
var cropContext = cropCanvas.getContext('2d');
cropContext.drawImage(image, 0, 0);
@@ -451,7 +451,8 @@ function Renderer(container) {
gl.uniform1i(gl.getUniformLocation(program, 'u_image1'), 1);

// Upload second half of image to the texture
cropImage = cropContext.getImageData(image.width / 2, 0, image.width / 2, image.height);
cropContext.drawImage(image, -image.width / 2, 0);
cropImage = cropContext.getImageData(0, 0, image.width / 2, image.height);
gl.texImage2D(glBindType, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, cropImage);

// Set parameters for rendering any size
@@ -460,7 +461,7 @@ function Renderer(container) {
gl.texParameteri(glBindType, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(glBindType, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

// Reactive first texture unit
// Reactivate first texture unit
gl.activeTexture(gl.TEXTURE0);
}
}


+ 108
- 43
src/js/pannellum.js Vedi File

@@ -254,22 +254,15 @@ controls.orientation.addEventListener('mousedown', function(e) {e.stopPropagatio
controls.orientation.addEventListener('touchstart', function(e) {e.stopPropagation();});
controls.orientation.addEventListener('pointerdown', function(e) {e.stopPropagation();});
controls.orientation.className = 'pnlm-orientation-button pnlm-orientation-button-inactive pnlm-sprite pnlm-controls pnlm-control';
var orientationSupport, startOrientationIfSupported = false;
function deviceOrientationTest(e) {
window.removeEventListener('deviceorientation', deviceOrientationTest);
if (e && e.alpha !== null && e.beta !== null && e.gamma !== null) {
controls.container.appendChild(controls.orientation);
orientationSupport = true;
if (startOrientationIfSupported)
startOrientation();
} else {
orientationSupport = false;
}
}
if (window.DeviceOrientationEvent) {
window.addEventListener('deviceorientation', deviceOrientationTest);
} else {
orientationSupport = false;
var orientationSupport = false;
if (window.DeviceOrientationEvent && location.protocol == 'https:' &&
navigator.userAgent.toLowerCase().indexOf('mobi') >= 0) {
// This user agent check is here because there's no way to check if a
// device has an inertia measurement unit. We used to be able to check if a
// DeviceOrientationEvent had non-null values, but with iOS 13 requiring a
// permission prompt to access such events, this is no longer possible.
controls.container.appendChild(controls.orientation);
orientationSupport = true;
}

// Compass
@@ -402,7 +395,7 @@ function init() {
anError(config.strings.fileAccessError.replace('%s', a.outerHTML));
}
var img = this.response;
parseGPanoXMP(img);
parseGPanoXMP(img, p);
infoDisplay.load.msg.innerHTML = '';
};
xhr.onprogress = function(e) {
@@ -528,7 +521,7 @@ function onImageLoad() {
* @private
* @param {Image} image - Image to read XMP metadata from.
*/
function parseGPanoXMP(image) {
function parseGPanoXMP(image, url) {
var reader = new FileReader();
reader.addEventListener('loadend', function() {
var img = reader.result;
@@ -604,6 +597,35 @@ function parseGPanoXMP(image) {
// Load panorama
panoImage.src = window.URL.createObjectURL(image);
panoImage.onerror = function() {
// If the image fails to load, we check the Content Security Policy
// headers and see if they block loading images as blobs. If they
// do, we load the image directly from the URL. While this should
// allow the image to load, it does prevent parsing of XMP data.
function getCspHeaders() {
if (!window.fetch)
return null;
return window.fetch(document.location.href)
.then(function(resp){
return resp.headers.get('Content-Security-Policy');
});
}
getCspHeaders().then(function(cspHeaders) {
if (cspHeaders) {
var invalidImgSource = cspHeaders.split(";").find(function(p) {
var matchstring = p.match(/img-src(.*)/);
if (matchstring) {
return !matchstring[1].includes("blob");
}
});
if (invalidImgSource) {
console.log('CSP blocks blobs; reverting to URL.');
panoImage.crossOrigin = config.crossOrigin;
panoImage.src = url;
}
}
});
}
});
if (reader.readAsBinaryString !== undefined)
reader.readAsBinaryString(image);
@@ -933,6 +955,9 @@ var pointerIDs = [],
*/
function onDocumentPointerDown(event) {
if (event.pointerType == 'touch') {
// Only do something if the panorama is loaded
if (!loaded || !config.draggable)
return;
pointerIDs.push(event.pointerId);
pointerCoordinates.push({clientX: event.clientX, clientY: event.clientY});
event.targetTouches = pointerCoordinates;
@@ -948,6 +973,8 @@ function onDocumentPointerDown(event) {
*/
function onDocumentPointerMove(event) {
if (event.pointerType == 'touch') {
if (!config.draggable)
return;
for (var i = 0; i < pointerIDs.length; i++) {
if (event.pointerId == pointerIDs[i]) {
pointerCoordinates[i].clientX = event.clientX;
@@ -1416,9 +1443,9 @@ function render() {

if (config.autoRotate !== false) {
// When auto-rotating this check needs to happen first (see issue #764)
if (config.yaw > 180) {
if (config.yaw > 360) {
config.yaw -= 360;
} else if (config.yaw < -180) {
} else if (config.yaw < -360) {
config.yaw += 360;
}
}
@@ -1459,9 +1486,9 @@ function render() {
if (!(config.autoRotate !== false)) {
// When not auto-rotating, this check needs to happen after the
// previous check (see issue #698)
if (config.yaw > 180) {
if (config.yaw > 360) {
config.yaw -= 360;
} else if (config.yaw < -180) {
} else if (config.yaw < -360) {
config.yaw += 360;
}
}
@@ -1590,8 +1617,6 @@ function computeQuaternion(alpha, beta, gamma) {
* @param {DeviceOrientationEvent} event - Device orientation event.
*/
function orientationListener(e) {
if (e.hasOwnProperty('requestPermission'))
e.requestPermission()
var q = computeQuaternion(e.alpha, e.beta, e.gamma).toEulerAngles();
if (typeof(orientation) == 'number' && orientation < 10) {
// This kludge is necessary because iOS sometimes provides a few stale
@@ -1723,7 +1748,7 @@ function createHotSpot(hs) {
if (config.basePath && !absoluteURL(imgp))
imgp = config.basePath + imgp;
a = document.createElement('a');
a.href = sanitizeURL(hs.URL ? hs.URL : imgp);
a.href = sanitizeURL(hs.URL ? hs.URL : imgp, true);
a.target = '_blank';
span.appendChild(a);
var image = document.createElement('img');
@@ -1735,7 +1760,7 @@ function createHotSpot(hs) {
span.style.maxWidth = 'initial';
} else if (hs.URL) {
a = document.createElement('a');
a.href = sanitizeURL(hs.URL);
a.href = sanitizeURL(hs.URL, true);
if (hs.attributes) {
for (var key in hs.attributes) {
a.setAttribute(key, hs.attributes[key]);
@@ -2009,7 +2034,7 @@ function processOptions(isPreview) {
var authorText = escapeHTML(config[key]);
if (config.authorURL) {
var authorLink = document.createElement('a');
authorLink.href = sanitizeURL(config['authorURL']);
authorLink.href = sanitizeURL(config['authorURL'], true);
authorLink.target = '_blank';
authorLink.innerHTML = escapeHTML(config[key]);
authorText = authorLink.outerHTML;
@@ -2020,7 +2045,7 @@ function processOptions(isPreview) {
case 'fallback':
var link = document.createElement('a');
link.href = sanitizeURL(config[key]);
link.href = sanitizeURL(config[key], true);
link.target = '_blank';
link.textContent = 'Click here to view this panorama in an alternative viewer.';
var message = document.createElement('p');
@@ -2084,12 +2109,8 @@ function processOptions(isPreview) {
break;

case 'orientationOnByDefault':
if (config[key]) {
if (orientationSupport === undefined)
startOrientationIfSupported = true;
else if (orientationSupport === true)
startOrientation();
}
if (config[key])
startOrientation();
break;
}
}
@@ -2348,9 +2369,19 @@ function stopOrientation() {
* @private
*/
function startOrientation() {
orientation = 1;
window.addEventListener('deviceorientation', orientationListener);
controls.orientation.classList.add('pnlm-orientation-button-active');
if (typeof DeviceMotionEvent.requestPermission === 'function') {
DeviceOrientationEvent.requestPermission().then(function(response) {
if (response == 'granted') {
orientation = 1;
window.addEventListener('deviceorientation', orientationListener);
controls.orientation.classList.add('pnlm-orientation-button-active');
}
});
} else {
orientation = 1;
window.addEventListener('deviceorientation', orientationListener);
controls.orientation.classList.add('pnlm-orientation-button-active');
}
}

/**
@@ -2376,16 +2407,50 @@ function escapeHTML(s) {
* The URL cannot be of protocol 'javascript'.
* @private
* @param {string} url - URL to sanitize
* @param {boolean} href - True if URL is for link (blocks data URIs)
* @returns {string} Sanitized URL
*/
function sanitizeURL(url) {
if (url.trim().toLowerCase().indexOf('javascript:') === 0) {
function sanitizeURL(url, href) {
try {
var decoded_url = decodeURIComponent(unescape(url)).replace(/[^\w:]/g, '').toLowerCase();
} catch (e) {
return 'about:blank';
}
if (decoded_url.indexOf('javascript:') === 0 ||
decoded_url.indexOf('vbscript:') === 0) {
console.log('Script URL removed.');
return 'about:blank';
}
if (href && decoded_url.indexOf('data:') === 0) {
console.log('Data URI removed from link.');
return 'about:blank';
}
return url;
}

/**
* Unescapes HTML entities.
* Copied from Marked.js 0.7.0.
* @private
* @param {string} url - URL to sanitize
* @param {boolean} href - True if URL is for link (blocks data URIs)
* @returns {string} Sanitized URL
*/
function unescape(html) {
// Explicitly match decimal, hex, and named HTML entities
return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig, function(_, n) {
n = n.toLowerCase();
if (n === 'colon') return ':';
if (n.charAt(0) === '#') {
return n.charAt(1) === 'x'
? String.fromCharCode(parseInt(n.substring(2), 16))
: String.fromCharCode(+n.substring(1));
}
return '';
});
}

/**
* Removes possibility of XSS atacks with URLs for CSS.
* The URL will be sanitized with `sanitizeURL()` and single quotes
* and double quotes escaped.
@@ -2483,7 +2548,7 @@ this.setPitchBounds = function(bounds) {
* @returns {number} Yaw in degrees
*/
this.getYaw = function() {
return config.yaw;
return (config.yaw + 540) % 360 - 180;
};

/**
@@ -2538,15 +2603,15 @@ this.getYawBounds = function() {
};

/**
* Set the minimum and maximum allowed yaws (in degrees [-180, 180]).
* Set the minimum and maximum allowed yaws (in degrees [-360, 360]).
* @memberof Viewer
* @instance
* @param {number[]} bounds - [minimum yaw, maximum yaw]
* @returns {Viewer} `this`
*/
this.setYawBounds = function(bounds) {
config.minYaw = Math.max(-180, Math.min(bounds[0], 180));
config.maxYaw = Math.max(-180, Math.min(bounds[1], 180));
config.minYaw = Math.max(-360, Math.min(bounds[0], 360));
config.maxYaw = Math.max(-360, Math.min(bounds[1], 360));
return this;
};



+ 2
- 0
utils/multires/generate.py Vedi File

@@ -132,6 +132,8 @@ else:
cubeSize = 8 * int((360 / haov) * origWidth / math.pi / 8)
tileSize = min(args.tileSize, cubeSize)
levels = int(math.ceil(math.log(float(cubeSize) / tileSize, 2))) + 1
if round(cubeSize / 2**(levels - 2)) == tileSize:
levels -= 1 # Handle edge case
origHeight = str(origHeight)
origWidth = str(origWidth)
origFilename = os.path.join(os.getcwd(), args.inputFile)


Caricamento…
Annulla
Salva