rmagick 7.1.4 → 7.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a5c3f56f5b39c2f505f37875134e7af86904393036c531e6074f2796939f204c
4
- data.tar.gz: 3bf4833ad9aa56f93e1d2426bd6c72b95c54e0c2c8dcd2c4248f62635bf7606e
3
+ metadata.gz: b0652dce5387352787bef0e59b1548a5c36b6f80a66b8ec9ab4c7f03828f5d0f
4
+ data.tar.gz: 6d286a5f5649816c5cf1f821969783fb6ebef9f5a81b4502719f498869f926f9
5
5
  SHA512:
6
- metadata.gz: ff12f7c040309d906c8ba517cc4a10c472019c0b99d718f470f24b804021dd2a431b4fd876a59e0c18eb8acbb8edac0eb2e15b68826a8ba11779e62e5894a202
7
- data.tar.gz: 06dfb967d7de90cdc6791dcbb2e9fc6b5ece9ccef12e53b3588ea96c09960e28cd938b9f982654ccd95c6f765cd65a1fae179e3c230d14f4548511d851f36de8
6
+ metadata.gz: 4ab4ce5e26eed8275df1cdc1130da0c5bb717fdca8f472c89b26a5aea37579a17e9bc4d9a34aa0c272a4b26242c03c2acbd9b690484b503ddfdf7220fc075cf2
7
+ data.tar.gz: c5ee3eddbed492432ae70f34680a3d9a768fec6ba33bd3c97eca98262f29fd81d2037f56f8e8018ee0afc0a43896ad7186ee5f779da559f9d14ccc05aa28c3cb
data/CHANGELOG.md CHANGED
@@ -3,6 +3,26 @@
3
3
  All notable changes to this project are documented in this file.
4
4
  This project adheres to [Semantic Versioning](http://semver.org/).
5
5
 
6
+ ## RMagick 7.1.5
7
+
8
+ > [!IMPORTANT]
9
+ > More strings that ImageMagick would read from a file now raise `ArgumentError`.
10
+ >
11
+ > `Montage#title=` and the `caption`, `comment` and `label` options, set through `Info#caption=`, `#comment=`, `#label=` or `Info#[]=`, now reject a value whose first non-blank character is `@`, as `Image#polaroid` has done since 7.1.4. ImageMagick read the file it named and put its contents into the montage or into the metadata of the image being read. These checks and the one in `Image#polaroid` also reject a `%[fx:@...]`, `%[hex:@...]` or `%[pixel:@...]` escape, which ImageMagick 7 reads from the named file as well, and their message now says the value "must not name a file with '@'".
12
+ >
13
+ > `Image#profile!` now accepts only the profile formats of ImageMagick's META coder (8BIM, APP1, EXIF, ICC, ICM, IPTC, XMP and their variants) and raises `ArgumentError` for any other name. It decoded the profile with the coder of whatever name it was given, so a profile named `msl` copied from an uploaded image ran as an MSL script.
14
+
15
+ Breaking Changes
16
+
17
+ * Accept only META profile formats in Image#profile! instead of decoding the profile with the coder of that name (#1873)
18
+ * Reject a Montage#title beginning with '@' instead of reading that file (#1874)
19
+ * Reject an Info#caption, #comment or #label value beginning with '@' instead of reading that file (#1875)
20
+ * Also reject %[fx:@...], %[hex:@...] and %[pixel:@...] escapes where a value beginning with '@' is rejected (#1876)
21
+
22
+ Bug Fixes
23
+
24
+ * Fix stack buffer overflow in Image#inspect with a long format name (#1877)
25
+
6
26
  ## RMagick 7.1.4
7
27
 
8
28
  > [!IMPORTANT]
@@ -1159,6 +1159,7 @@ extern VALUE rm_no_freeze(VALUE) ATTRIBUTE_NORETURN;
1159
1159
  extern int rm_strcasecmp(const char *, const char *);
1160
1160
  extern int rm_strncasecmp(const char *, const char *, size_t);
1161
1161
  extern size_t rm_strnlen_s(const char *, size_t);
1162
+ extern bool rm_has_file_reference(const char *);
1162
1163
  extern void rm_check_ary_len(VALUE, long);
1163
1164
  extern VALUE rm_check_ary_type(VALUE ary);
1164
1165
  extern Image *rm_check_destroyed(VALUE);
@@ -2900,6 +2900,35 @@ Image_color_histogram(VALUE self)
2900
2900
  }
2901
2901
 
2902
2902
 
2903
+ /**
2904
+ * Return true if the format is one handled by ImageMagick's META coder. set_profile
2905
+ * decodes the profile with the coder of that name, so any other format would run an
2906
+ * unrelated decoder (MSL, MVG, SVG, ...) on the profile bytes.
2907
+ *
2908
+ * No Ruby usage (internal function)
2909
+ *
2910
+ * @param format the format name
2911
+ * @return true if the format is a META profile format, otherwise false
2912
+ */
2913
+ static bool
2914
+ is_meta_profile_format(const char *format)
2915
+ {
2916
+ static const char *meta_formats[] = {
2917
+ "8BIM", "8BIMTEXT", "8BIMWTEXT", "APP1", "APP1JPEG", "EXIF",
2918
+ "ICC", "ICM", "IPTC", "IPTCTEXT", "IPTCWTEXT", "XMP"
2919
+ };
2920
+
2921
+ for (size_t i = 0; i < sizeof(meta_formats) / sizeof(meta_formats[0]); i++)
2922
+ {
2923
+ if (rm_strcasecmp(format, meta_formats[i]) == 0)
2924
+ {
2925
+ return true;
2926
+ }
2927
+ }
2928
+ return false;
2929
+ }
2930
+
2931
+
2903
2932
  /**
2904
2933
  * Store all the profiles in the profile in the target image. Called from
2905
2934
  * Image_color_profile_eq and Image_iptc_profile_eq.
@@ -2927,10 +2956,17 @@ set_profile(VALUE self, const char *name, VALUE profile)
2927
2956
 
2928
2957
  profile_blob = rm_str2cstr(&profile, &profile_length);
2929
2958
 
2959
+ // Reject the name before GetMagickInfo(), which loads the coder module it names
2960
+ // ("*" loads all of them).
2961
+ if (!is_meta_profile_format(name))
2962
+ {
2963
+ rb_raise(rb_eArgError, "unknown name: %s", name);
2964
+ }
2965
+
2930
2966
  exception = AcquireExceptionInfo();
2931
2967
  m = GetMagickInfo(name, exception);
2932
2968
  CHECK_EXCEPTION();
2933
- if (!m)
2969
+ if (!m || !is_meta_profile_format(m->name))
2934
2970
  {
2935
2971
  DestroyExceptionInfo(exception);
2936
2972
  rb_raise(rb_eArgError, "unknown name: %s", name);
@@ -8305,6 +8341,42 @@ Image_import_pixels(int argc, VALUE *argv, VALUE self)
8305
8341
  }
8306
8342
 
8307
8343
 
8344
+ /**
8345
+ * Append formatted text to the buffer used by build_inspect_string.
8346
+ *
8347
+ * No Ruby usage (internal function)
8348
+ *
8349
+ * Notes:
8350
+ * - vsnprintf returns the length of the untruncated text, so only the bytes
8351
+ * that actually fit are counted. The returned offset never exceeds len-1,
8352
+ * so once the buffer is full later appends add nothing to it.
8353
+ *
8354
+ * @param buffer buffer for the output string
8355
+ * @param len length of buffer
8356
+ * @param x # bytes used in buffer, at most len-1
8357
+ * @param format printf-style format string
8358
+ * @return the new # bytes used in buffer, at most len-1
8359
+ * @see build_inspect_string
8360
+ */
8361
+ static int
8362
+ append_inspect_string(char *buffer, size_t len, int x, const char *format, ...)
8363
+ {
8364
+ va_list args;
8365
+ int n;
8366
+
8367
+ va_start(args, format);
8368
+ n = vsnprintf(buffer+x, len-x, format, args);
8369
+ va_end(args);
8370
+
8371
+ if (n > 0)
8372
+ {
8373
+ x += (int)min((size_t)n, len-1-x);
8374
+ }
8375
+
8376
+ return x;
8377
+ }
8378
+
8379
+
8308
8380
  /**
8309
8381
  * Override Object#inspect - return a string description of the image.
8310
8382
  *
@@ -8328,56 +8400,56 @@ build_inspect_string(Image *image, char *buffer, size_t len)
8328
8400
  // Print magick filename if different from current filename.
8329
8401
  if (*image->magick_filename != '\0' && strcmp(image->magick_filename, image->filename) != 0)
8330
8402
  {
8331
- x += snprintf(buffer+x, len-x, "%.1024s=>", image->magick_filename);
8403
+ x = append_inspect_string(buffer, len, x, "%.1024s=>", image->magick_filename);
8332
8404
  }
8333
8405
  // Print current filename.
8334
- x += snprintf(buffer+x, len-x, "%.1024s", image->filename);
8406
+ x = append_inspect_string(buffer, len, x, "%.1024s", image->filename);
8335
8407
  // Print scene number.
8336
8408
  if ((GetPreviousImageInList(image) != NULL) && (GetNextImageInList(image) != NULL) && image->scene > 0)
8337
8409
  {
8338
- x += snprintf(buffer+x, len-x, "[%" RMIuSIZE "]", image->scene);
8410
+ x = append_inspect_string(buffer, len, x, "[%" RMIuSIZE "]", image->scene);
8339
8411
  }
8340
8412
  // Print format
8341
- x += snprintf(buffer+x, len-x, " %s ", image->magick);
8413
+ x = append_inspect_string(buffer, len, x, " %s ", image->magick);
8342
8414
 
8343
8415
  // Print magick columnsXrows if different from current.
8344
8416
  if (image->magick_columns != 0 || image->magick_rows != 0)
8345
8417
  {
8346
8418
  if (image->magick_columns != image->columns || image->magick_rows != image->rows)
8347
8419
  {
8348
- x += snprintf(buffer+x, len-x, "%" RMIuSIZE "x%" RMIuSIZE "=>", image->magick_columns, image->magick_rows);
8420
+ x = append_inspect_string(buffer, len, x, "%" RMIuSIZE "x%" RMIuSIZE "=>", image->magick_columns, image->magick_rows);
8349
8421
  }
8350
8422
  }
8351
8423
 
8352
- x += snprintf(buffer+x, len-x, "%" RMIuSIZE "x%" RMIuSIZE " ", image->columns, image->rows);
8424
+ x = append_inspect_string(buffer, len, x, "%" RMIuSIZE "x%" RMIuSIZE " ", image->columns, image->rows);
8353
8425
 
8354
8426
  // Print current columnsXrows
8355
8427
  if ( image->page.width != 0 || image->page.height != 0
8356
8428
  || image->page.x != 0 || image->page.y != 0)
8357
8429
  {
8358
- x += snprintf(buffer+x, len-x, "%" RMIuSIZE "x%" RMIuSIZE "+%" RMIdSIZE "+%" RMIdSIZE " ",
8359
- image->page.width, image->page.height,
8360
- image->page.x, image->page.y);
8430
+ x = append_inspect_string(buffer, len, x, "%" RMIuSIZE "x%" RMIuSIZE "+%" RMIdSIZE "+%" RMIdSIZE " ",
8431
+ image->page.width, image->page.height,
8432
+ image->page.x, image->page.y);
8361
8433
  }
8362
8434
 
8363
8435
  if (image->storage_class == DirectClass)
8364
8436
  {
8365
- x += snprintf(buffer+x, len-x, "DirectClass ");
8437
+ x = append_inspect_string(buffer, len, x, "DirectClass ");
8366
8438
  if (image->total_colors != 0)
8367
8439
  {
8368
8440
  if (image->total_colors >= (unsigned long)(1 << 24))
8369
8441
  {
8370
- x += snprintf(buffer+x, len-x, "%" RMIuSIZE "mc ", image->total_colors/1024/1024);
8442
+ x = append_inspect_string(buffer, len, x, "%" RMIuSIZE "mc ", image->total_colors/1024/1024);
8371
8443
  }
8372
8444
  else
8373
8445
  {
8374
8446
  if (image->total_colors >= (unsigned long)(1 << 16))
8375
8447
  {
8376
- x += snprintf(buffer+x, len-x, "%" RMIuSIZE "kc ", image->total_colors/1024);
8448
+ x = append_inspect_string(buffer, len, x, "%" RMIuSIZE "kc ", image->total_colors/1024);
8377
8449
  }
8378
8450
  else
8379
8451
  {
8380
- x += snprintf(buffer+x, len-x, "%" RMIuSIZE "c ", image->total_colors);
8452
+ x = append_inspect_string(buffer, len, x, "%" RMIuSIZE "c ", image->total_colors);
8381
8453
  }
8382
8454
  }
8383
8455
  }
@@ -8388,39 +8460,39 @@ build_inspect_string(Image *image, char *buffer, size_t len)
8388
8460
  // building with GM. GM defines that field as an unsigned int.
8389
8461
  if (image->total_colors <= image->colors)
8390
8462
  {
8391
- x += snprintf(buffer+x, len-x, "PseudoClass %ldc ", (long) image->colors);
8463
+ x = append_inspect_string(buffer, len, x, "PseudoClass %ldc ", (long) image->colors);
8392
8464
  }
8393
8465
  else
8394
8466
  {
8395
- x += snprintf(buffer+x, len-x, "PseudoClass %" RMIuSIZE "=>%" RMIuSIZE "c ", image->total_colors, image->colors);
8467
+ x = append_inspect_string(buffer, len, x, "PseudoClass %" RMIuSIZE "=>%" RMIuSIZE "c ", image->total_colors, image->colors);
8396
8468
  if (image->error.mean_error_per_pixel != 0.0)
8397
8469
  {
8398
- x += snprintf(buffer+x, len-x, "%ld/%.6f/%.6fdb ",
8399
- (long) (image->error.mean_error_per_pixel+0.5),
8400
- image->error.normalized_mean_error,
8401
- image->error.normalized_maximum_error);
8470
+ x = append_inspect_string(buffer, len, x, "%ld/%.6f/%.6fdb ",
8471
+ (long) (image->error.mean_error_per_pixel+0.5),
8472
+ image->error.normalized_mean_error,
8473
+ image->error.normalized_maximum_error);
8402
8474
  }
8403
8475
  }
8404
8476
  }
8405
8477
 
8406
8478
  // Print bit depth
8407
8479
  quantum_depth = GetImageQuantumDepth(image, MagickTrue);
8408
- x += snprintf(buffer+x, len-x, "%lu-bit", quantum_depth);
8480
+ x = append_inspect_string(buffer, len, x, "%lu-bit", quantum_depth);
8409
8481
 
8410
8482
  // Print blob info if appropriate.
8411
8483
  if (GetBlobSize(image) != 0)
8412
8484
  {
8413
8485
  if (GetBlobSize(image) >= (1 << 24))
8414
8486
  {
8415
- x += snprintf(buffer+x, len-x, " %lumb", (unsigned long) (GetBlobSize(image)/1024/1024));
8487
+ x = append_inspect_string(buffer, len, x, " %lumb", (unsigned long) (GetBlobSize(image)/1024/1024));
8416
8488
  }
8417
8489
  else if (GetBlobSize(image) >= 1024)
8418
8490
  {
8419
- x += snprintf(buffer+x, len-x, " %lukb", (unsigned long) (GetBlobSize(image)/1024));
8491
+ x = append_inspect_string(buffer, len, x, " %lukb", (unsigned long) (GetBlobSize(image)/1024));
8420
8492
  }
8421
8493
  else
8422
8494
  {
8423
- x += snprintf(buffer+x, len-x, " %lub", (unsigned long) GetBlobSize(image));
8495
+ x = append_inspect_string(buffer, len, x, " %lub", (unsigned long) GetBlobSize(image));
8424
8496
  }
8425
8497
  }
8426
8498
 
@@ -8440,7 +8512,7 @@ build_inspect_string(Image *image, char *buffer, size_t len)
8440
8512
  }
8441
8513
  }
8442
8514
 
8443
- assert(x < (int)(len-1));
8515
+ assert(x < (int)len);
8444
8516
  buffer[x] = '\0';
8445
8517
 
8446
8518
  return;
@@ -10868,8 +10940,9 @@ Image_pixel_interpolation_method_eq(VALUE self, VALUE method)
10868
10940
 
10869
10941
  /**
10870
10942
  * Produce an image that looks like a Polaroid instant picture. If the image has a "Caption"
10871
- * property, the value is used as a caption. A caption whose first non-blank character is '@'
10872
- * is rejected because ImageMagick reads the caption from the file it names.
10943
+ * property, the value is used as a caption. A caption whose first non-blank character is '@',
10944
+ * or that contains a %[fx:@...], %[hex:@...] or %[pixel:@...] escape, is rejected because
10945
+ * ImageMagick reads the file it names.
10873
10946
  *
10874
10947
  * The following annotate attributes control the label rendering:
10875
10948
  * align, decorate, density, encoding, fill, font, font_family, font_stretch, font_style,
@@ -10890,7 +10963,7 @@ Image_pixel_interpolation_method_eq(VALUE self, VALUE method)
10890
10963
  * @yieldparam opt [Magick::Image::PolaroidOptions]
10891
10964
  *
10892
10965
  * @return [Magick::Image] a new image
10893
- * @raise [ArgumentError] if the "Caption" property begins with '@'
10966
+ * @raise [ArgumentError] if the "Caption" property names a file with '@'
10894
10967
  */
10895
10968
  VALUE
10896
10969
  Image_polaroid(int argc, VALUE *argv, VALUE self)
@@ -10928,20 +11001,11 @@ Image_polaroid(int argc, VALUE *argv, VALUE self)
10928
11001
  #else
10929
11002
  caption = GetImageProperty(clone, "Caption");
10930
11003
  #endif
10931
- if (caption)
11004
+ if (caption && rm_has_file_reference(caption))
10932
11005
  {
10933
- const char *p = caption;
10934
-
10935
- while (isspace((int) ((unsigned char) *p)))
10936
- {
10937
- p++;
10938
- }
10939
- if (*p == '@')
10940
- {
10941
- DestroyImage(clone);
10942
- DestroyExceptionInfo(exception);
10943
- rb_raise(rb_eArgError, "the Caption property must not begin with '@'");
10944
- }
11006
+ DestroyImage(clone);
11007
+ DestroyExceptionInfo(exception);
11008
+ rb_raise(rb_eArgError, "the Caption property must not name a file with '@'");
10945
11009
  }
10946
11010
 
10947
11011
  #if defined(IMAGEMAGICK_7)
@@ -11045,11 +11109,13 @@ Image_preview(VALUE self, VALUE preview)
11045
11109
 
11046
11110
  /**
11047
11111
  * Set the image profile. If "profile" is nil, deletes the profile. Otherwise "profile" must be a
11048
- * string containing the specified profile.
11112
+ * string containing the specified profile, and "name" must be one of the profile formats of
11113
+ * ImageMagick's META coder (8BIM, APP1, EXIF, ICC, ICM, IPTC, XMP and their variants).
11049
11114
  *
11050
11115
  * @param name [String, nil] The profile name, or "*" to represent all the profiles in the image.
11051
11116
  * @param profile [String] The profile value, or nil to cause the profile to be removed.
11052
11117
  * @return [Magick::Image] self
11118
+ * @raise [ArgumentError] if "profile" is not nil and "name" is not a META profile format
11053
11119
  */
11054
11120
  VALUE
11055
11121
  Image_profile_bang(VALUE self, VALUE name, VALUE profile)
@@ -47,6 +47,25 @@ get_option(VALUE self, const char *key)
47
47
  return Qnil;
48
48
  }
49
49
 
50
+ /**
51
+ * Raise ArgumentError if the option is one that ReadImage runs through
52
+ * InterpretImageProperties() and the value would make it read the file it names.
53
+ *
54
+ * No Ruby usage (internal function)
55
+ *
56
+ * @param key the option key
57
+ * @param value the value
58
+ */
59
+ static void
60
+ check_interpreted_option(const char *key, const char *value)
61
+ {
62
+ if ((rm_strcasecmp(key, "caption") == 0 || rm_strcasecmp(key, "comment") == 0 || rm_strcasecmp(key, "label") == 0)
63
+ && rm_has_file_reference(value))
64
+ {
65
+ rb_raise(rb_eArgError, "the %s option must not name a file with '@'", key);
66
+ }
67
+ }
68
+
50
69
  /**
51
70
  * Set the specified option to this value. If the value is nil just unset any
52
71
  * current value.
@@ -74,6 +93,7 @@ set_option(VALUE self, const char *key, VALUE string)
74
93
  char *value;
75
94
 
76
95
  value = StringValueCStr(string);
96
+ check_interpreted_option(key, value);
77
97
  SetImageOption(info, key, value);
78
98
  }
79
99
  return string;
@@ -306,6 +326,9 @@ Info_aref(int argc, VALUE *argv, VALUE self)
306
326
  *
307
327
  * - Essentially the same function as {Info#define} but paired with {Info#[]}
308
328
  * - If the value is nil it is equivalent to {Info#undefine}.
329
+ * - A value whose first non-blank character is '@', or that contains a %[fx:@...], %[hex:@...] or
330
+ * %[pixel:@...] escape, is rejected for the "caption", "comment" and "label" keys because
331
+ * ImageMagick reads the file it names.
309
332
  *
310
333
  * @overload []=(format, key)
311
334
  * @param format [String] An image format name such as "ps" or "tiff".
@@ -315,6 +338,7 @@ Info_aref(int argc, VALUE *argv, VALUE self)
315
338
  * @param key [String] A string that identifies the option.
316
339
  *
317
340
  * @return [Magick::Image::Info] self
341
+ * @raise [ArgumentError] if the "caption", "comment" or "label" value names a file with '@'
318
342
  * @see #[]
319
343
  * @see #define
320
344
  * @see #undefine
@@ -368,6 +392,7 @@ Info_aset(int argc, VALUE *argv, VALUE self)
368
392
  /* Allow any argument that supports to_s */
369
393
  value = rb_String(value);
370
394
  value_p = StringValueCStr(value);
395
+ check_interpreted_option(ckey, value_p);
371
396
 
372
397
  okay = SetImageOption(info, ckey, value_p);
373
398
  if (!okay)
@@ -552,10 +577,13 @@ Info_caption(VALUE self)
552
577
 
553
578
 
554
579
  /**
555
- * Assigns a caption to an image.
580
+ * Assigns a caption to an image. A caption whose first non-blank character is '@', or that
581
+ * contains a %[fx:@...], %[hex:@...] or %[pixel:@...] escape, is rejected because ImageMagick
582
+ * reads the file it names.
556
583
  *
557
584
  * @param caption [String] the caption
558
585
  * @return [String] the given value
586
+ * @raise [ArgumentError] if the caption names a file with '@'
559
587
  */
560
588
  VALUE
561
589
  Info_caption_eq(VALUE self, VALUE caption)
@@ -637,10 +665,13 @@ VALUE Info_comment(VALUE self)
637
665
  }
638
666
 
639
667
  /**
640
- * Set the comment
668
+ * Set the comment. A comment whose first non-blank character is '@', or that contains a
669
+ * %[fx:@...], %[hex:@...] or %[pixel:@...] escape, is rejected because ImageMagick reads the
670
+ * file it names.
641
671
  *
642
672
  * @param string [String] the comment
643
673
  * @return [String] the given comment
674
+ * @raise [ArgumentError] if the comment names a file with '@'
644
675
  */
645
676
  VALUE Info_comment_eq(VALUE self, VALUE string)
646
677
  {
@@ -1529,10 +1560,13 @@ VALUE Info_label(VALUE self)
1529
1560
  }
1530
1561
 
1531
1562
  /**
1532
- * Set the label.
1563
+ * Set the label. A label whose first non-blank character is '@', or that contains a
1564
+ * %[fx:@...], %[hex:@...] or %[pixel:@...] escape, is rejected because ImageMagick reads the
1565
+ * file it names.
1533
1566
  *
1534
1567
  * @param string [String] the label
1535
1568
  * @return [String] the given label
1569
+ * @raise [ArgumentError] if the label names a file with '@'
1536
1570
  */
1537
1571
  VALUE Info_label_eq(VALUE self, VALUE string)
1538
1572
  {
@@ -442,18 +442,29 @@ Montage_tile_eq(VALUE self, VALUE tile_arg)
442
442
 
443
443
 
444
444
  /**
445
- * Set title value.
445
+ * Set title value. A title whose first non-blank character is '@', or that contains a
446
+ * %[fx:@...], %[hex:@...] or %[pixel:@...] escape, is rejected because ImageMagick reads the
447
+ * file it names.
446
448
  *
447
449
  * @param title [String] the title
448
450
  * @return [String] the given title
451
+ * @raise [ArgumentError] if the title names a file with '@'
449
452
  */
450
453
  VALUE
451
454
  Montage_title_eq(VALUE self, VALUE title)
452
455
  {
453
456
  Montage *montage;
457
+ const char *title_cstr;
454
458
 
455
459
  TypedData_Get_Struct(self, Montage, &rm_montage_data_type, montage);
456
- magick_clone_string(&montage->info->title, StringValueCStr(title));
460
+ title_cstr = StringValueCStr(title);
461
+
462
+ if (rm_has_file_reference(title_cstr))
463
+ {
464
+ rb_raise(rb_eArgError, "the title must not name a file with '@'");
465
+ }
466
+
467
+ magick_clone_string(&montage->info->title, title_cstr);
457
468
  return title;
458
469
  }
459
470
 
@@ -221,6 +221,50 @@ rm_strnlen_s(const char *str, size_t strsz)
221
221
  }
222
222
 
223
223
 
224
+ /**
225
+ * Return true if InterpretImageProperties() would read a file the string names, that is, if its
226
+ * first non-blank character is '@', or if it contains a %[fx:...], %[hex:...] or %[pixel:...]
227
+ * escape whose expression begins with '@'.
228
+ *
229
+ * ImageMagick 7 reads such an expression from the file it names, with no blank skipped before
230
+ * the '@'. It also interprets a %[...] inside an fx expression, even after "%%" since '%' is the
231
+ * modulo operator there, so every "%[" in the string is checked. The prefixes are compared with
232
+ * LocaleNCompare() as ImageMagick does. ImageMagick 6 does not read these expressions, but the
233
+ * check is the same for both.
234
+ *
235
+ * No Ruby usage (internal function)
236
+ *
237
+ * @param str the string
238
+ * @return true if the string names a file to read, otherwise false
239
+ */
240
+ bool
241
+ rm_has_file_reference(const char *str)
242
+ {
243
+ const char *p;
244
+
245
+ p = str;
246
+ while (isspace((int) ((unsigned char) *p)))
247
+ {
248
+ p++;
249
+ }
250
+ if (*p == '@')
251
+ {
252
+ return true;
253
+ }
254
+
255
+ for (p = strstr(str, "%["); p != NULL; p = strstr(p + 2, "%["))
256
+ {
257
+ if (LocaleNCompare(p + 2, "fx:@", 4) == 0
258
+ || LocaleNCompare(p + 2, "hex:@", 5) == 0
259
+ || LocaleNCompare(p + 2, "pixel:@", 7) == 0)
260
+ {
261
+ return true;
262
+ }
263
+ }
264
+ return false;
265
+ }
266
+
267
+
224
268
  /**
225
269
  * Raise exception if array too short.
226
270
  *
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Magick
4
- VERSION = '7.1.4'
4
+ VERSION = '7.1.5'
5
5
  MIN_RUBY_VERSION = '3.2.0'
6
6
  MIN_IM6_VERSION = '6.9.0'
7
7
  MIN_IM7_VERSION = '7.1.0'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rmagick
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.1.4
4
+ version: 7.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tim Hunter
@@ -143,7 +143,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
143
143
  version: '0'
144
144
  requirements:
145
145
  - ImageMagick 6.9.0+ (for ImageMagick 6) or 7.1.0+ (for ImageMagick 7)
146
- rubygems_version: 4.0.18
146
+ rubygems_version: 4.0.20
147
147
  specification_version: 4
148
148
  summary: Ruby binding to ImageMagick
149
149
  test_files: []