rmagick 7.1.3 → 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: 0d81d58d1022ce126e777f8fa23d3cd3ee634c3f1c9fd6518de50b3d6126b239
4
- data.tar.gz: 1cee4ad10699e4a2b4119c1e4ef86d3b206da05e1af830b450d43f8dcc7e1d62
3
+ metadata.gz: b0652dce5387352787bef0e59b1548a5c36b6f80a66b8ec9ab4c7f03828f5d0f
4
+ data.tar.gz: 6d286a5f5649816c5cf1f821969783fb6ebef9f5a81b4502719f498869f926f9
5
5
  SHA512:
6
- metadata.gz: 517f893ecbf2d2c34257ae10af315b7b5f9cf24104f3422c951334bb63a17ee23941198fa9e8d4404f6bd3deb75a4efbe9d707be6ada44108217c584ba793815
7
- data.tar.gz: 45f92c15dc8f1790de2a71949fb0c32fb496a7806266ff7ea9e0890e4da51e4dce4896b7ae09d0a4ec11a925b393f41fea34527cf21d45c54ad3917786ce2d6b
6
+ metadata.gz: 4ab4ce5e26eed8275df1cdc1130da0c5bb717fdca8f472c89b26a5aea37579a17e9bc4d9a34aa0c272a4b26242c03c2acbd9b690484b503ddfdf7220fc075cf2
7
+ data.tar.gz: c5ee3eddbed492432ae70f34680a3d9a768fec6ba33bd3c97eca98262f29fd81d2037f56f8e8018ee0afc0a43896ad7186ee5f779da559f9d14ccc05aa28c3cb
data/CHANGELOG.md CHANGED
@@ -3,6 +3,42 @@
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
+
26
+ ## RMagick 7.1.4
27
+
28
+ > [!IMPORTANT]
29
+ > Four kinds of input that used to be accepted now raise `ArgumentError`.
30
+ >
31
+ > Three of them were ways for a string RMagick documents as a value to be read back as part of an ImageMagick program: an opacity of `"1%\nimage Over 0,0 80,80 'secret.png'\n1%"`, a `Draw#pattern` name carrying the same payload, and a `Caption` property of `"@/etc/passwd"` arriving in the metadata of an uploaded image. ImageMagick executed the injected primitive, or read the named file and drew its contents into the returned image. `Image#dispatch` is the fourth: a `map` containing a NUL byte is now rejected rather than sized with the byte length while ImageMagick fills only what precedes the NUL.
32
+ >
33
+ > `Image#export_pixels_to_str` with `Magick::LongPixel` also returns half as many bytes on LP64, and `Image#import_pixels` takes a buffer of that size. RMagick reserved `sizeof(unsigned long)` per element while ImageMagick reads and writes `unsigned int`, so the second half of the string was never written and carried whatever the heap held. `Image#colormap` returned uninitialized memory the same way, as the previous colour of an entry it had just created.
34
+
35
+ Breaking Changes
36
+
37
+ * Reject an opacity percentage that is not a plain number from 0% to 100% (#1867)
38
+ * Restrict a Draw#pattern name to letters, digits, hyphen, period and underscore (#1869)
39
+ * Return LongPixel pixel data as unsigned int, without the uninitialized padding Image#export_pixels_to_str used to append (#1870)
40
+ * Reject a Caption property beginning with '@' in Image#polaroid instead of reading that file (#1871)
41
+
6
42
  ## RMagick 7.1.3
7
43
 
8
44
  Bug Fixes
@@ -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);
@@ -3295,7 +3331,7 @@ Image_colormap(int argc, VALUE *argv, VALUE self)
3295
3331
  image->colormap = (PixelColor *)magick_safe_realloc(image->colormap, (idx+1), sizeof(PixelColor));
3296
3332
  }
3297
3333
 
3298
- for (i = image->colors; i < idx; i++)
3334
+ for (i = image->colors; i <= idx; i++)
3299
3335
  {
3300
3336
  image->colormap[i] = black;
3301
3337
  }
@@ -5758,7 +5794,8 @@ Image_dispatch(int argc, VALUE *argv, VALUE self)
5758
5794
  y = NUM2LONG(argv[1]);
5759
5795
  columns = NUM2ULONG(argv[2]);
5760
5796
  rows = NUM2ULONG(argv[3]);
5761
- map = rm_str2cstr(&argv[4], &mapL);
5797
+ map = StringValueCStr(argv[4]);
5798
+ mapL = strlen(map);
5762
5799
  if (argc == 6)
5763
5800
  {
5764
5801
  stg_type = RTEST(argv[5]) ? DoublePixel : QuantumPixel;
@@ -6903,7 +6940,7 @@ Image_export_pixels_to_str(int argc, VALUE *argv, VALUE self)
6903
6940
  sz = sizeof(float);
6904
6941
  break;
6905
6942
  case LongPixel:
6906
- sz = sizeof(unsigned long);
6943
+ sz = sizeof(unsigned int);
6907
6944
  break;
6908
6945
  case QuantumPixel:
6909
6946
  sz = sizeof(Quantum);
@@ -8174,7 +8211,7 @@ Image_import_pixels(int argc, VALUE *argv, VALUE self)
8174
8211
  type_sz = sizeof(unsigned short);
8175
8212
  break;
8176
8213
  case LongPixel:
8177
- type_sz = sizeof(unsigned long);
8214
+ type_sz = sizeof(unsigned int);
8178
8215
  break;
8179
8216
  case DoublePixel:
8180
8217
  type_sz = sizeof(double);
@@ -8304,6 +8341,42 @@ Image_import_pixels(int argc, VALUE *argv, VALUE self)
8304
8341
  }
8305
8342
 
8306
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
+
8307
8380
  /**
8308
8381
  * Override Object#inspect - return a string description of the image.
8309
8382
  *
@@ -8327,56 +8400,56 @@ build_inspect_string(Image *image, char *buffer, size_t len)
8327
8400
  // Print magick filename if different from current filename.
8328
8401
  if (*image->magick_filename != '\0' && strcmp(image->magick_filename, image->filename) != 0)
8329
8402
  {
8330
- x += snprintf(buffer+x, len-x, "%.1024s=>", image->magick_filename);
8403
+ x = append_inspect_string(buffer, len, x, "%.1024s=>", image->magick_filename);
8331
8404
  }
8332
8405
  // Print current filename.
8333
- x += snprintf(buffer+x, len-x, "%.1024s", image->filename);
8406
+ x = append_inspect_string(buffer, len, x, "%.1024s", image->filename);
8334
8407
  // Print scene number.
8335
8408
  if ((GetPreviousImageInList(image) != NULL) && (GetNextImageInList(image) != NULL) && image->scene > 0)
8336
8409
  {
8337
- x += snprintf(buffer+x, len-x, "[%" RMIuSIZE "]", image->scene);
8410
+ x = append_inspect_string(buffer, len, x, "[%" RMIuSIZE "]", image->scene);
8338
8411
  }
8339
8412
  // Print format
8340
- x += snprintf(buffer+x, len-x, " %s ", image->magick);
8413
+ x = append_inspect_string(buffer, len, x, " %s ", image->magick);
8341
8414
 
8342
8415
  // Print magick columnsXrows if different from current.
8343
8416
  if (image->magick_columns != 0 || image->magick_rows != 0)
8344
8417
  {
8345
8418
  if (image->magick_columns != image->columns || image->magick_rows != image->rows)
8346
8419
  {
8347
- 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);
8348
8421
  }
8349
8422
  }
8350
8423
 
8351
- 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);
8352
8425
 
8353
8426
  // Print current columnsXrows
8354
8427
  if ( image->page.width != 0 || image->page.height != 0
8355
8428
  || image->page.x != 0 || image->page.y != 0)
8356
8429
  {
8357
- x += snprintf(buffer+x, len-x, "%" RMIuSIZE "x%" RMIuSIZE "+%" RMIdSIZE "+%" RMIdSIZE " ",
8358
- image->page.width, image->page.height,
8359
- 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);
8360
8433
  }
8361
8434
 
8362
8435
  if (image->storage_class == DirectClass)
8363
8436
  {
8364
- x += snprintf(buffer+x, len-x, "DirectClass ");
8437
+ x = append_inspect_string(buffer, len, x, "DirectClass ");
8365
8438
  if (image->total_colors != 0)
8366
8439
  {
8367
8440
  if (image->total_colors >= (unsigned long)(1 << 24))
8368
8441
  {
8369
- 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);
8370
8443
  }
8371
8444
  else
8372
8445
  {
8373
8446
  if (image->total_colors >= (unsigned long)(1 << 16))
8374
8447
  {
8375
- 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);
8376
8449
  }
8377
8450
  else
8378
8451
  {
8379
- x += snprintf(buffer+x, len-x, "%" RMIuSIZE "c ", image->total_colors);
8452
+ x = append_inspect_string(buffer, len, x, "%" RMIuSIZE "c ", image->total_colors);
8380
8453
  }
8381
8454
  }
8382
8455
  }
@@ -8387,39 +8460,39 @@ build_inspect_string(Image *image, char *buffer, size_t len)
8387
8460
  // building with GM. GM defines that field as an unsigned int.
8388
8461
  if (image->total_colors <= image->colors)
8389
8462
  {
8390
- x += snprintf(buffer+x, len-x, "PseudoClass %ldc ", (long) image->colors);
8463
+ x = append_inspect_string(buffer, len, x, "PseudoClass %ldc ", (long) image->colors);
8391
8464
  }
8392
8465
  else
8393
8466
  {
8394
- 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);
8395
8468
  if (image->error.mean_error_per_pixel != 0.0)
8396
8469
  {
8397
- x += snprintf(buffer+x, len-x, "%ld/%.6f/%.6fdb ",
8398
- (long) (image->error.mean_error_per_pixel+0.5),
8399
- image->error.normalized_mean_error,
8400
- 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);
8401
8474
  }
8402
8475
  }
8403
8476
  }
8404
8477
 
8405
8478
  // Print bit depth
8406
8479
  quantum_depth = GetImageQuantumDepth(image, MagickTrue);
8407
- x += snprintf(buffer+x, len-x, "%lu-bit", quantum_depth);
8480
+ x = append_inspect_string(buffer, len, x, "%lu-bit", quantum_depth);
8408
8481
 
8409
8482
  // Print blob info if appropriate.
8410
8483
  if (GetBlobSize(image) != 0)
8411
8484
  {
8412
8485
  if (GetBlobSize(image) >= (1 << 24))
8413
8486
  {
8414
- 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));
8415
8488
  }
8416
8489
  else if (GetBlobSize(image) >= 1024)
8417
8490
  {
8418
- 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));
8419
8492
  }
8420
8493
  else
8421
8494
  {
8422
- x += snprintf(buffer+x, len-x, " %lub", (unsigned long) GetBlobSize(image));
8495
+ x = append_inspect_string(buffer, len, x, " %lub", (unsigned long) GetBlobSize(image));
8423
8496
  }
8424
8497
  }
8425
8498
 
@@ -8439,7 +8512,7 @@ build_inspect_string(Image *image, char *buffer, size_t len)
8439
8512
  }
8440
8513
  }
8441
8514
 
8442
- assert(x < (int)(len-1));
8515
+ assert(x < (int)len);
8443
8516
  buffer[x] = '\0';
8444
8517
 
8445
8518
  return;
@@ -10867,7 +10940,9 @@ Image_pixel_interpolation_method_eq(VALUE self, VALUE method)
10867
10940
 
10868
10941
  /**
10869
10942
  * Produce an image that looks like a Polaroid instant picture. If the image has a "Caption"
10870
- * property, the value is used as a caption.
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.
10871
10946
  *
10872
10947
  * The following annotate attributes control the label rendering:
10873
10948
  * align, decorate, density, encoding, fill, font, font_family, font_stretch, font_style,
@@ -10888,6 +10963,7 @@ Image_pixel_interpolation_method_eq(VALUE self, VALUE method)
10888
10963
  * @yieldparam opt [Magick::Image::PolaroidOptions]
10889
10964
  *
10890
10965
  * @return [Magick::Image] a new image
10966
+ * @raise [ArgumentError] if the "Caption" property names a file with '@'
10891
10967
  */
10892
10968
  VALUE
10893
10969
  Image_polaroid(int argc, VALUE *argv, VALUE self)
@@ -10897,9 +10973,7 @@ Image_polaroid(int argc, VALUE *argv, VALUE self)
10897
10973
  double angle = -5.0;
10898
10974
  Draw *draw;
10899
10975
  ExceptionInfo *exception;
10900
- #if defined(IMAGEMAGICK_7)
10901
10976
  const char *caption;
10902
- #endif
10903
10977
 
10904
10978
  image = rm_check_destroyed(self);
10905
10979
 
@@ -10924,6 +10998,17 @@ Image_polaroid(int argc, VALUE *argv, VALUE self)
10924
10998
  exception = AcquireExceptionInfo();
10925
10999
  #if defined(IMAGEMAGICK_7)
10926
11000
  caption = GetImageProperty(clone, "Caption", exception);
11001
+ #else
11002
+ caption = GetImageProperty(clone, "Caption");
11003
+ #endif
11004
+ if (caption && rm_has_file_reference(caption))
11005
+ {
11006
+ DestroyImage(clone);
11007
+ DestroyExceptionInfo(exception);
11008
+ rb_raise(rb_eArgError, "the Caption property must not name a file with '@'");
11009
+ }
11010
+
11011
+ #if defined(IMAGEMAGICK_7)
10927
11012
  GVL_STRUCT_TYPE(PolaroidImage) args = { clone, draw->info, caption, angle, image->interpolate, exception };
10928
11013
  new_image = (Image *)CALL_FUNC_WITHOUT_GVL(GVL_FUNC(PolaroidImage), &args);
10929
11014
  #else
@@ -11024,11 +11109,13 @@ Image_preview(VALUE self, VALUE preview)
11024
11109
 
11025
11110
  /**
11026
11111
  * Set the image profile. If "profile" is nil, deletes the profile. Otherwise "profile" must be a
11027
- * 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).
11028
11114
  *
11029
11115
  * @param name [String, nil] The profile name, or "*" to represent all the profiles in the image.
11030
11116
  * @param profile [String] The profile value, or nil to cause the profile to be removed.
11031
11117
  * @return [Magick::Image] self
11118
+ * @raise [ArgumentError] if "profile" is not nil and "name" is not a META profile format
11032
11119
  */
11033
11120
  VALUE
11034
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.3'
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'
@@ -228,7 +228,12 @@ module Magick
228
228
  end
229
229
 
230
230
  def to_opacity(opacity)
231
- return opacity if opacity.is_a?(String) && opacity.end_with?('%')
231
+ if opacity.is_a?(String) && opacity.end_with?('%')
232
+ percentage = /\A(\d*\.?\d+(?:[eE][-+]?\d+)?)%\z/.match(opacity)
233
+ Kernel.raise ArgumentError, 'opacity must be a percentage between 0% and 100%' unless percentage && Float(percentage[1]) <= 100
234
+
235
+ return opacity
236
+ end
232
237
 
233
238
  value = Float(opacity)
234
239
  Kernel.raise ArgumentError, 'opacity must be >= 0 and <= 1.0' if value < 0 || value > 1.0
@@ -448,14 +453,21 @@ module Magick
448
453
  # draw the pattern. Reference the pattern by using its name
449
454
  # as the argument to the 'fill' or 'stroke' methods
450
455
  def pattern(name, x, y, width, height)
456
+ name = to_string(name)
457
+ Kernel.raise ArgumentError, 'pattern name must consist of letters, digits, hyphen, period and underscore' unless /\A[\w.-]+\z/.match?(name)
458
+
459
+ viewport = sprintf('%g %g %g %g', x, y, width, height)
460
+
451
461
  push('defs')
452
- push("pattern #{to_string(name)} " + sprintf('%g %g %g %g', x, y, width, height))
462
+ push("pattern #{name} " + viewport)
453
463
  push('graphic-context')
454
- yield
455
- ensure
456
- pop('graphic-context')
457
- pop('pattern')
458
- pop('defs')
464
+ begin
465
+ yield
466
+ ensure
467
+ pop('graphic-context')
468
+ pop('pattern')
469
+ pop('defs')
470
+ end
459
471
  end
460
472
 
461
473
  # Set point to fill color.
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.3
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: []