SIP filters collab/phasefx/sip-redaction-squashed
authorJason Etheridge <jason@EquinoxOLI.org>
Thu, 30 Jun 2022 18:15:11 +0000 (14:15 -0400)
committerJason Etheridge <jason@EquinoxOLI.org>
Tue, 13 Sep 2022 14:04:17 +0000 (10:04 -0400)
* fix whitespace first
* rift between the example config here and the one in Evergreen
* fix whitespace here too
* see if we can delete SIPconfig.xml later
* the type of config options we should look for
* toward SIP filters
* more mixed use of tabs and spaces
* tests
* SIPServer specific test tweak

Signed-off-by: Jason Etheridge <jason@EquinoxOLI.org>
SIPconfig.xml
SIPconfig.xml.example.evergreen [new file with mode: 0644]
SIPconfig.xml.example.stock [new file with mode: 0644]
Sip.pm
Sip/MsgType.pm
t/16filter.t [new file with mode: 0755]
t/SIPtest.pm
t/augment_concerto.sql

index a882f41..84775a1 100644 (file)
 <institutions>
     <institution id="UWOLS" implementation="ILS" parms="">
           <policy checkin="true" renewal="false"
-                 status_update="false" offline="false"
-                 timeout="600" client_location_code="true"
-                 retries="3" />
+            status_update="false" offline="false"
+            timeout="600" client_location_code="true"
+            retries="3" />
           <relais_extensions_to_msg24 enabled="false" />
-         <encoding>ascii</encoding>
-         <phone_ext_to_msg10 enabled="false" />
+    <encoding>ascii</encoding>
+    <phone_ext_to_msg10 enabled="false" />
     </institution>
 
     <institution id="LPL" implementation="ILS">
-         <encoding>UTF-8</encoding>
+    <encoding>UTF-8</encoding>
     </institution>
 </institutions>
 </acsconfig>
diff --git a/SIPconfig.xml.example.evergreen b/SIPconfig.xml.example.evergreen
new file mode 100644 (file)
index 0000000..b8cd4ce
--- /dev/null
@@ -0,0 +1,220 @@
+<?xml version="1.0"?>
+<acsconfig xmlns="http://openncip.org/acs-config/1.0/">
+
+  <error-detect enabled="true" />
+
+  <!-- Set Net::Server::PreFork runtime parameters -->
+  <!--  <server-params
+           min_servers='1'
+           min_spare_servers='0' /> -->
+  
+  
+  <listeners>
+    <service
+      port="0:8080/tcp"
+      transport="http"
+      protocol="NCIP/1.0" />
+
+    <service
+      port="8023/tcp"
+      transport="telnet"
+      protocol="SIP/1.00"
+      timeout="60" />
+
+    <service
+      port="127.0.0.1:6001/tcp"
+      transport="RAW" 
+      protocol="SIP/2.00"
+      timeout="60" />
+  </listeners>
+
+  <accounts>
+    <!--
+    Fine Item Detail returned by the Patron Information Request is
+    manufacturer-specific.  We support the following formats:
+    3m,  Swyer_A, Swyer_B, and EG_Legacy (default).
+    Specify which treatment you want in the av_format attribute.
+    For example: <login id="sc" password="pwd" institution="main" av_format="3m">
+    -->
+    <!--
+    The login attribute patron_status_always_permit_loans specifies whether
+    the charge privileges denied, renewal privilges denied, and
+    card reported lost flags in the patron status block should be
+    coerced to permissive values regardless of the actual state
+    of the patron record. Turning this on works around an issue
+    where a 2019-12 change by the Hoopla SIP2 client takes those flag
+    fields into account, but some libraries may not wish those
+    to block a patron's access to online resources that use
+    SIP2 to authenticate. This setting can also be set as
+    an implementation_config option; note that if it is set to
+    'true' or 'false' as a login attribute, the login attribute will
+    override whatever is set in the implementation_config.
+    -->
+    <login id="scclient" password="clientpwd" institution="gapines"/>
+  </accounts>
+
+  <!-- Institution tags will hold stuff used to interface to -->
+  <!-- the rest of the ILS: authentication parameters, etc.  I -->
+  <!-- don't know what yet, so it'll just be blank.  But there -->
+  <!-- needs to be one institution stanza for each institution -->
+  <!-- named in the accounts above. -->
+  <institutions>
+
+
+    <institution id="gapines" implementation="OpenILS::SIP">
+
+      <!-- This defines what actions we want to allow 
+        remote clients (self-check machines) to perform -->
+      <policy 
+        checkin="true" 
+        checkout="true" 
+        renewal="true" 
+        status_update="false" 
+        offline="false" 
+        timeout="600" 
+        retries="3"/>
+  
+      <!-- The default encoding defined in the SIP specification is -->
+      <!-- ASCII, which isn't great for French, Spanish, Armenian. -->
+      <!-- You can specify a different encoding here, based on the -->
+      <!-- encodings supported by your SIP client and your Encode -->
+      <!-- module; run the following command to get a list of supported -->
+      <!-- encodings: -->
+      <!--   perl -MEncode -le "print for Encode->encodings(':all')" -->
+
+      <!-- UTF-8 is the recommended encoding if your SIP client supports it -->
+      <encoding>ascii</encoding>
+
+      <!--
+        When set to true, return the hold notification phone number in the patron home phone (BF) field for each Checkin Response (10).
+        This is an unsupported extention to SIP2 and potentially exposes additional user infomation, do not enable it unless required locally.
+      -->
+      <!--
+      <phone_ext_to_msg10 enabled="false" />
+      -->
+
+      <!-- implementation specific config options go here -->
+      <implementation_config>
+        <bootstrap>SYSCONFDIR/opensrf_core.xml</bootstrap>
+        <currency>USD</currency>
+
+        <!-- These defines what this SIP code has the ability to support -->
+        <supports>
+          <item name='magnetic media' value='true'/>
+          <item name='security inhibit' value='false'/>
+          <item name='offline operation' value='false'/>
+          <item name='patron status request' value='true'/>
+          <item name='checkout' value='true'/>
+          <item name='checkin' value='true'/>
+          <item name='block patron' value='true'/>
+          <item name='acs status' value='true'/>
+          <item name='login' value='true'/>
+          <item name='patron information' value='true'/>
+          <item name='end patron session' value='true'/>
+          <item name='fee paid' value='true'/>
+          <item name='item information' value='true'/>
+          <item name='item status update' value='false'/>
+          <item name='patron enable' value='false'/>
+          <item name='hold' value='false'/>
+          <item name='renew' value='true'/>
+          <item name='renew all' value='true'/>
+        </supports>
+        <options>
+          <!-- msg64, the patron information request can be
+            made to return item barcodes by setting
+            the option 'msg64_summary_datatype' to 'barcode'
+            as below. Any other value, or no value at all
+            will cause OpenILS::SIP to return the title
+            in response to a message 64 request, which was the
+            default behaviour in previous versions of Evergreen.
+          -->
+          <option name='msg64_summary_datatype' value='barcode' />
+
+
+                    <!--
+                        When set, holds will be returned to the SIP client as copy
+                        barcodes instead of title strings.  This is useful, in 
+                        particular, for making subsequent calls for hold cancellation.  
+                    -->
+                    <!--
+                    <option name='msg64_hold_datatype' value='barcode' />
+                    -->
+
+                    <!--
+                        When set, hold items details will return only available holds to the SIP client.
+                    -->
+                    <!--
+                    <option name='msg64_hold_items_available' value='true' />
+                    -->
+
+          <!--
+            If enabled, the PC field in patron-info requests will return the non-translated profile name
+          <option name='patron_type_uses_code' value='true' />
+          -->
+
+                    <!--
+                        By default, most dates use the SIP date format.  Some,
+                        like circulation due dates, use the ISO8601 date format 
+                        instead.  If this setting is set to true, all dates will
+                        use the SIP date format.
+                    <option name='use_sip_date_format' value='true' />
+                    -->
+
+          <!--
+            If enabled, return the calculated value for the recall
+            flag instead of always returning not-OK
+          <option name='patron_calculate_recal_ok' value='true' />
+          -->
+
+          <!-- see description of patron_status_always_permit_loans in the login section -->
+          <!--
+          <option name='patron_status_always_permit_loans' value='false' />
+          -->
+
+          <!--
+            Allow patrons to connect to SIP services using their
+            username in addition to their barcode.  See the
+            org unit setting 'opac.barcode_regex' for configuring
+            what constitutes a barcode vs a username.
+          <option name='support_patron_username_login' value='false' />
+          -->
+
+        </options>
+
+                <checkin_override>
+                    <event>COPY_ALERT_MESSAGE</event>
+                    <event>COPY_BAD_STATUS</event>
+                    <event>COPY_STATUS_MISSING</event>
+                    <!--
+                    <event>COPY_STATUS_LOST</event>
+                    -->
+                </checkin_override>
+
+                <checkout_override>
+                    <event>COPY_ALERT_MESSAGE</event>
+                </checkout_override>
+
+                <!-- If uncommented, SIP2 checkins will capture local holds as transits, instead of marking as ready for pickup. -->
+                <!--
+                <checkin_hold_as_transit>1</checkin_hold_as_transit>
+                -->
+
+        <filters>
+            <!--
+                Filters allow SIPServer to strip or modify specified fields inline from outgoing SIP messages.  Currently, these
+                rules apply to all message types and only work for variable-length fields, but the config layout leaves room for
+                future enhancement.
+
+                Examples:
+
+                <field identifier="AE" replace_with="John Doe" />
+                <field identifier="BE" remove="true" />
+            -->
+        </filters>
+
+      </implementation_config>
+  
+    </institution>
+  
+  </institutions>
+</acsconfig>
diff --git a/SIPconfig.xml.example.stock b/SIPconfig.xml.example.stock
new file mode 100644 (file)
index 0000000..3cef29c
--- /dev/null
@@ -0,0 +1,105 @@
+<!--
+#
+# Copyright (C) 2006-2008  Georgia Public Library Service
+# Copyright (C) 2013,2016 Equinox Software, Inc.
+# 
+# Author: David J. Fiander
+# Author: Mike Rylander
+# 
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+-->
+<acsconfig xmlns="http://openncip.org/acs-config/1.0/">
+
+  <error-detect enabled="true" />
+
+  <!-- Set Net::Server runtime parameters.  "personality" may -->
+  <!-- be either PreFork or Multiplex. -->
+  <server-params
+           personality='PreFork'
+           min_servers='1'
+           min_spare_servers='0' />
+  
+  
+  <listeners>
+    <service
+      port="0:8080/tcp"
+      transport="http"
+      protocol="NCIP/1.0" />
+
+    <service
+      port="8023/tcp"
+      transport="telnet"
+      protocol="SIP/1.00"
+      timeout="60" />
+
+    <service
+      port="127.0.0.1:6001/tcp"
+      transport="RAW" 
+      protocol="SIP/2.00"
+      allow_sc_status_then_login="disabled"
+      timeout="60" />
+  </listeners>
+
+  <!-- One or more Memecache servers are required for Multiplex mode. -->
+  <!-- Cache server(s) are ignored in non-Multiplex mode -->
+  <cache>
+    <server>127.0.0.1:11211</server>
+  </cache>
+
+  <accounts>
+      <login id="scclient" password="clientpwd" institution="UWOLS">
+      </login>
+      <login id="scclient-2" password="clientpwd-2"
+             institution="UWOLS" />
+      <login id="lpl-sc" password="1234" institution="LPL" />
+      <login id="lpl-sc-beacock" password="xyzzy" location_code="WORKSTATION5"
+             delimiter="|" error-detect="enabled" institution="LPL" />
+  </accounts>
+
+<!-- Institution tags will hold stuff used to interface to -->
+<!-- the rest of the ILS: authentication parameters, etc.  I -->
+<!-- don't know what yet, so it'll just be blank.  But there -->
+<!-- needs to be one institution stanza for each institution -->
+<!-- named in the accounts above. -->
+<institutions>
+    <institution id="UWOLS" implementation="ILS" parms="">
+          <policy checkin="true" renewal="false"
+            status_update="false" offline="false"
+            timeout="600" client_location_code="true"
+            retries="3" />
+          <relais_extensions_to_msg24 enabled="false" />
+    <encoding>ascii</encoding>
+    <phone_ext_to_msg10 enabled="false" />
+    </institution>
+
+    <institution id="LPL" implementation="ILS">
+        <encoding>UTF-8</encoding>
+        <implementation_config>
+            <filters>
+                <!--
+                    Filters allow SIPServer to strip or modify specified fields inline from outgoing SIP messages.  Currently, these
+                    rules apply to all message types and only work for variable-length fields, but the config layout leaves room for
+                    future enhancement.
+
+                    Examples:
+
+                    <field identifier="AE" replace_with="John Doe" />
+                    <field identifier="BE" remove="true" />
+                -->
+            </filters>
+        </implementation_config>
+    </institution>
+</institutions>
+</acsconfig>
diff --git a/Sip.pm b/Sip.pm
index cf9e575..762e8fb 100644 (file)
--- a/Sip.pm
+++ b/Sip.pm
@@ -32,28 +32,28 @@ use Sys::Syslog qw(syslog);
 use POSIX qw(strftime);
 use Socket qw(:crlf);
 
-use Sip::Constants qw(SIP_DATETIME);
+use Sip::Constants qw(:all);
 use Sip::Checksum qw(checksum);
 
 our $VERSION = 0.02;
 our @ISA = qw(Exporter);
 
 our @EXPORT_OK = qw(y_or_n timestamp add_field maybe_add add_count
-                   denied sipbool boolspace write_msg read_SIP_packet
-                   $error_detection $protocol_version $field_delimiter
-                   $last_response);
+        denied sipbool boolspace write_msg read_SIP_packet
+        $error_detection $protocol_version $field_delimiter
+        $last_response);
 
 our %EXPORT_TAGS = (
-                   all => [qw(y_or_n timestamp add_field maybe_add
-                              add_count denied sipbool boolspace write_msg
-                              read_SIP_packet
-                              $error_detection $protocol_version
-                              $field_delimiter $last_response)]);
+        all => [qw(y_or_n timestamp add_field maybe_add
+            add_count denied sipbool boolspace write_msg
+            read_SIP_packet
+            $error_detection $protocol_version
+            $field_delimiter $last_response)]);
 
 
 our $error_detection = 0;
 our $protocol_version = 1;
-our $field_delimiter = '|';    # Protocol Default
+our $field_delimiter = '|';     # Protocol Default
 
 # We need to keep a copy of the last message we sent to the SC,
 # in case there's a transmission error and the SC sends us a
@@ -69,17 +69,17 @@ sub timestamp {
 }
 
 #
-# add_field(field_id, value)
+# add_field(field_id, value, filters)
 #    return constructed field value
 #
 sub add_field {
-    my ($field_id, $value) = @_;
+    my ($field_id, $value, $filters) = @_;
     my ($i, $ent);
 
     if (!defined($value)) {
-       syslog("LOG_DEBUG", "add_field: Undefined value being added to '%s'",
-              $field_id);
-       $value = '';
+        syslog("LOG_DEBUG", "add_field: Undefined value being added to '%s'",
+            $field_id);
+        $value = '';
     }
 
     # Replace any occurences of the field delimiter in the
@@ -87,7 +87,7 @@ sub add_field {
     $ent = sprintf("&#%d;", ord($field_delimiter));
 
     while (($i = index($value, $field_delimiter)) != ($[-1)) {
-       substr($value, $i, 1) = $ent;
+        substr($value, $i, 1) = $ent;
     }
 
     # SIP2 Protocol document specifies that variable fields are from 0
@@ -97,17 +97,44 @@ sub add_field {
         $value = substr($value, 0, 255);
     }
 
+    # on the fly field munging...
+    if (defined $filters) {
+        # filters v1 = $VAR1 = { 'field' => { 'identifier' => 'AE',  'replace_with' => 'John Doe' } };
+        # filters v1 = $VAR1 = { 'field' => { 'identifier' => 'AE',  'remove' => 'true' } };
+        # filters v1 = $VAR1 = { 'field' => [ { 'identifier' => 'AE', 'replace_with' => 'John Doe' }, { 'replace_with' => 'Jane Doe', 'identifier' => 'AE' } ] };
+        my $field_configs = $filters->{field};
+        $field_configs = [$field_configs] unless ref $field_configs eq 'ARRAY';
+        my @relevant_field_configs = grep { $_->{identifier} eq $field_id } @$field_configs;
+        if (@relevant_field_configs) {
+
+            # for now, since we can't do anything complicated, let's just take the first matching field configuration
+            my $field_config = $relevant_field_configs[0];
+
+            # custom field value
+            if (defined $field_config->{replace_with}) {
+                $value = $field_config->{replace_with};
+            }
+
+            # whole field stripping (id and value)
+            my $remove = $field_config->{remove};
+            if (defined($remove) && (($remove =~ /true|y|yes/i) || $remove != 0)) { # test truthiness
+                return '';
+            }
+
+        }
+    }
+
     return $field_id . $value . $field_delimiter;
 }
 #
-# maybe_add(field_id, value):
+# maybe_add(field_id, value, filters):
 #    If value is defined and non-empty, then return the
 #    constructed field value, otherwise return the empty string.
 #    NOTE: if zero is a valid value for your field, don't use maybe_add!
 #
 sub maybe_add {
-    my ($fid, $value) = @_;
-    return (defined($value) && $value) ? add_field($fid, $value) : '';
+    my ($fid, $value, $filters) = @_;
+    return (defined($value) && $value) ? add_field($fid, $value, $filters) : '';
 }
 
 #
@@ -121,14 +148,14 @@ sub add_count {
     # If the field is unsupported, it will be undef, return blanks
     # as per the spec.
     if (!defined($count)) {
-       return ' ' x 4;
+        return ' ' x 4;
     }
 
     $count = sprintf("%04d", $count);
     if (length($count) != 4) {
-       syslog("LOG_WARNING", "handle_patron_info: %s wrong size: '%s'",
-              $label, $count);
-       $count = ' ' x 4;
+        syslog("LOG_WARNING", "handle_patron_info: %s wrong size: '%s'",
+            $label, $count);
+        $count = ' ' x 4;
     }
     return $count;
 }
@@ -273,7 +300,7 @@ sub write_msg {
         syslog("LOG_ERR", "Error writing to STDOUT $!") unless $rv;
     }
 
-    syslog("LOG_INFO", "OUTPUT MSG: '$msg'");
+    syslog("LOG_INFO", "OUTPUT MSG2: '$msg'");
     $last_response = $msg;
 }
 
index c3cd3e0..bdf3f0c 100644 (file)
@@ -433,6 +433,7 @@ sub handle {
     return($self->{handler}->($self, $server));
 }
 
+#
 ##
 ## Message Handlers
 ##
@@ -461,40 +462,40 @@ sub build_patron_status {
 
        # while the patron ID we got from the SC is valid, let's
        # use the one returned from the ILS, just in case...
-       $resp .= add_field(FID_INST_ID, $fields->{(FID_INST_ID)});
-       $resp .= add_field(FID_PATRON_ID, $patron->id);
-       $resp .= add_field(FID_PERSONAL_NAME, $patron->name);
+       $resp .= add_field(FID_INST_ID, $fields->{(FID_INST_ID)}, $server->{filters});
+       $resp .= add_field(FID_PATRON_ID, $patron->id, $server->{filters});
+       $resp .= add_field(FID_PERSONAL_NAME, $patron->name, $server->{filters});
        if ($protocol_version >= 2) {
-           $resp .= add_field(FID_VALID_PATRON, 'Y');
+           $resp .= add_field(FID_VALID_PATRON, 'Y', $server->{filters});
            # Patron password is a required field.
-               $resp .= add_field(FID_VALID_PATRON_PWD, sipbool($patron->check_password($patron_pwd)));
-           $resp .= maybe_add(FID_CURRENCY, $patron->currency);
-           $resp .= maybe_add(FID_FEE_AMT, $patron->fee_amount);
+               $resp .= add_field(FID_VALID_PATRON_PWD, sipbool($patron->check_password($patron_pwd)), $server->{filters});
+           $resp .= maybe_add(FID_CURRENCY, $patron->currency, $server->{filters});
+           $resp .= maybe_add(FID_FEE_AMT, $patron->fee_amount, $server->{filters});
 
                # Relais extensions
                if ($server->{institution}->relais_extensions_to_msg24()) {
-                       $resp .= maybe_add(FID_HOME_ADDR,  $patron->address   );
-                       $resp .= maybe_add(FID_EMAIL,      $patron->email_addr);
-                       $resp .= maybe_add(FID_HOME_PHONE, $patron->home_phone);
+                       $resp .= maybe_add(FID_HOME_ADDR,  $patron->address   , $server->{filters});
+                       $resp .= maybe_add(FID_EMAIL,      $patron->email_addr, $server->{filters});
+                       $resp .= maybe_add(FID_HOME_PHONE, $patron->home_phone, $server->{filters});
                }
 
        }
 
-       $resp .= maybe_add(FID_SCREEN_MSG, $patron->screen_msg);
-       $resp .= maybe_add(FID_PRINT_LINE, $patron->print_line);
+       $resp .= maybe_add(FID_SCREEN_MSG, $patron->screen_msg, $server->{filters});
+       $resp .= maybe_add(FID_PRINT_LINE, $patron->print_line, $server->{filters});
     } else {
        # Invalid patron id.  Report that the user has no privs.,
        # no personal name, and is invalid (if we're using 2.00)
        $resp .= 'YYYY' . (' ' x 10) . $lang . Sip::timestamp();
-       $resp .= add_field(FID_INST_ID, $fields->{(FID_INST_ID)});
+       $resp .= add_field(FID_INST_ID, $fields->{(FID_INST_ID)}, $server->{filters});
 
        # the patron ID is invalid, but it's a required field, so
        # just echo it back
-       $resp .= add_field(FID_PATRON_ID, $fields->{(FID_PATRON_ID)});
-       $resp .= add_field(FID_PERSONAL_NAME, '');
+       $resp .= add_field(FID_PATRON_ID, $fields->{(FID_PATRON_ID)}, $server->{filters});
+       $resp .= add_field(FID_PERSONAL_NAME, '', $server->{filters});
 
        if ($protocol_version >= 2) {
-           $resp .= add_field(FID_VALID_PATRON, 'N');
+           $resp .= add_field(FID_VALID_PATRON, 'N', $server->{filters});
        }
     }
 
@@ -578,29 +579,29 @@ sub handle_checkout {
        $resp .= Sip::timestamp;
 
        # Now for the variable fields
-       $resp .= add_field(FID_INST_ID,  $inst);
-       $resp .= add_field(FID_PATRON_ID, $patron_id);
-       $resp .= add_field(FID_ITEM_ID,  $item_id);
-       $resp .= add_field(FID_TITLE_ID, $item->title_id);
-       $resp .= add_field(FID_DUE_DATE, $item->due_date);
+       $resp .= add_field(FID_INST_ID,  $inst, $server->{filters});
+       $resp .= add_field(FID_PATRON_ID, $patron_id, $server->{filters});
+       $resp .= add_field(FID_ITEM_ID,  $item_id, $server->{filters});
+       $resp .= add_field(FID_TITLE_ID, $item->title_id, $server->{filters});
+       $resp .= add_field(FID_DUE_DATE, $item->due_date, $server->{filters});
 
-       $resp .= maybe_add(FID_SCREEN_MSG, $status->screen_msg);
-       $resp .= maybe_add(FID_PRINT_LINE, $status->print_line);
+       $resp .= maybe_add(FID_SCREEN_MSG, $status->screen_msg, $server->{filters});
+       $resp .= maybe_add(FID_PRINT_LINE, $status->print_line, $server->{filters});
 
        if ($protocol_version >= 2) {
            if ($ils->supports('security inhibit')) {
-               $resp .= add_field(FID_SECURITY_INHIBIT, $status->security_inhibit);
+               $resp .= add_field(FID_SECURITY_INHIBIT, $status->security_inhibit, $server->{filters});
            }
-           $resp .= maybe_add(FID_MEDIA_TYPE, $item->sip_media_type);
-           $resp .= maybe_add(FID_ITEM_PROPS, $item->sip_item_properties);
+           $resp .= maybe_add(FID_MEDIA_TYPE, $item->sip_media_type, $server->{filters});
+           $resp .= maybe_add(FID_ITEM_PROPS, $item->sip_item_properties, $server->{filters});
 
            # Financials
            if ($status->fee_amount) {
-               $resp .= add_field(FID_FEE_AMT,  $status->fee_amount);
-               $resp .= maybe_add(FID_CURRENCY, $status->sip_currency);
-               $resp .= maybe_add(FID_FEE_TYPE, $status->sip_fee_type);
+               $resp .= add_field(FID_FEE_AMT,  $status->fee_amount, $server->{filters});
+               $resp .= maybe_add(FID_CURRENCY, $status->sip_currency, $server->{filters});
+               $resp .= maybe_add(FID_FEE_TYPE, $status->sip_fee_type, $server->{filters});
                $resp .= maybe_add(FID_TRANSACTION_ID,
-                                  $status->transaction_id);
+                                  $status->transaction_id, $server->{filters});
            }
        }
 
@@ -609,35 +610,35 @@ sub handle_checkout {
        # Checkout Response: not ok, no renewal, don't know mag. media,
        # no desensitize
        $resp = sprintf("120%sUN%s", sipbool($status->renew_ok), Sip::timestamp);
-       $resp .= add_field(FID_INST_ID, $inst);
-       $resp .= add_field(FID_PATRON_ID, $patron_id);
-       $resp .= add_field(FID_ITEM_ID, $item_id);
+       $resp .= add_field(FID_INST_ID, $inst, $server->{filters});
+       $resp .= add_field(FID_PATRON_ID, $patron_id, $server->{filters});
+       $resp .= add_field(FID_ITEM_ID, $item_id, $server->{filters});
 
        # If the item is valid, provide the title, otherwise
        # leave it blank
-       $resp .= add_field(FID_TITLE_ID, $item ? $item->title_id : '');
+       $resp .= add_field(FID_TITLE_ID, $item ? $item->title_id : '', $server->{filters});
        # Due date is required.  Since it didn't get checked out,
        # it's not due, so leave the date blank
-       $resp .= add_field(FID_DUE_DATE, '');
+       $resp .= add_field(FID_DUE_DATE, '', $server->{filters});
 
-       $resp .= maybe_add(FID_SCREEN_MSG, $status->screen_msg);
-       $resp .= maybe_add(FID_PRINT_LINE, $status->print_line);
+       $resp .= maybe_add(FID_SCREEN_MSG, $status->screen_msg, $server->{filters});
+       $resp .= maybe_add(FID_PRINT_LINE, $status->print_line, $server->{filters});
 
        if ($protocol_version >= 2) {
            # Is the patron ID valid?
-           $resp .= add_field(FID_VALID_PATRON, sipbool($patron));
+           $resp .= add_field(FID_VALID_PATRON, sipbool($patron), $server->{filters});
 
            if ($patron && exists($fields->{FID_PATRON_PWD})) {
                # Password provided, so we can tell if it was valid or not
                $resp .= add_field(FID_VALID_PATRON_PWD,
-                                  sipbool($patron->check_password($fields->{(FID_PATRON_PWD)})));
+                                  sipbool($patron->check_password($fields->{(FID_PATRON_PWD)})), $server->{filters});
            }
             # For the patron to accept a fee in chargeable loans, we
             # need to return fee information.
            if ($status->fee_amount) {
-               $resp .= add_field(FID_FEE_AMT,  $status->fee_amount);
-               $resp .= maybe_add(FID_CURRENCY, $status->sip_currency);
-               $resp .= maybe_add(FID_FEE_TYPE, $status->sip_fee_type);
+               $resp .= add_field(FID_FEE_AMT,  $status->fee_amount, $server->{filters});
+               $resp .= maybe_add(FID_CURRENCY, $status->sip_currency, $server->{filters});
+               $resp .= maybe_add(FID_FEE_TYPE, $status->sip_fee_type, $server->{filters});
             }
        }
     }
@@ -687,36 +688,36 @@ sub handle_checkin {
     }
     $resp .= $status->alert ? 'Y' : 'N';
     $resp .= Sip::timestamp;
-    $resp .= add_field(FID_INST_ID, $inst_id);
-    $resp .= add_field(FID_ITEM_ID, $item_id);
+    $resp .= add_field(FID_INST_ID, $inst_id, $server->{filters});
+    $resp .= add_field(FID_ITEM_ID, $item_id, $server->{filters});
 
     if ($item) {
-        $resp .= add_field(FID_PERM_LOCN, $item->permanent_location);
-        $resp .= maybe_add(FID_TITLE_ID, $item->title_id);
+        $resp .= add_field(FID_PERM_LOCN, $item->permanent_location, $server->{filters});
+        $resp .= maybe_add(FID_TITLE_ID, $item->title_id, $server->{filters});
     }
 
     if ($protocol_version >= 2) {
-        $resp .= maybe_add(FID_SORT_BIN, $status->sort_bin);
+        $resp .= maybe_add(FID_SORT_BIN, $status->sort_bin, $server->{filters});
         if ($patron) {
-            $resp .= add_field(FID_PATRON_ID, $patron->id);
+            $resp .= add_field(FID_PATRON_ID, $patron->id, $server->{filters});
         }
         if ($item) {
-            $resp .= maybe_add(FID_MEDIA_TYPE,           $item->sip_media_type     );
-            $resp .= maybe_add(FID_ITEM_PROPS,           $item->sip_item_properties);
-            $resp .= maybe_add(FID_COLLECTION_CODE,      $item->collection_code    );
-            $resp .= maybe_add(FID_CALL_NUMBER,          $item->call_number        );
-            $resp .= maybe_add(FID_DESTINATION_LOCATION, $item->destination_loc    );
-            $resp .= maybe_add(FID_HOLD_PATRON_ID,       $item->hold_patron_bcode  );
-            $resp .= maybe_add(FID_HOLD_PATRON_NAME,     $item->hold_patron_name   );
+            $resp .= maybe_add(FID_MEDIA_TYPE,           $item->sip_media_type     , $server->{filters});
+            $resp .= maybe_add(FID_ITEM_PROPS,           $item->sip_item_properties, $server->{filters});
+            $resp .= maybe_add(FID_COLLECTION_CODE,      $item->collection_code    , $server->{filters});
+            $resp .= maybe_add(FID_CALL_NUMBER,          $item->call_number        , $server->{filters});
+            $resp .= maybe_add(FID_DESTINATION_LOCATION, $item->destination_loc    , $server->{filters});
+            $resp .= maybe_add(FID_HOLD_PATRON_ID,       $item->hold_patron_bcode  , $server->{filters});
+            $resp .= maybe_add(FID_HOLD_PATRON_NAME,     $item->hold_patron_name   , $server->{filters});
             if ($server->{institution}->phone_ext_to_msg10()) {
-                $resp .= maybe_add(FID_HOME_PHONE,           $item->hold_patron_phone  );
+                $resp .= maybe_add(FID_HOME_PHONE,           $item->hold_patron_phone  , $server->{filters});
             }
         }
     }
 
-    $resp .= maybe_add(FID_ALERT_TYPE, $status->alert_type) if $status->alert;
-    $resp .= maybe_add(FID_SCREEN_MSG, $status->screen_msg);
-    $resp .= maybe_add(FID_PRINT_LINE, $status->print_line);
+    $resp .= maybe_add(FID_ALERT_TYPE, $status->alert_type, $server->{filters}) if $status->alert;
+    $resp .= maybe_add(FID_SCREEN_MSG, $status->screen_msg, $server->{filters});
+    $resp .= maybe_add(FID_PRINT_LINE, $status->print_line, $server->{filters});
 
     $self->write_msg($resp, undef, $server->{encoding});
 
@@ -897,6 +898,8 @@ sub _load_ils_handler {
     $server->{institution} = $server->{config}->{institutions}->{$inst};
     $server->{policy}      = $server->{institution}->{policy};
     $server->{account}->{location} = $sc_loc if $sc_loc;
+    $server->{filters} = $server->{institution}->{implementation_config}->{filters} || {};
+
     # Set the encoding for responses messages.
     $server->{encoding} = $server->{account}->{encoding}
         || $server->{institution}->{encoding}
@@ -937,7 +940,7 @@ sub _load_ils_handler {
 # and we're going to believe it.
 #
 sub summary_info {
-    my ($ils, $patron, $summary, $start, $end) = @_;
+    my ($server, $ils, $patron, $summary, $start, $end) = @_;
     my $resp = '';
     my $itemlist;
     my $summary_type;
@@ -976,7 +979,7 @@ sub summary_info {
 
     syslog("LOG_DEBUG", "summary_info: list = (%s)", join(", ", @{$itemlist}));
     foreach my $i (@{$itemlist}) {
-        $resp .= add_field($fid, $i);
+        $resp .= add_field($fid, $i, $server->{filters});
     }
 
     return $resp;
@@ -1014,13 +1017,13 @@ sub handle_patron_info {
         $resp .= add_count('patron_info/recall_items',  scalar @{$patron->recall_items(undef,undef,1) });
         $resp .= add_count('patron_info/unavail_holds', scalar @{$patron->unavail_holds(undef,undef,1)});
 
-        $resp .= add_field(FID_INST_ID, $server->{ils}->institution);
+        $resp .= add_field(FID_INST_ID, $server->{ils}->institution, $server->{filters});
 
         # while the patron ID we got from the SC is valid, let's
         # use the one returned from the ILS, just in case...
-        $resp .= add_field(FID_PATRON_ID, $patron->id);
+        $resp .= add_field(FID_PATRON_ID, $patron->id, $server->{filters});
 
-        $resp .= add_field(FID_PERSONAL_NAME, $patron->name);
+        $resp .= add_field(FID_PERSONAL_NAME, $patron->name, $server->{filters});
 
         # TODO: add code for the fields
         #    hold items limit
@@ -1028,50 +1031,50 @@ sub handle_patron_info {
         # charged items limit
         #           fee limit
 
-        $resp .= maybe_add(FID_CURRENCY,   $patron->currency  );
-        $resp .= maybe_add(FID_FEE_AMT,    $patron->fee_amount);
-        $resp .= maybe_add(FID_HOME_ADDR,  $patron->address   );
-        $resp .= maybe_add(FID_EMAIL,      $patron->email_addr);
-        $resp .= maybe_add(FID_HOME_PHONE, $patron->home_phone);
+        $resp .= maybe_add(FID_CURRENCY,   $patron->currency  , $server->{filters});
+        $resp .= maybe_add(FID_FEE_AMT,    $patron->fee_amount, $server->{filters});
+        $resp .= maybe_add(FID_HOME_ADDR,  $patron->address   , $server->{filters});
+        $resp .= maybe_add(FID_EMAIL,      $patron->email_addr, $server->{filters});
+        $resp .= maybe_add(FID_HOME_PHONE, $patron->home_phone, $server->{filters});
 
         # Extension requested by PINES. Report the home system for
         # the patron in the 'AQ' field. This is normally the "permanent
         # location" field for an ITEM, but it's not used in PATRON info.
         # Apparently TLC systems do this.
-        $resp .= maybe_add(FID_HOME_LIBRARY, $patron->home_library);
+        $resp .= maybe_add(FID_HOME_LIBRARY, $patron->home_library, $server->{filters});
 
-        $resp .= summary_info($ils, $patron, $summary, $start, $end);
+        $resp .= summary_info($server, $ils, $patron, $summary, $start, $end);
 
-        $resp .= add_field(FID_VALID_PATRON, 'Y');
+        $resp .= add_field(FID_VALID_PATRON, 'Y', $server->{filters});
         if (defined($patron_pwd)) {
                # If the patron password was provided, report on if it was right.
             $resp .= add_field(FID_VALID_PATRON_PWD,
-                              sipbool($patron->check_password($patron_pwd)));
+                              sipbool($patron->check_password($patron_pwd)), $server->{filters});
         }
 
         # SIP 2.0 extensions used by Envisionware
         # Other types of terminals will ignore the fields, if
         # they don't recognize the codes
         if ($patron->can('sip_expire')) {
-            $resp .= maybe_add(FID_PATRON_EXPIRE, $patron->sip_expire);
+            $resp .= maybe_add(FID_PATRON_EXPIRE, $patron->sip_expire, $server->{filters});
         }
-        $resp .= maybe_add(FID_PATRON_BIRTHDATE, $patron->sip_birthdate);
-        $resp .= maybe_add(FID_PATRON_CLASS, $patron->ptype);
+        $resp .= maybe_add(FID_PATRON_BIRTHDATE, $patron->sip_birthdate, $server->{filters});
+        $resp .= maybe_add(FID_PATRON_CLASS, $patron->ptype, $server->{filters});
 
         # Custom protocol extension to report patron internet privileges
-        $resp .= maybe_add(FID_INET_PROFILE, $patron->inet_privileges);
+        $resp .= maybe_add(FID_INET_PROFILE, $patron->inet_privileges, $server->{filters});
 
-        $resp .= maybe_add(FID_PATRON_INTERNAL_ID, $patron->internal_id);   # another extension
+        $resp .= maybe_add(FID_PATRON_INTERNAL_ID, $patron->internal_id, $server->{filters});   # another extension
 
-        $resp .= maybe_add(FID_SCREEN_MSG, $patron->screen_msg);
-        $resp .= maybe_add(FID_PRINT_LINE, $patron->print_line);
+        $resp .= maybe_add(FID_SCREEN_MSG, $patron->screen_msg, $server->{filters});
+        $resp .= maybe_add(FID_PRINT_LINE, $patron->print_line, $server->{filters});
 
         # Custom ILS-defined protocol extensions
         if ($patron->can('extra_fields')) {
             my $extra_fields = $patron->extra_fields();
             foreach my $field (keys %$extra_fields) {
                 foreach my $value (@{$extra_fields->{ $field }}) {
-                    $resp .= maybe_add($field, $value);
+                    $resp .= maybe_add($field, $value, $server->{filters});
                 }
             }
         }
@@ -1082,14 +1085,14 @@ sub handle_patron_info {
         $resp .= 'YYYY' . (' ' x 10) . $lang . Sip::timestamp();
         $resp .= '0000' x 6;
 
-        $resp .= add_field(FID_INST_ID, $server->{ils}->institution);
+        $resp .= add_field(FID_INST_ID, $server->{ils}->institution, $server->{filters});
         # the patron ID is invalid, but it's a required field, so
         # just echo it back
-        $resp .= add_field(FID_PATRON_ID, $fields->{(FID_PATRON_ID)});
-        $resp .= add_field(FID_PERSONAL_NAME, '');
+        $resp .= add_field(FID_PATRON_ID, $fields->{(FID_PATRON_ID)}, $server->{filters});
+        $resp .= add_field(FID_PERSONAL_NAME, '', $server->{filters});
 
         if ($protocol_version >= 2) {
-            $resp .= add_field(FID_VALID_PATRON, 'N');
+            $resp .= add_field(FID_VALID_PATRON, 'N', $server->{filters});
         }
     }
 
@@ -1115,11 +1118,11 @@ sub handle_end_patron_session {
     $resp .= $status ? 'Y' : 'N';
     $resp .= Sip::timestamp();
 
-    $resp .= add_field(FID_INST_ID, $server->{ils}->institution);
-    $resp .= add_field(FID_PATRON_ID, $fields->{(FID_PATRON_ID)});
+    $resp .= add_field(FID_INST_ID, $server->{ils}->institution, $server->{filters});
+    $resp .= add_field(FID_PATRON_ID, $fields->{(FID_PATRON_ID)}, $server->{filters});
 
-    $resp .= maybe_add(FID_SCREEN_MSG, $screen_msg);
-    $resp .= maybe_add(FID_PRINT_LINE, $print_line);
+    $resp .= maybe_add(FID_SCREEN_MSG, $screen_msg, $server->{filters});
+    $resp .= maybe_add(FID_PRINT_LINE, $print_line, $server->{filters});
 
     $self->write_msg($resp, undef, $server->{encoding});
 
@@ -1149,11 +1152,11 @@ sub handle_fee_paid {
                           $pay_type, $fee_id, $trans_id, $currency);
 
     $resp .= ($status->ok ? 'Y' : 'N') . Sip::timestamp;
-    $resp .= add_field(FID_INST_ID, $inst_id);
-    $resp .= add_field(FID_PATRON_ID, $patron_id);
-    $resp .= maybe_add(FID_TRANSACTION_ID, $status->transaction_id);
-    $resp .= maybe_add(FID_SCREEN_MSG, $status->screen_msg);
-    $resp .= maybe_add(FID_PRINT_LINE, $status->print_line);
+    $resp .= add_field(FID_INST_ID, $inst_id, $server->{filters});
+    $resp .= add_field(FID_PATRON_ID, $patron_id, $server->{filters});
+    $resp .= maybe_add(FID_TRANSACTION_ID, $status->transaction_id, $server->{filters});
+    $resp .= maybe_add(FID_SCREEN_MSG, $status->screen_msg, $server->{filters});
+    $resp .= maybe_add(FID_PRINT_LINE, $status->print_line, $server->{filters});
 
     $self->write_msg($resp, undef, $server->{encoding});
 
@@ -1180,9 +1183,9 @@ sub handle_item_information {
         $resp .= "010101";
         $resp .= Sip::timestamp;
         # Just echo back the invalid item id
-        $resp .= add_field(FID_ITEM_ID, $fields->{(FID_ITEM_ID)});
+        $resp .= add_field(FID_ITEM_ID, $fields->{(FID_ITEM_ID)}, $server->{filters});
         # title id is required, but we don't have one
-        $resp .= add_field(FID_TITLE_ID, '');
+        $resp .= add_field(FID_TITLE_ID, '', $server->{filters});
     } else {
         # Valid Item ID, send the good stuff
         $resp .= $item->sip_circulation_status;
@@ -1190,34 +1193,34 @@ sub handle_item_information {
         $resp .= $item->sip_fee_type;
         $resp .= Sip::timestamp;
 
-        $resp .= add_field(FID_ITEM_ID,  $item->id);
-        $resp .= add_field(FID_TITLE_ID, $item->title_id);
+        $resp .= add_field(FID_ITEM_ID,  $item->id, $server->{filters});
+        $resp .= add_field(FID_TITLE_ID, $item->title_id, $server->{filters});
 
-        $resp .= maybe_add(FID_MEDIA_TYPE,   $item->sip_media_type);
-        $resp .= maybe_add(FID_PERM_LOCN,    $item->permanent_location);
-        $resp .= maybe_add(FID_CURRENT_LOCN, $item->current_location);
-        $resp .= maybe_add(FID_ITEM_PROPS,   $item->sip_item_properties);
+        $resp .= maybe_add(FID_MEDIA_TYPE,   $item->sip_media_type, $server->{filters});
+        $resp .= maybe_add(FID_PERM_LOCN,    $item->permanent_location, $server->{filters});
+        $resp .= maybe_add(FID_CURRENT_LOCN, $item->current_location, $server->{filters});
+        $resp .= maybe_add(FID_ITEM_PROPS,   $item->sip_item_properties, $server->{filters});
 
         if ($item->fee) {
-            $resp .= add_field(FID_CURRENCY, $item->fee_currency);
-            $resp .= add_field(FID_FEE_AMT,  $item->fee);
+            $resp .= add_field(FID_CURRENCY, $item->fee_currency, $server->{filters});
+            $resp .= add_field(FID_FEE_AMT,  $item->fee, $server->{filters});
         }
-        $resp .= maybe_add(FID_OWNER,            $item->owner);
-        $resp .= maybe_add(FID_HOLD_QUEUE_LEN,   scalar @{$item->hold_queue});
-        $resp .= maybe_add(FID_DUE_DATE,         $item->due_date);
-        $resp .= maybe_add(FID_RECALL_DATE,      $item->recall_date);
-        $resp .= maybe_add(FID_HOLD_PICKUP_DATE, $item->hold_pickup_date);
-        $resp .= maybe_add(FID_DESTINATION_LOCATION, $item->destination_loc);  # Extension for AMH sorting
-        $resp .= maybe_add(FID_CALL_NUMBER,      $item->call_number);          # Extension for AMH sorting
-        $resp .= maybe_add(FID_SCREEN_MSG,       $item->screen_msg);
-        $resp .= maybe_add(FID_PRINT_LINE,       $item->print_line);
+        $resp .= maybe_add(FID_OWNER,            $item->owner, $server->{filters});
+        $resp .= maybe_add(FID_HOLD_QUEUE_LEN,   scalar @{$item->hold_queue}, $server->{filters});
+        $resp .= maybe_add(FID_DUE_DATE,         $item->due_date, $server->{filters});
+        $resp .= maybe_add(FID_RECALL_DATE,      $item->recall_date, $server->{filters});
+        $resp .= maybe_add(FID_HOLD_PICKUP_DATE, $item->hold_pickup_date, $server->{filters});
+        $resp .= maybe_add(FID_DESTINATION_LOCATION, $item->destination_loc, $server->{filters});  # Extension for AMH sorting
+        $resp .= maybe_add(FID_CALL_NUMBER,      $item->call_number, $server->{filters});          # Extension for AMH sorting
+        $resp .= maybe_add(FID_SCREEN_MSG,       $item->screen_msg, $server->{filters});
+        $resp .= maybe_add(FID_PRINT_LINE,       $item->print_line, $server->{filters});
 
         # Custom ILS-defined protocol extensions
         if ($item->can('extra_fields')) {
             my $extra_fields = $item->extra_fields();
             foreach my $field (keys %$extra_fields) {
                 foreach my $value (@{$extra_fields->{ $field }}) {
-                    $resp .= maybe_add($field, $value);
+                    $resp .= maybe_add($field, $value, $server->{filters});
                 }
             }
         }
@@ -1254,7 +1257,7 @@ sub handle_item_status_update {
         # Invalid Item ID
         $resp .= '0';
         $resp .= Sip::timestamp;
-        $resp .= add_field(FID_ITEM_ID, $item_id);
+        $resp .= add_field(FID_ITEM_ID, $item_id, $server->{filters});
     } else {
         # Valid Item ID
         $status = $item->status_update($item_props);
@@ -1262,13 +1265,13 @@ sub handle_item_status_update {
         $resp .= $status->ok ? '1' : '0';
         $resp .= Sip::timestamp;
 
-        $resp .= add_field(FID_ITEM_ID,    $item->id);
-        $resp .= add_field(FID_TITLE_ID,   $item->title_id);
-        $resp .= maybe_add(FID_ITEM_PROPS, $item->sip_item_properties);
+        $resp .= add_field(FID_ITEM_ID,    $item->id, $server->{filters});
+        $resp .= add_field(FID_TITLE_ID,   $item->title_id, $server->{filters});
+        $resp .= maybe_add(FID_ITEM_PROPS, $item->sip_item_properties, $server->{filters});
     }
 
-    $resp .= maybe_add(FID_SCREEN_MSG, $status->screen_msg);
-    $resp .= maybe_add(FID_PRINT_LINE, $status->print_line);
+    $resp .= maybe_add(FID_SCREEN_MSG, $status->screen_msg, $server->{filters});
+    $resp .= maybe_add(FID_PRINT_LINE, $status->print_line, $server->{filters});
 
     $self->write_msg($resp, undef, $server->{encoding});
 
@@ -1295,10 +1298,10 @@ sub handle_patron_enable {
     if (!defined($patron)) {
         # Invalid patron ID
         $resp .= 'YYYY' . (' ' x 10) . '000' . Sip::timestamp();
-        $resp .= add_field(FID_PATRON_ID, $patron_id);
-        $resp .= add_field(FID_PERSONAL_NAME,    '' );
-        $resp .= add_field(FID_VALID_PATRON,     'N');
-        $resp .= add_field(FID_VALID_PATRON_PWD, 'N');
+        $resp .= add_field(FID_PATRON_ID, $patron_id, $server->{filters});
+        $resp .= add_field(FID_PERSONAL_NAME,    '' , $server->{filters});
+        $resp .= add_field(FID_VALID_PATRON,     'N', $server->{filters});
+        $resp .= add_field(FID_VALID_PATRON_PWD, 'N', $server->{filters});
     } else {
         # valid patron
         if (!defined($patron_pwd) || $patron->check_password($patron_pwd)) {
@@ -1308,18 +1311,18 @@ sub handle_patron_enable {
         $resp .= patron_status_string($patron);
         $resp .= $patron->language . Sip::timestamp();
 
-        $resp .= add_field(FID_PATRON_ID,     $patron->id);
-        $resp .= add_field(FID_PERSONAL_NAME, $patron->name);
+        $resp .= add_field(FID_PATRON_ID,     $patron->id, $server->{filters});
+        $resp .= add_field(FID_PERSONAL_NAME, $patron->name, $server->{filters});
         if (defined($patron_pwd)) {
             $resp .= add_field(FID_VALID_PATRON_PWD,
-                       sipbool($patron->check_password($patron_pwd)));
+                       sipbool($patron->check_password($patron_pwd)), $server->{filters});
         }
-        $resp .= add_field(FID_VALID_PATRON, 'Y');
-        $resp .= maybe_add(FID_SCREEN_MSG, $patron->screen_msg);
-        $resp .= maybe_add(FID_PRINT_LINE, $patron->print_line);
+        $resp .= add_field(FID_VALID_PATRON, 'Y', $server->{filters});
+        $resp .= maybe_add(FID_SCREEN_MSG, $patron->screen_msg, $server->{filters});
+        $resp .= maybe_add(FID_PRINT_LINE, $patron->print_line, $server->{filters});
     }
 
-    $resp .= add_field(FID_INST_ID, $ils->institution);
+    $resp .= add_field(FID_INST_ID, $ils->institution, $server->{filters});
 
     $self->write_msg($resp, undef, $server->{encoding});
 
@@ -1374,24 +1377,24 @@ sub handle_hold {
     $resp .= Sip::timestamp;
 
     if ($status->ok) {
-       $resp .= add_field(FID_PATRON_ID, $status->patron->id);
+       $resp .= add_field(FID_PATRON_ID, $status->patron->id, $server->{filters});
 
        if ($status->expiration_date) {
            $resp .= maybe_add(FID_EXPIRATION,
-                              Sip::timestamp($status->expiration_date));
+                              Sip::timestamp($status->expiration_date), $server->{filters});
        }
-       $resp .= maybe_add(FID_QUEUE_POS,   $status->queue_position);
-       $resp .= maybe_add(FID_PICKUP_LOCN, $status->pickup_location);
-       $resp .= maybe_add(FID_ITEM_ID,     $status->item->id);
-       $resp .= maybe_add(FID_TITLE_ID,    $status->item->title_id);
+       $resp .= maybe_add(FID_QUEUE_POS,   $status->queue_position, $server->{filters});
+       $resp .= maybe_add(FID_PICKUP_LOCN, $status->pickup_location, $server->{filters});
+       $resp .= maybe_add(FID_ITEM_ID,     $status->item->id, $server->{filters});
+       $resp .= maybe_add(FID_TITLE_ID,    $status->item->title_id, $server->{filters});
     } else {
        # Not ok.  still need required fields
-       $resp .= add_field(FID_PATRON_ID, $patron_id);
+       $resp .= add_field(FID_PATRON_ID, $patron_id, $server->{filters});
     }
 
-    $resp .= add_field(FID_INST_ID, $ils->institution);
-    $resp .= maybe_add(FID_SCREEN_MSG, $status->screen_msg);
-    $resp .= maybe_add(FID_PRINT_LINE, $status->print_line);
+    $resp .= add_field(FID_INST_ID, $ils->institution, $server->{filters});
+    $resp .= maybe_add(FID_SCREEN_MSG, $status->screen_msg, $server->{filters});
+    $resp .= maybe_add(FID_PRINT_LINE, $status->print_line, $server->{filters});
 
     $self->write_msg($resp, undef, $server->{encoding});
 
@@ -1443,15 +1446,15 @@ sub handle_renew {
        }
     $resp .= sipbool($status->desensitize);
     $resp .= Sip::timestamp;
-    $resp .= add_field(FID_PATRON_ID, $patron->id);
-    $resp .= add_field(FID_ITEM_ID,   $item->id);
-    $resp .= add_field(FID_TITLE_ID,  $item->title_id);
-    $resp .= add_field(FID_DUE_DATE,  $item->due_date);
+    $resp .= add_field(FID_PATRON_ID, $patron->id, $server->{filters});
+    $resp .= add_field(FID_ITEM_ID,   $item->id, $server->{filters});
+    $resp .= add_field(FID_TITLE_ID,  $item->title_id, $server->{filters});
+    $resp .= add_field(FID_DUE_DATE,  $item->due_date, $server->{filters});
     if ($ils->supports('security inhibit')) {
-        $resp .= add_field(FID_SECURITY_INHIBIT, $status->security_inhibit);
+        $resp .= add_field(FID_SECURITY_INHIBIT, $status->security_inhibit, $server->{filters});
     }
-       $resp .= add_field(FID_MEDIA_TYPE, $item->sip_media_type);
-       $resp .= maybe_add(FID_ITEM_PROPS, $item->sip_item_properties);
+       $resp .= add_field(FID_MEDIA_TYPE, $item->sip_media_type, $server->{filters});
+       $resp .= maybe_add(FID_ITEM_PROPS, $item->sip_item_properties, $server->{filters});
     } else {
        # renew failed for some reason
        # not OK, renewal not OK, Unknown media type (why bother checking?)
@@ -1460,22 +1463,22 @@ sub handle_renew {
        # If we found the patron or the item, the return the ILS
        # information, otherwise echo back the infomation we received
        # from the terminal
-    $resp .= add_field(FID_PATRON_ID, $patron ? $patron->id     : $patron_id);
-    $resp .= add_field(FID_ITEM_ID,   $item   ? $item->id       : $item_id  );
-    $resp .= add_field(FID_TITLE_ID,  $item   ? $item->title_id : $title_id );
+    $resp .= add_field(FID_PATRON_ID, $patron ? $patron->id     : $patron_id, $server->{filters});
+    $resp .= add_field(FID_ITEM_ID,   $item   ? $item->id       : $item_id  , $server->{filters});
+    $resp .= add_field(FID_TITLE_ID,  $item   ? $item->title_id : $title_id , $server->{filters});
     $resp .= add_field(FID_DUE_DATE, '');
     }
 
     if ($status->fee_amount) {
-        $resp .= add_field(FID_FEE_AMT,        $status->fee_amount);
-        $resp .= maybe_add(FID_CURRENCY,       $status->sip_currency);
-        $resp .= maybe_add(FID_FEE_TYPE,       $status->sip_fee_type);
-        $resp .= maybe_add(FID_TRANSACTION_ID, $status->transaction_id);
+        $resp .= add_field(FID_FEE_AMT,        $status->fee_amount, $server->{filters});
+        $resp .= maybe_add(FID_CURRENCY,       $status->sip_currency, $server->{filters});
+        $resp .= maybe_add(FID_FEE_TYPE,       $status->sip_fee_type, $server->{filters});
+        $resp .= maybe_add(FID_TRANSACTION_ID, $status->transaction_id, $server->{filters});
     }
 
-    $resp .= add_field(FID_INST_ID, $ils->institution);
-    $resp .= maybe_add(FID_SCREEN_MSG, $status->screen_msg);
-    $resp .= maybe_add(FID_PRINT_LINE, $status->print_line);
+    $resp .= add_field(FID_INST_ID, $ils->institution, $server->{filters});
+    $resp .= maybe_add(FID_SCREEN_MSG, $status->screen_msg, $server->{filters});
+    $resp .= maybe_add(FID_PRINT_LINE, $status->print_line, $server->{filters});
 
     $self->write_msg($resp, undef, $server->{encoding});
 
@@ -1517,13 +1520,13 @@ sub handle_renew_all {
     }
 
     $resp .= Sip::timestamp;
-    $resp .= add_field(FID_INST_ID, $ils->institution);
+    $resp .= add_field(FID_INST_ID, $ils->institution, $server->{filters});
 
-    $resp .= join('', map(add_field(FID_RENEWED_ITEMS, $_), @renewed));
-    $resp .= join('', map(add_field(FID_UNRENEWED_ITEMS, $_), @unrenewed));
+    $resp .= join('', map(add_field(FID_RENEWED_ITEMS, $_, $server->{filters}), @renewed));
+    $resp .= join('', map(add_field(FID_UNRENEWED_ITEMS, $_, $server->{filters}), @unrenewed));
 
-    $resp .= maybe_add(FID_SCREEN_MSG, $status->screen_msg);
-    $resp .= maybe_add(FID_PRINT_LINE, $status->print_line);
+    $resp .= maybe_add(FID_SCREEN_MSG, $status->screen_msg, $server->{filters});
+    $resp .= maybe_add(FID_PRINT_LINE, $status->print_line, $server->{filters});
 
     $self->write_msg($resp, undef, $server->{encoding});
 
@@ -1598,7 +1601,7 @@ sub send_acs_status {
     }
 
     # Institution ID
-    $msg .= add_field(FID_INST_ID, $account->{institution});
+    $msg .= add_field(FID_INST_ID, $account->{institution}, $server->{filters});
 
     if ($protocol_version >= 2) {
     # Supported messages: we do it all
@@ -1614,10 +1617,10 @@ sub send_acs_status {
     if (length($supported_msgs) < 16) {
         syslog("LOG_ERR", 'send_acs_status: supported messages "%s" too short', $supported_msgs);
     }
-        $msg .= add_field(FID_SUPPORTED_MSGS, $supported_msgs);
+        $msg .= add_field(FID_SUPPORTED_MSGS, $supported_msgs, $server->{filters});
     }
 
-    $msg .= maybe_add(FID_SCREEN_MSG, $screen_msg);
+    $msg .= maybe_add(FID_SCREEN_MSG, $screen_msg, $server->{filters});
 
     if (defined($account->{print_width}) && defined($print_line)
              && $account->{print_width}  <  length( $print_line)) {
@@ -1625,7 +1628,7 @@ sub send_acs_status {
         $print_line = substr($print_line, 0, $account->{print_width});
     }
 
-    $msg .= maybe_add(FID_PRINT_LINE, $print_line);
+    $msg .= maybe_add(FID_PRINT_LINE, $print_line, $server->{filters});
 
     # Do we want to tell the terminal its location?
 
diff --git a/t/16filter.t b/t/16filter.t
new file mode 100755 (executable)
index 0000000..202d055
--- /dev/null
@@ -0,0 +1,119 @@
+#!/usr/bin/perl
+#
+# Copyright (C) 2006-2008  Georgia Public Library Service
+# 
+# Author: David J. Fiander
+# 
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# checkout: test Checkout Response
+
+use strict;
+use warnings;
+use Clone qw(clone);
+
+use Sip::Constants qw(:all);
+
+use SIPtest qw($datepat $textpat $instid $currency
+    $blocked_br1_user_barcode $unblocked_br1_user_barcode
+    $blocked_br2_user_barcode $unblocked_br2_user_barcode
+    $filter_test_br1_item_barcode $filter_test_br2_item_barcode);
+
+my $br1_checkin = {
+    id  => 'Checkout: cleanup: check in item',
+    msg => "09N20050102    08423620060113    084235APUnder the bed|AO$instid|AB$filter_test_br1_item_barcode|ACterminal password|",
+    pat => qr/^101YNN$datepat/o,
+    fields => [],
+};
+
+my $br2_checkin_template = clone( $br1_checkin );
+$br2_checkin_template->{msg} =~ s/$filter_test_br1_item_barcode/$filter_test_br2_item_barcode/g;
+
+my $unfiltered_screen_msg = clone( $SIPtest::field_specs{(FID_SCREEN_MSG)} );
+$unfiltered_screen_msg->{pat} = 'Patron is not allowed to checkout the selected item';
+
+my $filtered_screen_msg = clone( $SIPtest::field_specs{(FID_SCREEN_MSG)} );
+$filtered_screen_msg->{pat} = 'BLOCKED';
+
+my $checkout_test_template = {
+    id  => 'Checkout attempt: valid item, valid patron but circ not allowed ',
+    msg => "11YN20060329    203000                  AO$instid|AA$blocked_br1_user_barcode|AB$filter_test_br1_item_barcode|AC|",
+    pat => qr/^12[01][YN][YNU][YN]$datepat/,
+    fields => [
+        $SIPtest::field_specs{(FID_INST_ID)},
+        $SIPtest::field_specs{(FID_SCREEN_MSG)},
+        $SIPtest::field_specs{(FID_PRINT_LINE)},
+        { field    => FID_PATRON_ID,
+            pat      => qr/^$blocked_br1_user_barcode$/o,
+            required => 1, },
+        { field    => FID_ITEM_ID,
+            pat      => qr/^$filter_test_br1_item_barcode$/o,
+            required => 1, },
+        { field    => FID_TITLE_ID,
+            pat      => $textpat,
+            required => 1, },
+        { field    => FID_DUE_DATE,
+            pat      => $textpat,
+            required => 1, },
+        { field    => FID_FEE_TYPE,
+            pat      => qr/^\d{2}$/,
+            required => 0, },
+        { field    => FID_SECURITY_INHIBIT,
+            pat      => qr/^[YN]$/,
+            required => 0, },
+        { field    => FID_CURRENCY,
+            pat      => qr/^$currency$/o,
+            required => 0, },
+        { field    => FID_FEE_AMT,
+            pat      => qr/^[.0-9]+$/,
+            required => 0, },
+        { field    => FID_MEDIA_TYPE,
+            pat      => qr/^\d{3}$/,
+            required => 0, },
+        { field    => FID_ITEM_PROPS,
+            pat      => $textpat,
+            required => 0, },
+        { field    => FID_TRANSACTION_ID,
+            pat      => $textpat,
+            required => 0, },
+        ]
+};
+
+my $filtered_and_blocked_checkout = clone( $checkout_test_template );
+$filtered_and_blocked_checkout->{id} .= '(filtered,blocked)';
+$filtered_and_blocked_checkout->{fields}[1] = $filtered_screen_msg;
+
+my $filtered_and_unblocked_checkout = clone( $checkout_test_template );
+$filtered_and_unblocked_checkout->{id} .= '(filtered,unblocked)';
+$filtered_and_unblocked_checkout->{fields}[1] = $filtered_screen_msg;
+$filtered_and_unblocked_checkout->{fields}[3] = {
+    field    => FID_PATRON_ID,
+    pat      => qr/^$unblocked_br1_user_barcode$/o,
+    required => 1 },
+$filtered_and_unblocked_checkout->{msg} =~ s/$blocked_br1_user_barcode/$unblocked_br1_user_barcode/g;
+
+# TODO - use BR2 patron/item for no matching filter rule, to hit every combination
+
+my @tests = (
+    $SIPtest::login_test,
+    $SIPtest::sc_status_test,
+    $filtered_and_unblocked_checkout,
+    $br1_checkin,
+    $filtered_and_blocked_checkout,
+    $br1_checkin
+);
+
+SIPtest::run_sip_tests(@tests);
+
+1;
index aa23533..94385e3 100644 (file)
@@ -44,6 +44,8 @@ our @EXPORT_OK = qw(run_sip_tests no_tagged_fields
                    $second_user_email $second_user_phone $second_user_birthday $second_user_ptype
                    $second_user_inet $second_user_homelib
             $cancel_hold_user_barcode $user_with_fees_barcode
+            $blocked_br1_user_barcode $unblocked_br1_user_barcode $filter_test_br1_item_barcode
+            $blocked_br2_user_barcode $unblocked_br2_user_barcode $filter_test_br2_item_barcode
                    $item_barcode $item_title $item_owner
                    $item2_barcode $item2_title $item2_owner
             $item_on_hold_barcode
@@ -143,6 +145,20 @@ our $item_diacritic_barcode = 'CONC40000546';
 our $item_diacritic_title = 'Composition student recital, April 6, 2000, Huntington University / composition students of Daniel Bédard';
 our $item_diacritic_owner = 'BR1';
 
+# For 16filter.t, testing SIP filters
+
+# Users with a block_circ penalty
+our $blocked_br1_user_barcode = '99999329410';
+our $blocked_br2_user_barcode = '99999329410';
+
+# And users without such a penalty
+our $unblocked_br1_user_barcode = '99999354736';
+our $unblocked_br2_user_barcode = '99999324566';
+
+# And items with which to attempt checkouts to such users
+our $filter_test_br1_item_barcode = 'CONC40000565';
+our $filter_test_br2_item_barcode = 'CONC51000136';
+
 # End configuration
 
 # Pattern for a SIP datestamp, to be used by individual tests to
index 76d58b8..f60d648 100644 (file)
@@ -17,3 +17,87 @@ set
 where
     name = 'sip.sc_status_before_login_institution'
 ;
+
+-- the following section is for testing SIP Filters using SIP2Mediator
+
+INSERT INTO actor.usr_standing_penalty (usr, staff, org_unit, standing_penalty) VALUES (53, 1, 4, 22);
+
+INSERT INTO permission.grp_tree (name,parent,description,application_perm)
+VALUES ('SIP', 1, 'SIP2 Client Systems', 'group_application.user.sip_client');
+
+INSERT INTO
+  permission.grp_perm_map (grp, perm, depth, grantable)
+SELECT
+  g.id, p.id, 0, FALSE
+FROM
+  permission.grp_tree g,
+  permission.perm_list p
+WHERE
+  g.name = 'SIP' AND
+  p.code IN (
+    'COPY_CHECKIN',
+    'COPY_CHECKOUT',
+    'RENEW_CIRC',
+    'VIEW_CIRCULATIONS',
+    'VIEW_COPY_CHECKOUT_HISTORY',
+    'VIEW_PERMIT_CHECKOUT',
+    'VIEW_USER',
+    'VIEW_USER_FINES_SUMMARY',
+    'VIEW_USER_TRANSACTIONS',
+    'UPDATE_USER',
+    'CANCEL_HOLDS',
+    'CREATE_PAYMENT'
+ );
+
+INSERT INTO actor.usr (
+    profile,
+    usrname,
+    passwd,
+    ident_type,
+    first_given_name,
+    family_name,
+    home_ou,
+    super_user)
+VALUES (
+    (SELECT id FROM permission.grp_tree WHERE name ~ 'SIP' LIMIT 1),
+    'scclient',
+    'clientpwd',
+    (SELECT MAX(id) FROM config.identification_type),
+    'Ima',
+    'SIP',
+    (SELECT MIN(id) FROM actor.org_unit WHERE ou_type = (SELECT MIN(id) FROM actor.org_unit_type WHERE can_have_users)),
+    'f'
+);
+INSERT INTO actor.card (
+    usr,
+    barcode
+) VALUES (
+    (SELECT CURRVAL('actor.usr_id_seq')),
+    'scclient'
+);
+UPDATE actor.usr
+    SET card = CURRVAL('actor.card_id_seq')
+    WHERE id = (SELECT CURRVAL('actor.usr_id_seq'));
+SELECT actor.set_passwd((select id from actor.usr where usrname = 'scclient'), 'sip2', 'sip_password');
+
+INSERT INTO actor.workstation (name, owning_lib) VALUES ('BR1-SIP2-Gateway', 4);
+
+INSERT INTO sip.account(
+    enabled, transient, setting_group, sip_username, usr, workstation
+) VALUES (
+    't', 'f', 1, 'scclient',
+    (select id from actor.usr where usrname = 'scclient'),
+    (SELECT id FROM actor.workstation WHERE name = 'BR1-SIP2-Gateway')
+);
+
+-- for t/16filter.t
+
+insert into sip.filter (enabled, setting_group, identifier, strip, replace_with) values ('t',1,'AF','f','BLOCKED');
+insert into actor.usr_standing_penalty (org_unit, usr, standing_penalty, staff, set_date) values (4, 16, 22, 1, now());
+insert into actor.usr_standing_penalty (org_unit, usr, standing_penalty, staff, set_date) values (5, 15, 22, 1, now());
+
+
+