|
| 1 | +#!/usr/bin/perl |
| 2 | + |
| 3 | +#-------------------------------------------------------------- |
| 4 | +# 12-05-2014 Rich Wenger, MIT Libraries |
| 5 | +# This script provides Aleph services to an external server. |
| 6 | +#-------------------------------------------------------------- |
| 7 | + |
| 8 | +use strict; |
| 9 | +use warnings; |
| 10 | +use HTTP::Request; |
| 11 | +use LWP::UserAgent; |
| 12 | +use Switch; |
| 13 | +use POSIX; |
| 14 | +use Time::Local; |
| 15 | + |
| 16 | +my $rest_port = 'PORT'; |
| 17 | +my @whitelist = (WHITELIST); |
| 18 | +#----------------------------------------------------------------- |
| 19 | +# Only accept connections from authorized IP addresses. |
| 20 | +#----------------------------------------------------------------- |
| 21 | +if (!grep /$ENV{REMOTE_ADDR}/, @whitelist) { |
| 22 | + print STDERR "*** $0: Unauthorized access attempt from $ENV{REMOTE_ADDR} ***\n"; |
| 23 | + print "Content-type: text/html\n\n"; |
| 24 | + print "Unathorized access"; |
| 25 | + exit; |
| 26 | + } |
| 27 | + |
| 28 | +#-------------------------------------------------------------------------------------------- |
| 29 | +# $debug and $parameter_trace are for diagnostic purposes and will normally be set to 0. |
| 30 | +# $id_translation will be set to 1 as a default. Setting it to 0 disables the translation |
| 31 | +# of alternate identifiers to Aleph ids by the adapter. |
| 32 | +#-------------------------------------------------------------------------------------------- |
| 33 | +my $debug = 0; |
| 34 | +my $parameter_trace = 0; |
| 35 | +my $id_translation = 1; |
| 36 | +my $sql_lookup = 0; |
| 37 | + |
| 38 | +#------------------------------------------------------------------------ |
| 39 | +# Local base URLs for the Aleph X-server and the RESTful API. |
| 40 | +#------------------------------------------------------------------------ |
| 41 | +my $x_base_url = 'http://localhost/X?'; |
| 42 | +my $r_base_url = "http://localhost:$rest_port"; |
| 43 | + |
| 44 | +#------------------------------------- |
| 45 | +# Headers and XML constants. |
| 46 | +#------------------------------------- |
| 47 | +my $xml_header = "Content-type: text/xml\n\n"; |
| 48 | +my $html_header = "Content-type: text/html\n\n"; |
| 49 | +my $xml_prolog = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'; |
| 50 | + |
| 51 | +my $version_xml = join '', |
| 52 | + '<version>', |
| 53 | + '<ilsInstitutionName>INSTNAME</ilsInstitutionName>', |
| 54 | + '<ilsVersion>ALEPHVER</ilsVersion>', |
| 55 | + '<locale>en_US</locale>', |
| 56 | + '<timeZone>TIMEZONE</timeZone>', |
| 57 | + '<timeZoneCode>TZCODE</timeZoneCode>', |
| 58 | + '<timeZoneGMT>TZGMT</timeZoneGMT>', |
| 59 | + '<currencyCode>CURRENCY</currencyCode>', |
| 60 | + '</version>'; |
| 61 | + |
| 62 | +my $printline = ''; |
| 63 | +my $putdata; |
| 64 | +my $postdata; |
| 65 | + |
| 66 | +#---------------------------------------------------- |
| 67 | +# Valid parameters in Aleph RESTful URLs |
| 68 | +#---------------------------------------------------- |
| 69 | +my @allowed_groups = ('patron','ilsinstance','record'); |
| 70 | +my @allowed_categories = ('patroninformation','circulationactions','record','patronstatus','items','holds'); |
| 71 | +my @patinfo_functions = ('address','password'); |
| 72 | +my @circ_functions = ('loans','requests','cash'); |
| 73 | +my @patstatus_functions = ('blocks','registration'); |
| 74 | +my @allowed_subfunctions = ('holds','photocopies','acquisitionrequests','ill','bookings'); |
| 75 | + |
| 76 | +#-------------------------------- |
| 77 | +# Valid HTTP methods |
| 78 | +#-------------------------------- |
| 79 | +my @allowed_methods = ('get','post','put','delete'); |
| 80 | + |
| 81 | +#---------------------------------------------------------------------------- |
| 82 | +# Get the RESTful URL components. |
| 83 | +# @parms will contain the RESTful nodes between slashes. |
| 84 | +# @args will contain any key=value pairs from the end of the URI. |
| 85 | +#---------------------------------------------------------------------------- |
| 86 | +my @parms = split /\//, (split /\?parm1=/, lc $ENV{'REQUEST_URI'})[0]; |
| 87 | +splice @parms,0,2; |
| 88 | +my @args = split /\&/, (split /\?/, $parms[$#parms])[1]; |
| 89 | +if (grep /\?/, $parms[$#parms]) { |
| 90 | + $parms[$#parms] =~ s/\?(.*)$//go; |
| 91 | + } |
| 92 | + |
| 93 | +my ($group, $patron_id, $category, $function, $subfunction) = ''; |
| 94 | +($group, $patron_id, $category, $function, $subfunction) = @parms; |
| 95 | +if (!grep /$group/, @allowed_groups) { |
| 96 | + print "$html_header invalid group $group"; |
| 97 | + exit; |
| 98 | + } |
| 99 | +if (!grep /$category/, @allowed_categories) { |
| 100 | + print "$html_header invalid category $category"; |
| 101 | + exit; |
| 102 | + } |
| 103 | + |
| 104 | +#---------------------------------------------------------------------------------------- |
| 105 | +# $method will contain one of the HTTP commands: GET, POST, PUT, DELETE, etc. |
| 106 | +# They are stored here in lower case for later use as method calls to LWP. |
| 107 | +#---------------------------------------------------------------------------------------- |
| 108 | +my $method = lc $ENV{'REQUEST_METHOD'}; |
| 109 | + |
| 110 | +#------------------------------------------------------------------------------ |
| 111 | +# This paragraph is for diagnostic purposes only. It writes parameters |
| 112 | +# and arguments to the Apache log (STDERR) and exits. |
| 113 | +#------------------------------------------------------------------------------ |
| 114 | +if ($parameter_trace) { |
| 115 | + print "$html_header"; |
| 116 | + foreach my $x (@parms) { print "parm: $x<br>"; } |
| 117 | + if (@args) { |
| 118 | + foreach my $x (@args) { print "args: $x<br>"; } |
| 119 | + } |
| 120 | + if ($debug) { |
| 121 | + my $printline = join '', |
| 122 | + "*** Group: $group ***\n", |
| 123 | + "*** Patron id: $patron_id ***\n", |
| 124 | + "*** Category: $category ***\n", |
| 125 | + "*** Function: $function ***\n", |
| 126 | + "*** Subfunction: $subfunction ***\n"; |
| 127 | + print STDERR $printline; |
| 128 | + } |
| 129 | + exit; |
| 130 | + } |
| 131 | + |
| 132 | +#---------------------------------------------------------------------------- |
| 133 | +# This section handles the request for Aleph version information. |
| 134 | +# The Aleph REST API does not support this operation. |
| 135 | +#---------------------------------------------------------------------------- |
| 136 | +if ($group eq 'ilsinstance') { |
| 137 | + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); |
| 138 | + my $timezone = $isdst ? 'Eastern Daylight Time' : 'Eastern Standard Time'; |
| 139 | + my $tzcode = $isdst ? 'EDT' : 'EST'; |
| 140 | + |
| 141 | + my @aleph_info = `./get_aleph_info.csh`; |
| 142 | + my $version = (split ',', $aleph_info[0])[2]; |
| 143 | + my $aleph_version = (split ' ', $version)[1]; |
| 144 | + my $currency = $aleph_info[3]; |
| 145 | + chomp $currency; |
| 146 | + |
| 147 | + my @t = localtime(time); |
| 148 | + my $gmt_offset_in_hours = (timegm(@t) - timelocal(@t)) / 3600; |
| 149 | + |
| 150 | + my $version_string = $version_xml; |
| 151 | + $version_string =~ s/ALEPHVER/$aleph_version/; |
| 152 | + $version_string =~ s/TIMEZONE/$timezone/; |
| 153 | + $version_string =~ s/TZCODE/$tzcode/; |
| 154 | + $version_string =~ s/TZGMT/$gmt_offset_in_hours/; |
| 155 | + $version_string =~ s/CURRENCY/$currency/; |
| 156 | + |
| 157 | + $printline = join '', $xml_prolog, $version_string; |
| 158 | + } |
| 159 | + |
| 160 | +else { |
| 161 | + #---------------------------------------------------------------------- |
| 162 | + # Instantiate a user agent for use in calling the REST API. |
| 163 | + #---------------------------------------------------------------------- |
| 164 | + my $ua = LWP::UserAgent->new; |
| 165 | + |
| 166 | + my $request_uri = $ENV{'REQUEST_URI'}; |
| 167 | + my $response = ''; |
| 168 | + my $aleph_id = ''; |
| 169 | + if ($group eq 'patron' && $id_translation) { |
| 170 | + if (!$sql_lookup) { |
| 171 | + #--------------------------------------------------------------------- |
| 172 | + # Incoming identifer requires translation. Since $sql_lookup |
| 173 | + # is not set, convert it to an Aleph id via bor-by-key |
| 174 | + # x-server function, |
| 175 | + #--------------------------------------------------------------------- |
| 176 | + my $info_prefix = "op=bor-by-key&bor_id=$patron_id"; |
| 177 | + my $rest_url = join '', $x_base_url, $info_prefix; |
| 178 | + print STDERR "*** Bor-by-key URL: $rest_url ***\n" if $debug; |
| 179 | + $response = $ua->get($rest_url); |
| 180 | + $aleph_id = &extract_alephid($response); |
| 181 | + print STDERR "*** Aleph id: $aleph_id ***\n" if $debug; |
| 182 | + print STDERR "*** Patron id: $patron_id ***\n" if $debug; |
| 183 | + print STDERR "*** request_uri before: $request_uri ***\n" if $debug; |
| 184 | + $request_uri =~ s/$patron_id/$aleph_id/ig; |
| 185 | + print STDERR "*** request_uri after: $request_uri ***\n" if $debug; |
| 186 | + } |
| 187 | + else { |
| 188 | + #--------------------------------------------------------------------- |
| 189 | + # Incoming identifer requires translation. Since $sql_lookup |
| 190 | + # is on, convert it to an Aleph id via SQL lookup. |
| 191 | + #--------------------------------------------------------------------- |
| 192 | + $aleph_id = `./sql_lookup.csh $patron_id`; |
| 193 | + print STDERR "*** Aleph id from SQL: $aleph_id ***\n" if $debug; |
| 194 | + $request_uri =~ s/$patron_id/$aleph_id/ig; |
| 195 | + } |
| 196 | + } |
| 197 | + |
| 198 | + if (grep /$method/, @allowed_methods) { |
| 199 | + #----------------------------------- |
| 200 | + # Optional local programming can be inserted here by uncommenting the |
| 201 | + # switch structure below |
| 202 | + #----------------------------------- |
| 203 | + #switch ($category) { |
| 204 | + # case ('patroninformation') { |
| 205 | + # # local code here |
| 206 | + # } |
| 207 | + # case ('patronstatus') { |
| 208 | + # # local code here |
| 209 | + # } |
| 210 | + # case ('circulationactions') { |
| 211 | + # # local code here |
| 212 | + # } |
| 213 | + # case ('record') { |
| 214 | + # # local code here |
| 215 | + # } |
| 216 | + # case ('items') { |
| 217 | + # # local code here |
| 218 | + # } |
| 219 | + # } |
| 220 | + #----------------------------------- |
| 221 | + # Default passthrough. |
| 222 | + #----------------------------------- |
| 223 | + my $rest_url = join '', $r_base_url, $request_uri; |
| 224 | + print STDERR "*** $category: $rest_url ***\n" if $debug; |
| 225 | + print STDERR "*** Method: $method ***\n" if $debug; |
| 226 | + my $request; |
| 227 | + switch ($method) { |
| 228 | + case ('get') { |
| 229 | + $request = HTTP::Request->new(GET => $rest_url); |
| 230 | + } |
| 231 | + case ('post') { |
| 232 | + read(STDIN, $putdata, $ENV{'CONTENT_LENGTH'}); |
| 233 | + my $h = HTTP::Headers->new(Content_Type => 'text/xml'); |
| 234 | + $request = HTTP::Request->new('POST', $rest_url, $h, $putdata); |
| 235 | + } |
| 236 | + case ('put') { |
| 237 | + read(STDIN, $putdata, $ENV{'CONTENT_LENGTH'}); |
| 238 | + my $h = HTTP::Headers->new(Content_Type => 'text/xml'); |
| 239 | + $request = HTTP::Request->new('PUT', $rest_url, $h, $putdata); |
| 240 | + } |
| 241 | + case ('delete') { |
| 242 | + $request = HTTP::Request->new(DELETE => $rest_url); |
| 243 | + } |
| 244 | + } |
| 245 | + $response = $ua->request($request); |
| 246 | + $printline = $response->content; |
| 247 | + #------------------------------------------------------------ |
| 248 | + # Remove the port number from any URLs in the XML. |
| 249 | + #------------------------------------------------------------ |
| 250 | + $printline =~ s/localhost:$rest_port/$ENV{'HTTP_HOST'}/go; |
| 251 | + } |
| 252 | + else { |
| 253 | + #------------------------------------------------------------------ |
| 254 | + # HTTP method is not supported. Return failure message |
| 255 | + #------------------------------------------------------------------ |
| 256 | + $printline = join '', $xml_prolog, "<note>HTTP command $method is restricted or invalid</note>"; |
| 257 | + } |
| 258 | + } |
| 259 | + |
| 260 | +#----------------------------------------------------------------------------------------- |
| 261 | +# Return the content to the caller. |
| 262 | +# The following 'if' statement is required to ameliorate the Aleph REST API's |
| 263 | +# inexplicable practice of returning HTML in certain error conditions. |
| 264 | +#----------------------------------------------------------------------------------------- |
| 265 | +print STDERR "*** printline: $printline ***\n" if $debug; |
| 266 | +if (grep /<html>/, $printline) { print "$html_header" } |
| 267 | +else { print "$xml_header" } |
| 268 | +print $printline; |
| 269 | +exit; |
| 270 | + |
| 271 | +#------------------------- subroutines -------------------------- |
| 272 | +sub extract_alephid { |
| 273 | + my $xml_ref = pop; |
| 274 | + my @temp = split '<\/internal\-id>', (split '<internal\-id>', $xml_ref->content)[1]; |
| 275 | + return $temp[0]; |
| 276 | + } |
0 commit comments