From 91a8f051220ba1b29e76068a58cbb400ae521834 Mon Sep 17 00:00:00 2001 From: Mike Rylander Date: Mon, 3 Aug 2015 13:27:56 -0400 Subject: [PATCH] LP#1485371: Use client-supplied TZ Currently, there is no protocol-level mechanism for passing the client's desired timezone to the server. In much the same way we pass the locale, we can let the server know what timezone it should use when interpreting time stamps. To do this we: * Teach perl server code to live in the client TZ, if supplied * Teach perl client code to send the current $ENV{TZ} * Teach javascript library to include client TZ in gateway/translator/websocket communication * Teach C code to pull the incoming TZ and apply it to outgoing messages * Teach srfsh to pull TZ from the environment and pass it with requests Signed-off-by: Mike Rylander Signed-off-by: Galen Charlton --- include/opensrf/osrf_app_session.h | 5 +++ include/opensrf/osrf_message.h | 7 ++++ src/javascript/opensrf.js | 21 ++++++++++++ src/libopensrf/osrf_app_session.c | 32 ++++++++++++++++++ src/libopensrf/osrf_message.c | 33 +++++++++++++++++++ src/libopensrf/osrf_stack.c | 5 +++ .../lib/OpenSRF/DomainObject/oilsMessage.pm | 26 ++++++++++++++- src/srfsh/srfsh.c | 4 +++ 8 files changed, 132 insertions(+), 1 deletion(-) diff --git a/include/opensrf/osrf_app_session.h b/include/opensrf/osrf_app_session.h index f0e2586..57d9092 100644 --- a/include/opensrf/osrf_app_session.h +++ b/include/opensrf/osrf_app_session.h @@ -74,6 +74,9 @@ struct osrf_app_session_struct { /** the current locale for this session. **/ char* session_locale; + /** the current TZ for this session. **/ + char* session_tz; + /** let the user use the session to store their own session data. */ void* userData; @@ -105,6 +108,8 @@ osrfAppSession* osrf_app_server_session_init( char* osrf_app_session_set_locale( osrfAppSession*, const char* ); +char* osrf_app_session_set_tz( osrfAppSession*, const char* ); + /* ingress used by all sessions until replaced */ char* osrfAppSessionSetIngress( const char* ); diff --git a/include/opensrf/osrf_message.h b/include/opensrf/osrf_message.h index 76091d0..14b601b 100644 --- a/include/opensrf/osrf_message.h +++ b/include/opensrf/osrf_message.h @@ -95,17 +95,24 @@ struct osrf_message_struct { /** Magical ingress hint. */ char* sender_ingress; + + /** Magical TZ hint. */ + char* sender_tz; }; typedef struct osrf_message_struct osrfMessage; const char* osrf_message_set_locale( osrfMessage* msg, const char* locale ); +const char* osrf_message_set_tz( osrfMessage* msg, const char* tz ); + const char* osrfMessageSetIngress( osrfMessage* msg, const char* ingress ); const char* osrf_message_set_default_locale( const char* locale ); const char* osrf_message_get_last_locale(void); +const char* osrf_message_get_last_tz(void); + osrfMessage* osrf_message_init( enum M_TYPE type, int thread_trace, int protocol ); void osrf_message_set_status_info( osrfMessage*, diff --git a/src/javascript/opensrf.js b/src/javascript/opensrf.js index d67fbfd..414233f 100644 --- a/src/javascript/opensrf.js +++ b/src/javascript/opensrf.js @@ -13,6 +13,16 @@ * GNU General Public License for more details. * ----------------------------------------------------------------------- */ +/* ----------------------------------------------------------------------- + * Portions of this file are Copyright (c) Jon Nylander + * + * jsTimezoneDetect is released under the MIT License + * - http://www.opensource.org/licenses/mit-license.php + * + * For usage and examples, visit: http://pellepim.bitbucket.org/jstz/ + * ----------------------------------------------------------------------- */ +(function(e){var t=function(){"use strict";var e="s",n=function(e){var t=-e.getTimezoneOffset();return t!==null?t:0},r=function(e,t,n){var r=new Date;return e!==undefined&&r.setFullYear(e),r.setDate(n),r.setMonth(t),r},i=function(e){return n(r(e,0,2))},s=function(e){return n(r(e,5,2))},o=function(e){var t=e.getMonth()>7?s(e.getFullYear()):i(e.getFullYear()),r=n(e);return t-r!==0},u=function(){var t=i(),n=s(),r=i()-s();return r<0?t+",1":r>0?n+",1,"+e:t+",0"},a=function(){var e=u();return new t.TimeZone(t.olson.timezones[e])},f=function(e){var t=new Date(2010,6,15,1,0,0,0),n={"America/Denver":new Date(2011,2,13,3,0,0,0),"America/Mazatlan":new Date(2011,3,3,3,0,0,0),"America/Chicago":new Date(2011,2,13,3,0,0,0),"America/Mexico_City":new Date(2011,3,3,3,0,0,0),"America/Asuncion":new Date(2012,9,7,3,0,0,0),"America/Santiago":new Date(2012,9,3,3,0,0,0),"America/Campo_Grande":new Date(2012,9,21,5,0,0,0),"America/Montevideo":new Date(2011,9,2,3,0,0,0),"America/Sao_Paulo":new Date(2011,9,16,5,0,0,0),"America/Los_Angeles":new Date(2011,2,13,8,0,0,0),"America/Santa_Isabel":new Date(2011,3,5,8,0,0,0),"America/Havana":new Date(2012,2,10,2,0,0,0),"America/New_York":new Date(2012,2,10,7,0,0,0),"Asia/Beirut":new Date(2011,2,27,1,0,0,0),"Europe/Helsinki":new Date(2011,2,27,4,0,0,0),"Europe/Istanbul":new Date(2011,2,28,5,0,0,0),"Asia/Damascus":new Date(2011,3,1,2,0,0,0),"Asia/Jerusalem":new Date(2011,3,1,6,0,0,0),"Asia/Gaza":new Date(2009,2,28,0,30,0,0),"Africa/Cairo":new Date(2009,3,25,0,30,0,0),"Pacific/Auckland":new Date(2011,8,26,7,0,0,0),"Pacific/Fiji":new Date(2010,11,29,23,0,0,0),"America/Halifax":new Date(2011,2,13,6,0,0,0),"America/Goose_Bay":new Date(2011,2,13,2,1,0,0),"America/Miquelon":new Date(2011,2,13,5,0,0,0),"America/Godthab":new Date(2011,2,27,1,0,0,0),"Europe/Moscow":t,"Asia/Yekaterinburg":t,"Asia/Omsk":t,"Asia/Krasnoyarsk":t,"Asia/Irkutsk":t,"Asia/Yakutsk":t,"Asia/Vladivostok":t,"Asia/Kamchatka":t,"Europe/Minsk":t,"Australia/Perth":new Date(2008,10,1,1,0,0,0)};return n[e]};return{determine:a,date_is_dst:o,dst_start_for:f}}();t.TimeZone=function(e){"use strict";var n={"America/Denver":["America/Denver","America/Mazatlan"],"America/Chicago":["America/Chicago","America/Mexico_City"],"America/Santiago":["America/Santiago","America/Asuncion","America/Campo_Grande"],"America/Montevideo":["America/Montevideo","America/Sao_Paulo"],"Asia/Beirut":["Asia/Beirut","Europe/Helsinki","Europe/Istanbul","Asia/Damascus","Asia/Jerusalem","Asia/Gaza"],"Pacific/Auckland":["Pacific/Auckland","Pacific/Fiji"],"America/Los_Angeles":["America/Los_Angeles","America/Santa_Isabel"],"America/New_York":["America/Havana","America/New_York"],"America/Halifax":["America/Goose_Bay","America/Halifax"],"America/Godthab":["America/Miquelon","America/Godthab"],"Asia/Dubai":["Europe/Moscow"],"Asia/Dhaka":["Asia/Yekaterinburg"],"Asia/Jakarta":["Asia/Omsk"],"Asia/Shanghai":["Asia/Krasnoyarsk","Australia/Perth"],"Asia/Tokyo":["Asia/Irkutsk"],"Australia/Brisbane":["Asia/Yakutsk"],"Pacific/Noumea":["Asia/Vladivostok"],"Pacific/Tarawa":["Asia/Kamchatka"],"Africa/Johannesburg":["Asia/Gaza","Africa/Cairo"],"Asia/Baghdad":["Europe/Minsk"]},r=e,i=function(){var e=n[r],i=e.length,s=0,o=e[0];for(;ssession_locale; } +/** + @brief Install a copy of a TZ string in a specified session. + @param session Pointer to the osrfAppSession in which the TZ is to be installed. + @param TZ The TZ string to be copied and installed. + @return A pointer to the installed copy of the TZ string. +*/ +char* osrf_app_session_set_tz( osrfAppSession* session, const char* tz ) { + if (!session || !tz) + return NULL; + + if(session->session_tz) { + if( strlen(session->session_tz) >= strlen(tz) ) { + /* There's room available; just copy */ + strcpy(session->session_tz, tz); + } else { + free(session->session_tz); + session->session_tz = strdup( tz ); + } + } else { + session->session_tz = strdup( tz ); + } + + return session->session_tz; +} + /** @brief Install a copy of a ingress string as the new default. @param session Pointer to the new strdup'ed default_ingress @@ -499,6 +524,7 @@ osrfAppSession* osrfAppSessionClientInit( const char* remote_service ) { session->orig_remote_id = strdup(session->remote_id); session->remote_service = strdup(remote_service); session->session_locale = NULL; + session->session_tz = NULL; session->transport_error = 0; session->panic = 0; session->outbuf = NULL; // Not used by client @@ -603,6 +629,7 @@ osrfAppSession* osrf_app_server_session_init( session->state = OSRF_SESSION_DISCONNECTED; session->type = OSRF_SESSION_SERVER; session->session_locale = NULL; + session->session_tz = NULL; session->userData = NULL; session->userDataFree = NULL; @@ -711,6 +738,8 @@ static int osrfAppSessionMakeLocaleRequest( osrf_message_set_locale(req_msg, session->session_locale); } + osrf_message_set_tz(req_msg, session->session_tz); + if (!current_ingress) osrfAppSessionSetIngress("opensrf"); osrfMessageSetIngress(req_msg, current_ingress); @@ -1110,6 +1139,9 @@ void osrfAppSessionFree( osrfAppSession* session ){ if(session->session_locale) free(session->session_locale); + if(session->session_tz) + free(session->session_tz); + free(session->remote_id); free(session->orig_remote_id); free(session->session_id); diff --git a/src/libopensrf/osrf_message.c b/src/libopensrf/osrf_message.c index 3681dfa..9de5e0f 100644 --- a/src/libopensrf/osrf_message.c +++ b/src/libopensrf/osrf_message.c @@ -42,6 +42,7 @@ osrfMessage* osrf_message_init( enum M_TYPE type, int thread_trace, int protocol msg->_result_content = NULL; msg->method_name = NULL; msg->sender_locale = NULL; + msg->sender_tz = NULL; msg->sender_ingress = NULL; return msg; @@ -83,6 +84,25 @@ const char* osrf_message_set_locale( osrfMessage* msg, const char* locale ) { return msg->sender_locale = strdup( locale ); } +/** + @brief Set the TZ for a specified osrfMessage. + @param msg Pointer to the osrfMessage. + @param TZ Pointer to the TZ string to be installed in the osrfMessage. + @return Pointer to the new TZ string for the osrfMessage, or NULL if either + parameter is NULL. + + If no TZ is specified for an osrfMessage, we use the system TZ. + + Used for a REQUEST message. +*/ +const char* osrf_message_set_tz( osrfMessage* msg, const char* tz ) { + if( msg == NULL || tz == NULL ) + return NULL; + if( msg->sender_tz ) + free( msg->sender_tz ); + return msg->sender_tz = strdup( tz ); +} + /** @brief Set the ingress for a specified osrfMessage. @param msg Pointer to the osrfMessage. @@ -307,6 +327,9 @@ void osrfMessageFree( osrfMessage* msg ) { if( msg->sender_locale != NULL ) free(msg->sender_locale); + if( msg->sender_tz != NULL ) + free(msg->sender_tz); + if( msg->sender_ingress != NULL ) free(msg->sender_ingress); @@ -384,6 +407,7 @@ char* osrf_message_serialize(const osrfMessage* msg) { The resulting jsonObject is a JSON_HASH with a classname of "osrfMessage", and the following keys: - "threadTrace" - "locale" + - "tz" - "ingress" - "type" - "payload" (only for STATUS, REQUEST, and RESULT messages) @@ -422,6 +446,10 @@ jsonObject* osrfMessageToJSON( const osrfMessage* msg ) { jsonObjectSetKey(json, "locale", jsonNewObject(default_locale)); } + if (msg->sender_tz != NULL) { + jsonObjectSetKey(json, "tz", jsonNewObject(msg->sender_tz)); + } + if (msg->sender_ingress != NULL) jsonObjectSetKey(json, "ingress", jsonNewObject(msg->sender_ingress)); @@ -657,6 +685,11 @@ static osrfMessage* deserialize_one_message( const jsonObject* obj ) { osrfMessageSetIngress(msg, jsonObjectGetString(tmp)); } + tmp = jsonObjectGetKeyConst(obj, "tz"); + if (tmp) { + osrf_message_set_tz(msg, jsonObjectGetString(tmp)); + } + tmp = jsonObjectGetKeyConst( obj, "payload" ); if(tmp) { // Get method name and parameters for a REQUEST diff --git a/src/libopensrf/osrf_stack.c b/src/libopensrf/osrf_stack.c index 73793f8..e176b9e 100644 --- a/src/libopensrf/osrf_stack.c +++ b/src/libopensrf/osrf_stack.c @@ -274,6 +274,11 @@ static void _do_server( osrfAppSession* session, osrfMessage* msg ) { osrfLogDebug( OSRF_LOG_MARK, "Server received message of type %d", msg->m_type ); + osrf_app_session_set_tz(session, msg->sender_tz); + osrf_app_session_set_locale(session, msg->sender_locale); + + osrfLogDebug( OSRF_LOG_MARK, "Message has locale %s and tz %s", session->session_locale, session->session_tz ); + switch( msg->m_type ) { case STATUS: diff --git a/src/perl/lib/OpenSRF/DomainObject/oilsMessage.pm b/src/perl/lib/OpenSRF/DomainObject/oilsMessage.pm index 54c5963..33675a8 100644 --- a/src/perl/lib/OpenSRF/DomainObject/oilsMessage.pm +++ b/src/perl/lib/OpenSRF/DomainObject/oilsMessage.pm @@ -5,6 +5,7 @@ use OpenSRF::DomainObject::oilsResponse qw/:status/; use OpenSRF::Utils::Logger qw/:level/; use warnings; use strict; use OpenSRF::EX qw/:try/; +use POSIX qw/tzset/; OpenSRF::Utils::JSON->register_class_hint(hint => 'osrfMessage', name => 'OpenSRF::DomainObject::oilsMessage', type => 'hash'); @@ -17,6 +18,7 @@ sub new { my $self = shift; my $class = ref($self) || $self; my %args = @_; + $args{tz} = $ENV{TZ}; return bless \%args => $class; } @@ -102,6 +104,24 @@ sub sender_locale { return $self->{locale}; } +=head2 OpenSRF::DomainObject::oilsMessage->sender_tz( [$tz] ); + +=over 4 + +Sets or gets the current message tz. Useful for telling the +server how you see the world. + +=back + +=cut + +sub sender_tz { + my $self = shift; + my $val = shift; + $self->{tz} = $val if (defined $val); + return $self->{tz}; +} + =head2 OpenSRF::DomainObject::oilsMessage->sender_ingress( [$ingress] ); =over 4 @@ -197,12 +217,13 @@ sub handler { my $session = shift; my $mtype = $self->type; + my $tz = $self->sender_tz || ''; my $locale = $self->sender_locale || ''; my $ingress = $self->sender_ingress || ''; my $api_level = $self->api_level || 1; my $tT = $self->threadTrace; - $log->debug("Message locale is $locale; ingress = $ingress", DEBUG); + $log->debug("Message locale is $locale; ingress = $ingress; tz = $tz", DEBUG); $session->last_message_type($mtype); $session->last_message_api_level($api_level); @@ -217,10 +238,13 @@ sub handler { $val = $self->do_server( $session, $mtype, $api_level, $tT ); } elsif ($session->endpoint == $session->CLIENT()) { + $tz = undef; # Client should not adopt the TZ of the server $val = $self->do_client( $session, $mtype, $api_level, $tT ); } if( $val ) { + local $ENV{TZ} = $tz || $ENV{TZ}; # automatic revert at the end of this scope + tzset(); return OpenSRF::Application->handler($session, $self->payload); } else { $log->debug("Request was handled internally", DEBUG); diff --git a/src/srfsh/srfsh.c b/src/srfsh/srfsh.c index d8132d1..e3705a9 100644 --- a/src/srfsh/srfsh.c +++ b/src/srfsh/srfsh.c @@ -37,6 +37,7 @@ static void get_misc( ArgParser* parser ); #define SRFSH_PORT 5222 #define COMMAND_BUFSIZE 4096 +static char* tz = NULL; /* shell prompt */ static const char* prompt = "srfsh# "; @@ -104,6 +105,7 @@ int main( int argc, char* argv[] ) { /* --------------------------------------------- */ /* see if they have a .srfsh.xml in their home directory */ char* home = getenv("HOME"); + tz = getenv("TZ"); int l = strlen(home) + 36; char fbuf[l]; snprintf(fbuf, sizeof(fbuf), "%s/.srfsh.xml", home); @@ -798,6 +800,8 @@ int send_request( const char* server, session_is_temporary = 1; // just for this request } + if (tz) osrf_app_session_set_tz(session,tz); + double start = get_timestamp_millis(); int req_id = osrfAppSessionSendRequest( session, params, method, 1 ); -- 2.43.2