mirror of
				https://gitlab.gnome.org/GNOME/glib.git
				synced 2025-10-31 08:22:16 +01:00 
			
		
		
		
	If only someone would go ahead and invent a whole section of the list of HTTP status codes which could be used to inform a client of where a document has been moved to. For the sake of argument, let’s say it could be status codes 300–399, since they appear to be completely unused at the moment. 😩 Signed-off-by: Philip Withnall <pwithnall@gnome.org>
		
			
				
	
	
		
			255 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			255 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* Copyright © 2013 Canonical Limited
 | |
|  *
 | |
|  * SPDX-License-Identifier: LGPL-2.1-or-later
 | |
|  *
 | |
|  * This library is free software; you can redistribute it and/or
 | |
|  * modify it under the terms of the GNU Lesser General Public
 | |
|  * License as published by the Free Software Foundation; either
 | |
|  * version 2.1 of the License, or (at your option) any later version.
 | |
|  *
 | |
|  * This library is distributed in the hope that it will be useful,
 | |
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | |
|  * Lesser General Public License for more details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU Lesser General
 | |
|  * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
 | |
|  *
 | |
|  * Author: Ryan Lortie <desrt@desrt.ca>
 | |
|  */
 | |
| 
 | |
| #include "config.h"
 | |
| 
 | |
| #include "thumbnail-verify.h"
 | |
| 
 | |
| #include <string.h>
 | |
| 
 | |
| /* Begin code to check the validity of thumbnail files.  In order to do
 | |
|  * that we need to parse enough PNG in order to get the Thumb::URI,
 | |
|  * Thumb::MTime and Thumb::Size tags out of the file.  Fortunately this
 | |
|  * is relatively easy.
 | |
|  */
 | |
| typedef struct
 | |
| {
 | |
|   const gchar *uri;
 | |
|   guint64      mtime;
 | |
|   guint64      size;
 | |
| } ExpectedInfo;
 | |
| 
 | |
| /* We *require* matches on URI and MTime, but the Size field is optional
 | |
|  * (as per the spec).
 | |
|  *
 | |
|  * https://specifications.freedesktop.org/thumbnail-spec/latest/
 | |
|  */
 | |
| #define MATCHED_URI    (1u << 0)
 | |
| #define MATCHED_MTIME  (1u << 1)
 | |
| #define MATCHED_ALL    (MATCHED_URI | MATCHED_MTIME)
 | |
| 
 | |
| static gboolean
 | |
| check_integer_match (guint64      expected,
 | |
|                      const gchar *value,
 | |
|                      guint32      value_size)
 | |
| {
 | |
|   /* Would be nice to g_ascii_strtoll here, but we don't have a variant
 | |
|    * that works on strings that are not nul-terminated.
 | |
|    *
 | |
|    * It's easy enough to do it ourselves...
 | |
|    */
 | |
|   if (expected == 0)  /* special case: "0" */
 | |
|     return value_size == 1 && value[0] == '0';
 | |
| 
 | |
|   /* Check each digit, as long as we have data from both */
 | |
|   while (expected && value_size)
 | |
|     {
 | |
|       /* Check the low-order digit */
 | |
|       if (value[value_size - 1] != (gchar) ((expected % 10) + '0'))
 | |
|         return FALSE;
 | |
| 
 | |
|       /* Move on... */
 | |
|       expected /= 10;
 | |
|       value_size--;
 | |
|     }
 | |
| 
 | |
|   /* Make sure nothing is left over, on either side */
 | |
|   return !expected && !value_size;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| check_png_info_chunk (ExpectedInfo *expected_info,
 | |
|                       const gchar  *key,
 | |
|                       guint32       key_size,
 | |
|                       const gchar  *value,
 | |
|                       guint32       value_size,
 | |
|                       guint        *required_matches)
 | |
| {
 | |
|   if (key_size == 10 && memcmp (key, "Thumb::URI", 10) == 0)
 | |
|     {
 | |
|       gsize expected_size;
 | |
| 
 | |
|       expected_size = strlen (expected_info->uri);
 | |
| 
 | |
|       if (expected_size != value_size)
 | |
|         return FALSE;
 | |
| 
 | |
|       if (memcmp (expected_info->uri, value, value_size) != 0)
 | |
|         return FALSE;
 | |
| 
 | |
|       *required_matches |= MATCHED_URI;
 | |
|     }
 | |
| 
 | |
|   else if (key_size == 12 && memcmp (key, "Thumb::MTime", 12) == 0)
 | |
|     {
 | |
|       if (!check_integer_match (expected_info->mtime, value, value_size))
 | |
|         return FALSE;
 | |
| 
 | |
|       *required_matches |= MATCHED_MTIME;
 | |
|     }
 | |
| 
 | |
|   else if (key_size == 11 && memcmp (key, "Thumb::Size", 11) == 0)
 | |
|     {
 | |
|       /* A match on Thumb::Size is not required for success, but if we
 | |
|        * find this optional field and it's wrong, we should reject the
 | |
|        * thumbnail.
 | |
|        */
 | |
|       if (!check_integer_match (expected_info->size, value, value_size))
 | |
|         return FALSE;
 | |
|     }
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| check_thumbnail_validity (ExpectedInfo *expected_info,
 | |
|                           const gchar  *contents,
 | |
|                           gsize         size)
 | |
| {
 | |
|   guint required_matches = 0;
 | |
| 
 | |
|   /* Reference: http://www.w3.org/TR/PNG/ */
 | |
|   if (size < 8)
 | |
|     return FALSE;
 | |
| 
 | |
|   if (memcmp (contents, "\x89PNG\r\n\x1a\n", 8) != 0)
 | |
|     return FALSE;
 | |
| 
 | |
|   contents += 8, size -= 8;
 | |
| 
 | |
|   /* We need at least 12 bytes to have a chunk... */
 | |
|   while (size >= 12)
 | |
|     {
 | |
|       guint32 chunk_size_be;
 | |
|       guint32 chunk_size;
 | |
| 
 | |
|       /* PNG is not an aligned file format so we have to be careful
 | |
|        * about reading integers...
 | |
|        */
 | |
|       memcpy (&chunk_size_be, contents, 4);
 | |
|       chunk_size = GUINT32_FROM_BE (chunk_size_be);
 | |
| 
 | |
|       contents += 4, size -= 4;
 | |
| 
 | |
|       /* After consuming the size field, we need to have enough bytes
 | |
|        * for 4 bytes type field, chunk_size bytes for data, then 4 byte
 | |
|        * for CRC (which we ignore)
 | |
|        *
 | |
|        * We just read chunk_size from the file, so it may be very large.
 | |
|        * Make sure it won't wrap when we add 8 to it.
 | |
|        */
 | |
|       if (G_MAXUINT32 - chunk_size < 8 || size < chunk_size + 8)
 | |
|         goto out;
 | |
| 
 | |
|       /* We are only interested in tEXt fields */
 | |
|       if (memcmp (contents, "tEXt", 4) == 0)
 | |
|         {
 | |
|           const gchar *key = contents + 4;
 | |
|           guint32 key_size;
 | |
| 
 | |
|           /* We need to find the nul separator character that splits the
 | |
|            * key/value.  The value is not terminated.
 | |
|            *
 | |
|            * If we find no nul then we just ignore the field.
 | |
|            *
 | |
|            * value may contain extra nuls, but check_png_info_chunk()
 | |
|            * can handle that.
 | |
|            */
 | |
|           for (key_size = 0; key_size < chunk_size; key_size++)
 | |
|             {
 | |
|               if (key[key_size] == '\0')
 | |
|                 {
 | |
|                   const gchar *value;
 | |
|                   guint32 value_size;
 | |
| 
 | |
|                   /* Since key_size < chunk_size, value_size is
 | |
|                    * definitely non-negative.
 | |
|                    */
 | |
|                   value_size = chunk_size - key_size - 1;
 | |
|                   value = key + key_size + 1;
 | |
| 
 | |
|                   /* We found the separator character. */
 | |
|                   if (!check_png_info_chunk (expected_info,
 | |
|                                              key, key_size,
 | |
|                                              value, value_size,
 | |
|                                              &required_matches))
 | |
|                     return FALSE;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|       else
 | |
|         {
 | |
|           /* A bit of a hack: assume that all tEXt chunks will appear
 | |
|            * together.  Therefore, if we have already seen both required
 | |
|            * fields and then see a non-tEXt chunk then we can assume we
 | |
|            * are done.
 | |
|            *
 | |
|            * The common case is that the tEXt chunks come at the start
 | |
|            * of the file before any of the image data.  This trick means
 | |
|            * that we will only fault in a single page (4k) whereas many
 | |
|            * thumbnails (particularly the large ones) can approach 100k
 | |
|            * in size.
 | |
|            */
 | |
|           if (required_matches == MATCHED_ALL)
 | |
|             goto out;
 | |
|         }
 | |
| 
 | |
|       /* skip to the next chunk, ignoring CRC. */
 | |
|       contents += 4, size -= 4;                         /* type field */
 | |
|       contents += chunk_size, size -= chunk_size;       /* data */
 | |
|       contents += 4, size -= 4;                         /* CRC */
 | |
|     }
 | |
| 
 | |
| out:
 | |
|   return required_matches == MATCHED_ALL;
 | |
| }
 | |
| 
 | |
| gboolean
 | |
| thumbnail_verify (const char     *thumbnail_path,
 | |
|                   const gchar    *file_uri,
 | |
|                   const GLocalFileStat *file_stat_buf)
 | |
| {
 | |
|   gboolean thumbnail_is_valid = FALSE;
 | |
|   ExpectedInfo expected_info;
 | |
|   GMappedFile *file;
 | |
| 
 | |
|   if (file_stat_buf == NULL)
 | |
|     return FALSE;
 | |
| 
 | |
|   expected_info.uri = file_uri;
 | |
| #ifdef G_OS_WIN32
 | |
|   expected_info.mtime = (guint64) file_stat_buf->st_mtim.tv_sec;
 | |
| #else
 | |
|   expected_info.mtime = _g_stat_mtime (file_stat_buf);
 | |
| #endif
 | |
|   expected_info.size = _g_stat_size (file_stat_buf);
 | |
| 
 | |
|   file = g_mapped_file_new (thumbnail_path, FALSE, NULL);
 | |
|   if (file)
 | |
|     {
 | |
|       thumbnail_is_valid = check_thumbnail_validity (&expected_info,
 | |
|                                                      g_mapped_file_get_contents (file),
 | |
|                                                      g_mapped_file_get_length (file));
 | |
|       g_mapped_file_unref (file);
 | |
|     }
 | |
| 
 | |
|   return thumbnail_is_valid;
 | |
| }
 |