Repository navigation
fix(turba): enforce attribute constraints on every write path - #74
Merged
Merged
Conversation
The maxlength and regex parameters of attributes.php were only checked by the Horde_Form text type in the web edit form. Turba_Driver::add() and save() handed raw values to the backend, so API, ActiveSync, CardDAV and import writes ended in a database error (PostgreSQL: SQLSTATE 22001 "value too long for type character varying(10)") or in silent truncation (MySQL, non-strict mode). Add Horde\Turba\AttributeValidator, bound in Turba_Application::_init() from the loaded attribute definitions and handed to drivers by Turba_Factory_Driver. Turba_Driver::add() and save() validate the attributes the source stores and throw Turba_Exception_InvalidAttribute (extends Turba_Exception, implements Horde\Exception\InvalidDataThrowable, exposes getAttribute()) naming the offending field. Messages reuse the translated Horde_Form strings. The post office box columns (object_homepob, object_workpob, object_otherpob) were a genuine schema bug - "P.O. Box 12345" never fit into VARCHAR(10). Migration 14 widens them to 255 and attributes.php raises their maxlength. Postal code columns keep their width of 10; the limit is now enforced by the validator instead of the database. Fixes #73
This was referenced Oct 26, 2026
ralflang
approved these changes
Nov 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
maxlength/regexconstraints fromattributes.phpcentrally inTurba_Driver::add()andsave(), so every write path (web form,contacts/*API, ActiveSync, CardDAV, vCard/CSV import) rejects invalid values the same way instead of failing or truncating in the database.Turba_Exception_InvalidAttributenames the offending attribute and implementsHorde\Exception\InvalidDataThrowable, so protocol handlers can report "client data rejected" instead of "server error".VARCHAR(10)cannot hold "P.O. Box 12345"); postal code columns stay at 10, now protected by the validator.Fixes #73.
Motivation
A user entered postal code and city into the ZIP field on a phone. Turba stored nothing, PostgreSQL logged
SQLSTATE[22001] value too long for type character varying(10), ActiveSync reported a generic server error, and the device resent the same contact on every Sync. On MySQL in non-strict mode the same input would have been silently truncated. The web form would have rejected it with a clear message — becauseHorde_Form_Type_text::isValid()was the only place the attribute constraints were checked.Raising the column width alone would just move the limit; the defect is that the constraints are not enforced on the write path. See the rewritten #73 for the full analysis.
Changes
src/AttributeValidator.php(new,Horde\Turba\AttributeValidator): checksparams.maxlength(viaHordeString::length) andparams.regexfor every attribute present in a value array. Skips__*keys, undefined attributes and empty values. Error messages reuse the translatedHorde_Formstrings ("Value is over the maximum length of %d." / "You must enter a valid value."), prefixed with the attribute label — no new translatable strings.lib/Exception/InvalidAttribute.php(new):extends Turba_Exception(existingcatch (Turba_Exception)blocks keep working)implements InvalidDataThrowable;getAttribute().lib/Driver.php: accepts anattribute_validatorparameter in the constructor (consumed, not kept in$_params),_validateAttributes()restricted to the attributes the source stores ($this->fields); called inadd()afterensureAttributes()and at the start ofsave(). Drivers constructed without a validator behave as before.lib/Factory/Driver.php: passes the validator from the injector when bound.lib/Application.php: bindsAttributeValidatorin_init()from the loaded$attributes, so localattributes.phpoverrides and hooks are honoured.migration/14_turba_upgrade_addresslengths.php(new):object_homepob,object_workpob,object_otherpob→string(255);down()restores 10. Column existence is guarded.config/attributes.php:maxlength10 → 255 forhomePOBox,workPOBox,otherPOBox,commonPOBox(sizeunchanged).doc/UPGRADING.rst: documents the enforced constraints and the migration.test/Turba/Unit/AttributeValidatorTest.php(new).Behaviour change
Writes that previously failed in the database (PostgreSQL) or were truncated (MySQL non-strict) now throw
Turba_Exception_InvalidAttributewith a message such asHome Postal Code: Value is over the maximum length of 10.. The web form is unaffected (it validated the same rules before the driver was reached). Writes through the API that relied on silent truncation will now fail — intentionally.Exceptions
No new
catchblocks.Turba_Exception_InvalidAttributeis aTurba_Exception, so existing handlers (API, ActiveSync driver, CardDAV, import) continue to work; the ActiveSync driver in horde/core additionally recognises theInvalidDataThrowablemarker.Dependencies
InvalidDataThrowableinterface from feat(exception): add InvalidDataThrowable marker interface Exception#6; thehorde/exceptionconstraint should be raised to the release that ships it.Status 6for rejected items; without them the item is still rejected cleanly but reported asStatus 5.Test plan
AttributeValidatorTest(6 tests): maxlength, regex, label fallback,__*/ undefined / empty values skipped, valid values passTurba_Driver_Sql:add()/save()with a 13-character postal code throwTurba_Exception_InvalidAttribute(getAttribute() === 'homePostalCode'), valid values are stored,attribute_validatordoes not leak intogetParams()turba_objects.object_homepoblimit 10 → 255 → 10)Turba_Registry::appInit('turba')binds the validator;Turba_Factory_Driver::create()hands it toTurba_Driver_Sql; the loadedattributes.phprejects12345 ExampleforhomePostalCodeand accepts a long P.O. boxhorde-db-migrate turbaon PostgreSQL / MySQL