[authentication] URL protection for derivative images/item, not only for original image #261

Open
opened 2026-10-04 23:49:28 +02:00 by adam · 7 comments
Owner

Reported by benhup on 27 Jun 2014 15:36

Version: 2.6.2

URLS from original images/files are protected when enabling in /piwigo/local/config/config.inc.php:
$conf['original_url_protection'] = 'images';

Derivative/resized images are NOT URL protected.
When a user logs out of Piwigo, the URLs to display images when a user was logged in, are still accissible. This is due to the fact that an URL is used that directly point to the image itself.
The situation is insecure, allowing anyone to access images, without being logged in.

Steps to reproduce: 1. use Piwigo 2.6.3 (or any version tha is 2.4+)
2. Place images/files in /piwigo/galleries/
3. Log in to Piwigo
4. Synchronize and allow Piwigo to create an album out of the files in /piwigo/galleries/
5. open an album or an indiviual image
6. Open the source or page info from the webpage.
7. The plain URLs are shown, pointing directly to the image location.

This counts for all sizes/all derivatives.

Additional information: First efforts are made under:
http://piwigo.org/forum/viewtopic.php?id=24077
and
http://piwigo.org/forum/viewtopic.php?id=24090

Piwigo Bugtracker #3096

**Reported by benhup on 27 Jun 2014 15:36** **Version:** 2.6.2 URLS from original images/files are protected when enabling in /piwigo/local/config/config.inc.php: $conf[&#039;original_url_protection&#039;] = &#039;images&#039;; Derivative/resized images are _NOT_ URL protected. When a user logs out of Piwigo, the URLs to display images when a user was logged in, are still accissible. This is due to the fact that an URL is used that directly point to the image itself. The situation is insecure, allowing anyone to access images, without being logged in. **Steps to reproduce:** 1. use Piwigo 2.6.3 (or any version tha is 2.4+) 2. Place images/files in /piwigo/galleries/ 3. Log in to Piwigo 4. Synchronize and allow Piwigo to create an album out of the files in /piwigo/galleries/ 5. open an album or an indiviual image 6. Open the source or page info from the webpage. 7. The plain URLs are shown, pointing directly to the image location. This counts for all sizes/all derivatives. **Additional information:** First efforts are made under: http://piwigo.org/forum/viewtopic.php?id=24077 and http://piwigo.org/forum/viewtopic.php?id=24090 [Piwigo Bugtracker #3096](http://piwigo.org/bugs/view.php?id=3096) <!-- github-sync-id:97226815 -->
Author
Owner

Comment posted by benhup on 28 Jun 2014 11:05

--- D:/piwigo-2.6.3/piwigo/action.php Sun Jan 05 01:19:26 2014
+++ D:/piwigo-2.6.3/piwigo/benchanges/action.php Sat Jun 28 12:52:30 2014
@@ -61,7 +61,7 @@
if (!isset($_GET['id'])
or !is_numeric($_GET['id'])
or !isset($_GET['part'])

  • or !in_array($_GET['part'], array('e','r') ) )
  • or !in_array($_GET['part'], array('e','r','sq','th','2s','xs','sm','me','la','xl','xx') ) )
    {
    do_error(400, 'Invalid request - id/part');
    }
    @@ -100,6 +100,7 @@

include_once(PHPWG_ROOT_PATH.'include/functions_picture.inc.php');
$file='';
+$part = $_GET['part'];
switch ($_GET['part'])
{
case 'e':
@@ -116,6 +117,29 @@
case 'r':
$file = original_to_representative( get_element_path($element_info), $element_info['representative_ext'] );
break;

  • case 'sq':
  • $deriv = new DerivativeImage(IMG_SQUARE, new SrcImage($element_info));
  • case 'th':
  • $deriv = new DerivativeImage(IMG_THUMB, new SrcImage($element_info));
  • case '2s':
  • $deriv = new DerivativeImage(IMG_XXSMALL, new SrcImage($element_info));
  • case 'xs':
  • $deriv = new DerivativeImage(IMG_XSMALL, new SrcImage($element_info));
  • case 'sm':
  • $deriv = new DerivativeImage(IMG_SMALL, new SrcImage($element_info));
  • case 'me':
  • $deriv = new DerivativeImage(IMG_MEDIUM, new SrcImage($element_info));
  • case 'la':
  • $deriv = new DerivativeImage(IMG_LARGE, new SrcImage($element_info));
  • case 'xl':
  • $deriv = new DerivativeImage(IMG_XLARGE, new SrcImage($element_info));
  • case 'xx':
  • $deriv = new DerivativeImage(IMG_XXLARGE, new SrcImage($element_info));
  • $file = get_element_path($element_info);
  • $file = './_data/i/'.$file;
  • $picture_exts = implode('|',$conf['picture_ext']);
  • $file = preg_replace('/.('.$picture_exts.')$/i','-'.$part.'.${1}',$file);
  • break;
    }

if ( empty($file) )

**Comment posted by benhup on 28 Jun 2014 11:05** --- D:/piwigo-2.6.3/piwigo/action.php Sun Jan 05 01:19:26 2014 +++ D:/piwigo-2.6.3/piwigo/benchanges/action.php Sat Jun 28 12:52:30 2014 @@ -61,7 +61,7 @@ if (!isset($_GET[&#039;id&#039;]) or !is_numeric($_GET[&#039;id&#039;]) or !isset($_GET[&#039;part&#039;]) - or !in_array($_GET[&#039;part&#039;], array(&#039;e&#039;,&#039;r&#039;) ) ) - or !in_array($_GET[&#039;part&#039;], array(&#039;e&#039;,&#039;r&#039;,&#039;sq&#039;,&#039;th&#039;,&#039;2s&#039;,&#039;xs&#039;,&#039;sm&#039;,&#039;me&#039;,&#039;la&#039;,&#039;xl&#039;,&#039;xx&#039;) ) ) { do_error(400, &#039;Invalid request - id/part&#039;); } @@ -100,6 +100,7 @@ include_once(PHPWG_ROOT_PATH.&#039;include/functions_picture.inc.php&#039;); $file=&#039;&#039;; +$part = $_GET[&#039;part&#039;]; switch ($_GET[&#039;part&#039;]) { case &#039;e&#039;: @@ -116,6 +117,29 @@ case &#039;r&#039;: $file = original_to_representative( get_element_path($element_info), $element_info[&#039;representative_ext&#039;] ); break; - case &#039;sq&#039;: - $deriv = new DerivativeImage(IMG_SQUARE, new SrcImage($element_info)); - case &#039;th&#039;: - $deriv = new DerivativeImage(IMG_THUMB, new SrcImage($element_info)); - case &#039;2s&#039;: - $deriv = new DerivativeImage(IMG_XXSMALL, new SrcImage($element_info)); - case &#039;xs&#039;: - $deriv = new DerivativeImage(IMG_XSMALL, new SrcImage($element_info)); - case &#039;sm&#039;: - $deriv = new DerivativeImage(IMG_SMALL, new SrcImage($element_info)); - case &#039;me&#039;: - $deriv = new DerivativeImage(IMG_MEDIUM, new SrcImage($element_info)); - case &#039;la&#039;: - $deriv = new DerivativeImage(IMG_LARGE, new SrcImage($element_info)); - case &#039;xl&#039;: - $deriv = new DerivativeImage(IMG_XLARGE, new SrcImage($element_info)); - case &#039;xx&#039;: - $deriv = new DerivativeImage(IMG_XXLARGE, new SrcImage($element_info)); - $file = get_element_path($element_info); - $file = &#039;./_data/i/&#039;.$file; - $picture_exts = implode(&#039;|&#039;,$conf[&#039;picture_ext&#039;]); - $file = preg_replace(&#039;/.(&#039;.$picture_exts.&#039;)$/i&#039;,&#039;-&#039;.$part.&#039;.${1}&#039;,$file); - break; } if ( empty($file) ) <!-- github-comment-id:124893397 -->
Author
Owner

Comment posted by benhup on 28 Jun 2014 11:05

--- D:/piwigo-2.6.3/piwigo/include/derivative.inc.php Sun Jan 05 01:19:26 2014
+++ D:/piwigo-2.6.3/piwigo/benchanges/derivative.inc.php Sat Jun 28 13:04:02 2014
@@ -383,7 +383,14 @@
}
else
{

  •  $rel_url = $rel_path;
    
  •   if( !empty($conf[&#039;original_url_protection&#039;]) )
    
  •   {
    
  •       $rel_url = get_action_url($src-&gt;id, substr($params-&gt;type,0,2), false);
    
  •   }
    
  •   else
    
  •   {
    
  •     $rel_url = $rel_path;
    
  •   }
    
    }
    }
**Comment posted by benhup on 28 Jun 2014 11:05** --- D:/piwigo-2.6.3/piwigo/include/derivative.inc.php Sun Jan 05 01:19:26 2014 +++ D:/piwigo-2.6.3/piwigo/benchanges/derivative.inc.php Sat Jun 28 13:04:02 2014 @@ -383,7 +383,14 @@ } else { - $rel_url = $rel_path; - if( !empty($conf[&#039;original_url_protection&#039;]) ) - { - $rel_url = get_action_url($src-&gt;id, substr($params-&gt;type,0,2), false); - } - else - { - $rel_url = $rel_path; - } } } <!-- github-comment-id:124893404 -->
Author
Owner

Comment posted by benhup on 28 Jun 2014 11:06

--- D:/piwigo-2.6.3/piwigo/i.php Sat Jun 28 12:57:57 2014
+++ D:/piwigo-2.6.3/piwigo/benchanges/i.php Sat Jun 28 12:59:21 2014
@@ -349,7 +349,7 @@
return false;
}

-function send_derivative($expires)
+function send_derivative($expires, $id = null)
{
global $page;

@@ -358,7 +358,14 @@
include_once(PHPWG_ROOT_PATH.'include/functions_cookie.inc.php');
include_once(PHPWG_ROOT_PATH.'include/functions_url.inc.php');

  • echo json_encode( array( 'url'=>embellish_url(get_absolute_root_url().$page['derivative_path']) ) );
  • if( !empty($conf['original_url_protection']) )
  • {
  •   echo str_replace(&#039;&amp;&#039;, &#039;&amp;&#039;, json_encode( array( &#039;url&#039;=&gt;embellish_url(get_action_url($id, substr($page[&#039;derivative_type&#039;],0,2), false)) ) ) );
    
  • }
  • else
  • {
  •   echo json_encode( array( &#039;url&#039;=&gt;embellish_url(get_absolute_root_url().$page[&#039;derivative_path&#039;]) ) );
    
  • }
    return;
    }
    $fp = fopen($page['derivative_path'], 'rb');
    @@ -446,6 +453,11 @@

if (!$need_generate)
{

  • if( !empty($conf['original_url_protection']) )
  • {// if original_url_protection is enabled, only file creation and first-time retrieval is allowed; after-first-time: access denied
  • //echo "Access denied...";
  • //exit;
  • }
    if ( isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] )
    and strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $derivative_mtime)
    {// send the last mod time of the file back
    @@ -457,6 +469,8 @@
    exit;
    }

+$id = '';
+
include_once(PHPWG_ROOT_PATH . 'admin/include/image.class.php');
$page['coi'] = null;
if (strpos($page['src_location'], '/pwg_representative/')===false
@@ -473,6 +487,7 @@

 if ( ($row=pwg_db_fetch_assoc(pwg_query($query))) )
 {
  • $id = $row[&#039;id&#039;];
    
    if (isset($row['width']))
    {
    $page['original_size'] = array($row['width'],$row['height']);
    @@ -618,7 +633,7 @@
    @chmod($page['derivative_path'], 0644);
    $timing['save'] = time_step($step);

-send_derivative($expires);
+send_derivative($expires,$id);
$timing['send'] = time_step($step);

ilog('perf',

**Comment posted by benhup on 28 Jun 2014 11:06** --- D:/piwigo-2.6.3/piwigo/i.php Sat Jun 28 12:57:57 2014 +++ D:/piwigo-2.6.3/piwigo/benchanges/i.php Sat Jun 28 12:59:21 2014 @@ -349,7 +349,7 @@ return false; } -function send_derivative($expires) +function send_derivative($expires, $id = null) { global $page; @@ -358,7 +358,14 @@ include_once(PHPWG_ROOT_PATH.&#039;include/functions_cookie.inc.php&#039;); include_once(PHPWG_ROOT_PATH.&#039;include/functions_url.inc.php&#039;); - echo json_encode( array( &#039;url&#039;=&gt;embellish_url(get_absolute_root_url().$page[&#039;derivative_path&#039;]) ) ); - if( !empty($conf[&#039;original_url_protection&#039;]) ) - { - echo str_replace(&#039;&amp;&#039;, &#039;&amp;&#039;, json_encode( array( &#039;url&#039;=&gt;embellish_url(get_action_url($id, substr($page[&#039;derivative_type&#039;],0,2), false)) ) ) ); - } - else - { - echo json_encode( array( &#039;url&#039;=&gt;embellish_url(get_absolute_root_url().$page[&#039;derivative_path&#039;]) ) ); - } return; } $fp = fopen($page[&#039;derivative_path&#039;], &#039;rb&#039;); @@ -446,6 +453,11 @@ if (!$need_generate) { - if( !empty($conf[&#039;original_url_protection&#039;]) ) - {// if original_url_protection is enabled, only file creation and first-time retrieval is allowed; after-first-time: access denied - //echo &quot;Access denied...&quot;; - //exit; - } if ( isset( $_SERVER[&#039;HTTP_IF_MODIFIED_SINCE&#039;] ) and strtotime($_SERVER[&#039;HTTP_IF_MODIFIED_SINCE&#039;]) == $derivative_mtime) {// send the last mod time of the file back @@ -457,6 +469,8 @@ exit; } +$id = &#039;&#039;; + include_once(PHPWG_ROOT_PATH . &#039;admin/include/image.class.php&#039;); $page[&#039;coi&#039;] = null; if (strpos($page[&#039;src_location&#039;], &#039;/pwg_representative/&#039;)===false @@ -473,6 +487,7 @@ ``` if ( ($row=pwg_db_fetch_assoc(pwg_query($query))) ) { ``` - $id = $row[&#039;id&#039;]; if (isset($row[&#039;width&#039;])) { $page[&#039;original_size&#039;] = array($row[&#039;width&#039;],$row[&#039;height&#039;]); @@ -618,7 +633,7 @@ @chmod($page[&#039;derivative_path&#039;], 0644); $timing[&#039;save&#039;] = time_step($step); -send_derivative($expires); +send_derivative($expires,$id); $timing[&#039;send&#039;] = time_step($step); ilog(&#039;perf&#039;, <!-- github-comment-id:124893417 -->
Author
Owner

Comment posted by tho on 4 Sep 2014 20:59

Hi,

I came up with a different implementation, using the 'derivative_url_style == 2' configuration (script). I only did a few quick tests atm, and there are a few glitches to fix, but I wanted to get your feedback first on the global approach.

The idea is to pass a session variable to the i.php script, that encodes the path to the derivative file and that is set by the top level page generating the link. This way, a user can only see what the top level page has generated for him. Of course, to be effective, the _data/i directory also has to be protected by a 'deny all' apache directive (.htaccess or httpd.conf).

Here is the diff. What do you think?

diff --git i.php i.php
index 8ca3b68..5771eb6 100644
--- i.php
+++ i.php
@@ -201,6 +201,11 @@ function parse_request()

$req = ltrim($req, '/');

  • $req = pwg_get_session_var($req);
  • if ($req == null) {
  • ierror(&#039;Not found&#039;, 404);
    
  • }

foreach (preg_split('#/+#', $req) as $token)
{
preg_match($conf['sync_chars_regex'], $token) or ierror('Invalid chars in r
@@ -393,6 +398,9 @@ foreach( explode(',','load,rotate,crop,scale,sharpen,waterma
$timing[$k] = '';
}

+include_once(PHPWG_ROOT_PATH .'/include/constants.php');
+include_once(PHPWG_ROOT_PATH .'/include/functions_cookie.inc.php');
+include_once(PHPWG_ROOT_PATH .'/include/functions_session.inc.php');
include_once(PHPWG_ROOT_PATH .'include/dblayer/functions_'.$conf['dblayer'].'.i
include_once( PHPWG_ROOT_PATH .'/include/derivative_params.inc.php');
include_once( PHPWG_ROOT_PATH .'/include/derivative_std_params.inc.php');
@@ -411,7 +419,7 @@ pwg_db_check_charset();
list($conf['derivatives']) = pwg_db_fetch_row(pwg_query('SELECT value FROM '.$p
ImageStdParams::load_from_db();

+session_start();
parse_request();
//var_export($page);

diff --git include/derivative.inc.php include/derivative.inc.php
index 49fa9d0..511b79a 100644
--- include/derivative.inc.php
+++ include/derivative.inc.php
@@ -379,7 +379,10 @@ final class DerivativeImage
$rel_url = 'i';
if ($conf['php_extension_in_urls']) $rel_url .= '.php';
if ($conf['question_mark_in_urls']) $rel_url .= '?';

  •  $rel_url .= &#039;/&#039;.$loc;
    
  •  $key = base64_encode(md5($loc));
    
  •  pwg_set_session_var($key, $loc);
    
  •  $rel_url .= &#039;/&#039;.$key;
    
    }
    else
    {
**Comment posted by tho on 4 Sep 2014 20:59** Hi, I came up with a different implementation, using the &#039;derivative_url_style == 2&#039; configuration (script). I only did a few quick tests atm, and there are a few glitches to fix, but I wanted to get your feedback first on the global approach. The idea is to pass a session variable to the i.php script, that encodes the path to the derivative file and that is set by the top level page generating the link. This way, a user can only see what the top level page has generated for him. Of course, to be effective, the _data/i directory also has to be protected by a &#039;deny all&#039; apache directive (.htaccess or httpd.conf). Here is the diff. What do you think? diff --git i.php i.php index 8ca3b68..5771eb6 100644 --- i.php +++ i.php @@ -201,6 +201,11 @@ function parse_request() $req = ltrim($req, &#039;/&#039;); - $req = pwg_get_session_var($req); - if ($req == null) { - ierror(&#039;Not found&#039;, 404); - } + foreach (preg_split(&#039;#/+#&#039;, $req) as $token) { preg_match($conf[&#039;sync_chars_regex&#039;], $token) or ierror(&#039;Invalid chars in r @@ -393,6 +398,9 @@ foreach( explode(&#039;,&#039;,&#039;load,rotate,crop,scale,sharpen,waterma $timing[$k] = &#039;&#039;; } +include_once(PHPWG_ROOT_PATH .&#039;/include/constants.php&#039;); +include_once(PHPWG_ROOT_PATH .&#039;/include/functions_cookie.inc.php&#039;); +include_once(PHPWG_ROOT_PATH .&#039;/include/functions_session.inc.php&#039;); include_once(PHPWG_ROOT_PATH .&#039;include/dblayer/functions_&#039;.$conf[&#039;dblayer&#039;].&#039;.i include_once( PHPWG_ROOT_PATH .&#039;/include/derivative_params.inc.php&#039;); include_once( PHPWG_ROOT_PATH .&#039;/include/derivative_std_params.inc.php&#039;); @@ -411,7 +419,7 @@ pwg_db_check_charset(); list($conf[&#039;derivatives&#039;]) = pwg_db_fetch_row(pwg_query(&#039;SELECT value FROM &#039;.$p ImageStdParams::load_from_db(); ## +session_start(); parse_request(); //var_export($page); diff --git include/derivative.inc.php include/derivative.inc.php index 49fa9d0..511b79a 100644 --- include/derivative.inc.php +++ include/derivative.inc.php @@ -379,7 +379,10 @@ final class DerivativeImage $rel_url = &#039;i&#039;; if ($conf[&#039;php_extension_in_urls&#039;]) $rel_url .= &#039;.php&#039;; if ($conf[&#039;question_mark_in_urls&#039;]) $rel_url .= &#039;?&#039;; - $rel_url .= &#039;/&#039;.$loc; + - $key = base64_encode(md5($loc)); - pwg_set_session_var($key, $loc); - $rel_url .= &#039;/&#039;.$key; } else { <!-- github-comment-id:124893435 -->
Author
Owner

Comment posted by benhup on 16 Mar 2015 22:03

Hi Tho,
Unfortunately your e-mail is not shown in your profile.
Please contact me. I have tested your solution. The small amount of code is great. One thing that I also bumped into is that once the smaller size file is genereated, next access: a link that reveals the true image name and enables direct access again is used with i.php.
Example: first time access to image shows this URL: http://host/piwigo-2.7.4/i.php?/YWQ3MGQ1YjQyOTJmZGU1ZThhNmRhNWM0ODllMjk0NDY=
Now change folder and go back to the image, select the same size. The URL now shown is: http://host/piwigo-2.7.4/_data/i/galleries/ben1/image2-2s.jpg

Also tested: when logged out file is not accessible via URL anymore = good.
Used i.php URL is not valid after logging in again = could be a nagging problem if people want to share a link quickly. When person A copy/pastes a link via mail and sends the link to person B. Person B logs in and copy/pastes the link in browser. Current behaviour in your solution: access denied.

Great step, lets see if we can build on this. I'd like to hear from you. Best, Ben

**Comment posted by benhup on 16 Mar 2015 22:03** Hi Tho, Unfortunately your e-mail is not shown in your profile. Please contact me. I have tested your solution. The small amount of code is great. One thing that I also bumped into is that once the smaller size file is genereated, next access: a link that reveals the true image name and enables direct access again is used with i.php. Example: first time access to image shows this URL: http://host/piwigo-2.7.4/i.php?/YWQ3MGQ1YjQyOTJmZGU1ZThhNmRhNWM0ODllMjk0NDY= Now change folder and go back to the image, select the same size. The URL now shown is: http://host/piwigo-2.7.4/_data/i/galleries/ben1/image2-2s.jpg Also tested: when logged out file is not accessible via URL anymore = good. Used i.php URL is not valid after logging in again = could be a nagging problem if people want to share a link quickly. When person A copy/pastes a link via mail and sends the link to person B. Person B logs in and copy/pastes the link in browser. Current behaviour in your solution: access denied. Great step, lets see if we can build on this. I&#039;d like to hear from you. Best, Ben <!-- github-comment-id:124893453 -->
Author
Owner

Comment posted by benhup on 16 Mar 2015 22:42

Hi Tho,

Correction:

When /config/config.inc.php contains:

$conf['original_url_protection'] = 'images'; /* mask URL of original image */

$conf['derivative_url_style'] = 2; /* mask URL of resized images */

All appears to be well with preliminary testing.
I mean: after a resized image is generated at first time access, you leave the page and return to the image and select the same size. The URLs are again encrypted.

Logging out results in all files not being accessible = good.
Logging out and logging back in again results in:
Original file accessible again via URL e.g.: http://host/piwigo-2.7.4/action.php?id=5&part=e
Resized image URLs remain unusable, even after logging in .e.g.: http://host/piwigo-2.7.4/i.php?/ZmU5YzU4MTYxODQ4Nzc1NGJkYzRlMzUxMmEyNTBiYzE=
But: when you access the image via Piwigo and refresh the image with the base64_encoded URL in your open tab of your browser, it reappears again. This is because the $key van befound in the session again and thus the $loc.

By preliminary testing I also mean: I have not tested this by accessing large amounts of files and what happens when logged in for a long period of time. Also I have to think if any of these changes create security vulnerabilities.
Anyway, whats your reflection?

Best,
Ben

**Comment posted by benhup on 16 Mar 2015 22:42** Hi Tho, Correction: ## When /config/config.inc.php contains: $conf[&#039;original_url_protection&#039;] = &#039;images&#039;; /\* mask URL of original image */ ## $conf[&#039;derivative_url_style&#039;] = 2; /\* mask URL of resized images */ All appears to be well with preliminary testing. I mean: after a resized image is generated at first time access, you leave the page and return to the image and select the same size. The URLs are again encrypted. Logging out results in all files not being accessible = good. Logging out and logging back in again results in: Original file accessible again via URL e.g.: http://host/piwigo-2.7.4/action.php?id=5&amp;part=e Resized image URLs remain unusable, even after logging in .e.g.: http://host/piwigo-2.7.4/i.php?/ZmU5YzU4MTYxODQ4Nzc1NGJkYzRlMzUxMmEyNTBiYzE= But: when you access the image via Piwigo and refresh the image with the base64_encoded URL in your open tab of your browser, it reappears again. This is because the $key van befound in the session again and thus the $loc. By preliminary testing I also mean: I have not tested this by accessing large amounts of files and what happens when logged in for a long period of time. Also I have to think if any of these changes create security vulnerabilities. Anyway, whats your reflection? Best, Ben <!-- github-comment-id:124898908 -->
Author
Owner

The patch merged to current master is attached.
patch.diff.zip

The patch merged to current master is attached. [patch.diff.zip](https://github.com/Piwigo/Piwigo/files/1238880/patch.diff.zip) <!-- github-comment-id:323740845 -->
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: adam/Piwigo#261