Skip to content

Commit 6f1c38d

Browse files
committed
Disallow non-ASCII characters (outside of [0..127] range) in the interopID field.
Signed-off-by: cuneyt.ozdas <cuneyt.ozdas@autodesk.com>
1 parent e31afef commit 6f1c38d

3 files changed

Lines changed: 28 additions & 100 deletions

File tree

src/OpenColorIO/ColorSpace.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ void ColorSpace::setInteropID(const char * interopID)
236236
if (!id.empty())
237237
{
238238
// Count the number of ':' characters in the string
239-
// This is UTF-8 safe.
239+
// and check for non-ASCII characters
240240
size_t colonCount = 0;
241241
size_t lastColonPos = std::string::npos;
242242

@@ -247,6 +247,12 @@ void ColorSpace::setInteropID(const char * interopID)
247247
colonCount++;
248248
lastColonPos = i;
249249
}
250+
else if (static_cast<unsigned char>(id[i]) >= 0x80)
251+
{
252+
std::ostringstream oss;
253+
oss << "InteropID '" << id << "' is invalid: only ASCII characters [0x00..0x7F] are allowed.";
254+
throw Exception(oss.str().c_str());
255+
}
250256
}
251257

252258
// Validate: only zero or one ':' character allowed
@@ -265,7 +271,7 @@ void ColorSpace::setInteropID(const char * interopID)
265271
throw Exception(oss.str().c_str());
266272
}
267273

268-
// TODO: If no namespace is given, verify the interopID against CIF list and warn if not found.
274+
// TODO: Do we want to verify the interopID against the CIF list here?
269275
}
270276

271277
getImpl()->m_interopID = id;

tests/cpu/ColorSpace_tests.cpp

Lines changed: 13 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1855,59 +1855,22 @@ OCIO_ADD_TEST(ColorSpace, interop_id)
18551855

18561856
// Test invalid InteropID with multiple colons.
18571857
const char * invalidMultipleColons = "name:space:cs_name";
1858-
OCIO_CHECK_THROW_WHAT(cs->setInteropID(invalidMultipleColons),
1859-
OCIO::Exception,
1860-
"InteropID 'name:space:cs_name' is invalid: only zero or one ':' character is allowed.");
1858+
OCIO_CHECK_THROW_WHAT(cs->setInteropID(invalidMultipleColons), OCIO::Exception,
1859+
"only zero or one ':' character is allowed");
18611860

18621861
// Test invalid InteropID with colon at the end.
18631862
const char * invalidColonAtEnd = "namespace:";
1864-
OCIO_CHECK_THROW_WHAT(cs->setInteropID(invalidColonAtEnd),
1865-
OCIO::Exception,
1866-
"InteropID 'namespace:' is invalid: ':' character cannot be the last character.");
1867-
1868-
// Test invalid InteropID with three colons.
1869-
const char * invalidThreeColons = "a:b:c:d";
1870-
OCIO_CHECK_THROW_WHAT(cs->setInteropID(invalidThreeColons),
1871-
OCIO::Exception,
1872-
"InteropID 'a:b:c:d' is invalid: only zero or one ':' character is allowed.");
1873-
1874-
// Test UTF-8 strings with valid single colon.
1875-
const char * utf8ValidColon = "標準:萬國碼";
1876-
OCIO_CHECK_NO_THROW(cs->setInteropID(utf8ValidColon));
1877-
OCIO_CHECK_EQUAL(std::string(cs->getInteropID()), utf8ValidColon);
1878-
1879-
// Test UTF-8 strings with invalid multiple colons.
1880-
const char * utf8InvalidMultipleColons = "標準:萬國:碼";
1881-
OCIO_CHECK_THROW_WHAT(cs->setInteropID(utf8InvalidMultipleColons),
1882-
OCIO::Exception,
1883-
"only zero or one ':' character is allowed.");
1884-
1885-
// Test UTF-8 strings with invalid colon at end.
1886-
const char * utf8InvalidColonAtEnd = "標準萬國碼:";
1887-
OCIO_CHECK_THROW_WHAT(cs->setInteropID(utf8InvalidColonAtEnd),
1888-
OCIO::Exception,
1889-
"':' character cannot be the last character.");
1890-
1891-
// Test UTF-8 strings without colon (should be valid).
1892-
const char * utf8NoColon = "標準萬國碼";
1893-
OCIO_CHECK_NO_THROW(cs->setInteropID(utf8NoColon));
1894-
OCIO_CHECK_EQUAL(std::string(cs->getInteropID()), utf8NoColon);
1895-
1896-
// Test edge case: single colon character.
1897-
const char * singleColon = ":";
1898-
OCIO_CHECK_THROW_WHAT(cs->setInteropID(singleColon),
1899-
OCIO::Exception,
1900-
"':' character cannot be the last character.");
1901-
1902-
// Test edge case: colon at beginning (should be valid).
1903-
const char * colonAtBeginning = ":valid_name";
1904-
OCIO_CHECK_NO_THROW(cs->setInteropID(colonAtBeginning));
1905-
OCIO_CHECK_EQUAL(std::string(cs->getInteropID()), colonAtBeginning);
1906-
1907-
// Test valid InteropID with one colon (not at the end).
1908-
const char * validWithColon = "namespace:cs_name";
1909-
OCIO_CHECK_NO_THROW(cs->setInteropID(validWithColon));
1910-
OCIO_CHECK_EQUAL(std::string(cs->getInteropID()), validWithColon);
1863+
OCIO_CHECK_THROW_WHAT(cs->setInteropID(invalidColonAtEnd), OCIO::Exception,
1864+
"':' character cannot be the last character");
1865+
1866+
// Test invalid InteropID with non-ASCII characters.
1867+
const char * invalidNonASCII1 = "café_scene"; // Contains é (0xC3 0xA9)
1868+
OCIO_CHECK_THROW_WHAT(cs->setInteropID(invalidNonASCII1), OCIO::Exception,
1869+
"contains non-ASCII characters");
1870+
1871+
const char * invalidNonASCII2 = "space±_name"; // Contains ± (ANSI 0xB1)
1872+
OCIO_CHECK_THROW_WHAT(cs->setInteropID(invalidNonASCII2), OCIO::Exception,
1873+
"contains non-ASCII characters");
19111874
}
19121875

19131876
OCIO_ADD_TEST(ColorSpace, interop_id_serialization)

tests/python/ColorSpaceTest.py

Lines changed: 7 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -680,65 +680,24 @@ def test_interop_id(self):
680680
self.colorspace.setInteropID(valid_with_colon)
681681
self.assertEqual(self.colorspace.getInteropID(), valid_with_colon)
682682

683-
# Test valid InteropID with colon in the middle.
684-
valid_colon_middle = 'namespace:colorspace_name'
685-
self.colorspace.setInteropID(valid_colon_middle)
686-
self.assertEqual(self.colorspace.getInteropID(), valid_colon_middle)
687-
688683
# Test invalid InteropID with multiple colons.
689684
with self.assertRaises(Exception) as context:
690685
self.colorspace.setInteropID('name:space:cs_name')
691686
self.assertIn("only zero or one ':' character is allowed", str(context.exception))
692687

693688
# Test invalid InteropID with colon at the end.
694689
with self.assertRaises(Exception) as context:
695-
self.colorspace.setInteropID('lin_ap1_scene:')
690+
self.colorspace.setInteropID('namespace:')
696691
self.assertIn("':' character cannot be the last character", str(context.exception))
697692

698-
# Test invalid InteropID with three colons.
699-
with self.assertRaises(Exception) as context:
700-
self.colorspace.setInteropID('a:b:c:d')
701-
self.assertIn("only zero or one ':' character is allowed", str(context.exception))
702-
703-
# Test UTF-8 strings with valid single colon.
704-
utf8_valid_colon = '標準:萬國碼'
705-
self.colorspace.setInteropID(utf8_valid_colon)
706-
self.assertEqual(self.colorspace.getInteropID(), utf8_valid_colon)
707-
708-
# Test UTF-8 strings with invalid multiple colons.
709-
with self.assertRaises(Exception) as context:
710-
self.colorspace.setInteropID('標準:萬國:碼')
711-
self.assertIn("only zero or one ':' character is allowed", str(context.exception))
712-
713-
# Test UTF-8 strings with invalid colon at end.
693+
# Test invalid InteropID with non-ASCII characters.
714694
with self.assertRaises(Exception) as context:
715-
self.colorspace.setInteropID('標準萬國碼:')
716-
self.assertIn("':' character cannot be the last character", str(context.exception))
695+
self.colorspace.setInteropID('café_scene') # Contains é (UTF-8)
696+
self.assertIn("contains non-ASCII characters", str(context.exception))
717697

718-
# Test UTF-8 strings without colon (should be valid).
719-
utf8_no_colon = '標準萬國碼'
720-
self.colorspace.setInteropID(utf8_no_colon)
721-
self.assertEqual(self.colorspace.getInteropID(), utf8_no_colon)
722-
723-
# Test edge case: single colon character.
724698
with self.assertRaises(Exception) as context:
725-
self.colorspace.setInteropID(':')
726-
self.assertIn("':' character cannot be the last character", str(context.exception))
727-
728-
# Test edge case: colon at beginning (should be valid).
729-
colon_at_beginning = ':valid_name'
730-
self.colorspace.setInteropID(colon_at_beginning)
731-
self.assertEqual(self.colorspace.getInteropID(), colon_at_beginning)
732-
733-
# Test with Unicode characters that might look like colons but aren't ASCII colons.
734-
unicode_similar = 'test:name' # This uses a full-width colon (U+FF1A), not ASCII colon
735-
self.colorspace.setInteropID(unicode_similar)
736-
self.assertEqual(self.colorspace.getInteropID(), unicode_similar)
737-
738-
# Test mixed ASCII and Unicode with valid single ASCII colon.
739-
mixed_valid = 'ñamespace:色空間'
740-
self.colorspace.setInteropID(mixed_valid)
741-
self.assertEqual(self.colorspace.getInteropID(), mixed_valid)
699+
self.colorspace.setInteropID('space±_name') # Contains ± (ANSI 0xB1)
700+
self.assertIn("contains non-ASCII characters", str(context.exception))
742701

743702
def test_amf_transform_ids(self):
744703
"""
@@ -900,7 +859,7 @@ def test_processor_to_known_colorspace(self):
900859
- !<ColorSpace>
901860
name: Linear ITU-R BT.709
902861
description: A linear Rec.709 space with an unusual spelling.
903-
interop_id: "mycompany:lin_rec709_scene"
862+
interop_id: "mycompany:led_wall_1"
904863
isdata: false
905864
from_scene_reference: !<GroupTransform>
906865
name: AP0 to Linear Rec.709 (sRGB)

0 commit comments

Comments
 (0)