############################################################################
#
#   Copyright 1999-2000 Phone.com, Inc.  All rights reserved.
#
#   Subject to the terms and conditions of the SDK License Agreement, 
#   Phone.com, Inc. hereby grants you the right to use the UP.SDK 
#   software and its related documentation.
#
#   The Phone.com name and logo and the family of terms carrying the "UP."
#   prefix are trademarks of Phone.com, Inc. All other brands and product 
#   names may be trademarks of the respective companies with which they 
#   are associated.
#
############################################################################

######################################################################
#
# Digest.pm
#
# CLASS:
# Digest
#
# DESCRIPTION:
# The Digest class provides an API to build an UP.Link Digest.  An
# UP.Link Digest is a multipart response to a request from an
# UP.Browser.  Each Digest contains one or more entries which can be
# one of the following types:
#
#	CacheOp		- Cache operation
#	WML		- WML deck
#	Fax		- Fax data from the Fax Class
#	Alert		- Alert the user
#	Image		- Image data
#	DeviceData	- Data to be downloaded to the device
#	Unknown		- Any other type
#	
# The order in which entries are added to the Digest is significant;
# the browser processes the entries according to the order in which 
# they appear in the digest.
# The browser stores all HDML decks from a digest in the cache, and then
# displays the first HDML deck in the digest.
#
# SUPERCLASSES:
# MultipartMsg
#
# ATTRIBUTES:
# header		(From MultipartMsg)
# subParts		(From MultipartMsg)
#
# CAVEATS:
# Specifying a preamble or epilogue (from MultipartMsg) for a Digest
# is meaningless.  It will either be ignored, or may result in an
# error.
#
# The library provides convenience methods to add well-known entry types
# to the digest.  However, you can add any MsgEntity you want.  Note
# that the UP.Link will signal an error if it receives a content-type
# not listed in the Accepts header.
#
# EXAMPLE:
# require Digest;
# ...
# my $digest;
# $digest = new Digest;
# ...
# $digest->addCacheOp($Digest::InvalSvc, "?POS");
# $digest->addDeck("", $nextDeck);
# ...
# &AppUtils::OutputDigest($digest->asString());
#
######################################################################

# Require Perl 5.001 to run
require 5.001;

require Mime;
require 'DeckUtils.pl';

package Digest;

# Specify inheritance
@ISA = ("MultipartMsg");
@Digest::SUPER::ISA = @ISA;


######################################################################
#
# METHOD:
# new
#
# INDEX: (lists the categories this function is indexed under in the docs)
# digests:creating
# 
# DESCRIPTION:
# Creates a new digest.  Calling this method is a prerequisite for
# calling any other method of this class.
#
# SYNOPSIS:
# require Digest;
# . . .
# $digest = Digest->new();
#
# RETURNS:
# $self			Returns a reference to the Digest object on success	
#
# PUBLIC VARIABLES:
# $Digest::CONTENTTYPE_CHARSET - Sets the character set used in deck/alert output
#
# EXAMPLE:
# $digest = Digest->new();
#
######################################################################
sub new
{
	my($class) = shift;
	my($self);

	# Instantiate
	$self = MultipartMsg->new({"Content-type" => "multipart/mixed"});

	$self->{'fax-response'} = "";
	$self->{'has-fax'} = 0;
	$self->{'printing-now'} = 0;

	bless($self, $class);

	return $self;
}


######################################################################
#
# METHOD:
# addCookie
#
# INDEX: (lists the categories this function is indexed under in the docs)
# 
# DESCRIPTION:
# Adds a cookie to the digest HTTP Headers
#
# SYNOPSIS:
# require Digest;
# . . .
# $digest->addCookie(data);
#
# ARGUMENTS:
# $data			(string) The content data that describes the cookie
#
#		name=value; expires=date;|maxage=time-to-live; path=path;
#		domain=domainname; secure
#	 
#		* 'name=value' means (for example) 'zipcode=94063' or 'Rain=96%'
#		* only name is required
#		* date formats are based on RFCs 822, 850, 1036, and 1123
#		* 'path=/test' matches and path that starts with '/test'
#		* domain must have 2 periods if it is in com, edu, net, org, gov,
#  		  mil, int
#		* all others must have three periods
#		* secure cookies should only be transmitted over https
#		* multiple 'Set-Cookie' headers can be sent in one server response
#		* path and name must match exactly to overwrite the expiration of
#  		  an existing cookie
#
#
# EXAMPLE:
# $digest->addCookie("user=fred");
#
######################################################################

$CookieHeader = "Set-Cookie";

sub addCookie
{
	my($self) = shift;
	my($data) = shift;
	
	$self->addHeader($CookieHeader, $data);

}



######################################################################
#
# METHOD:
# addCacheOp
#
# INDEX: (lists the categories this function is indexed under in the docs)
# digests:creating
# 
# DESCRIPTION:
# Adds a cache operation to the deck
#
# SYNOPSIS:
# require Digest;
# . . .
# $digest->addCacheOp(op, url);
#
# ARGUMENTS:
# $op			(string) The operation to perform, one of \
#				$Digest::InvalSvc, invalidate all decks that prefix \
#				match the url argument, or $Digest::InvalURL, \
#				invalidate the one deck that exactly matches the url \
#				argument
# $url			(string) The URL to operate on
#
# EXAMPLE:
# $digest->addCacheOp($Digest::InvalURL, "?NS=headerList");
#
######################################################################

$InvalURL = "InvalidateURL";
$InvalSvc = "InvalidateSvc";

sub addCacheOp
{
	my($self) = shift;
	my($op) = shift;
	my($url) = shift;

	my($body);
	$body = "OpCode=";
	$body .= &AppUtils::HTTPEscapeString($op);
	$body .= "&Operand=";
	$body .= &AppUtils::HTTPEscapeString($url);
	$body .= "\n";

	my($entry);
	$entry = MsgEntity->new({"Content-type" => "application/x-up-cacheop"},
							$body);
	$self->appendSubpart($entry);
}

######################################################################
#
# METHOD:
# addDeck
#
# INDEX: (lists the categories this function is indexed under in the docs)
# digests:adding decks to;decks:adding to digests
# 
# DESCRIPTION:
# Adds a deck to the Digest.
#
# SYNOPSIS:
# require Digest;
# . . .
# $digest->addDeck($uri, $deck);
#
# ARGUMENTS:
# $uri			(string) The URI of the deck.  This URI is relative to \
#				the of the requested URI.  Specifying an absolute URI will \
#				result in an UP.Link error.
# $deck			(string) The HDML or WML deck to be added
# $charset		(Optional)(string) The charset used in the deck
#
# EXAMPLE:
# $deck = "<WML><CARD>Hello world!</CARD></WML>";
# $uri = "";
# $digest->addDeck($uri, $deck);
#
######################################################################
sub addDeck
{
	my($self) = shift;
	my($uri) = shift;
	my($deck) = shift;
	my($charset) = shift;
	my($entry);
	my($contentType);
	my ($deckPrefix);
	
	$contentType = &AppUtils::GetContentType($charset);	
	if ($AppUtils::DECKTYPE != $AppUtils::DECKTYPE_HDML) {
		$deckPrefix = $AppUtils::XML_HEADER . &AppUtils::GetDTD();
	}
	
	if ($uri ne "") {
		$entry = MsgEntity->new({"Content-type" => $contentType,
								 "Content-location" => $uri},
								$deckPrefix . $deck);
	} 
	else {
		$entry = MsgEntity->new({"Content-type" => $contentType},
								$deckPrefix . $deck);
	}
	$self->appendSubpart($entry);
}

######################################################################
#
# METHOD:
# addAlert
#
# INDEX: (lists the categories this function is indexed under in the docs)
# digests:adding alerts to;alerts, adding to digests
# 
# DESCRIPTION:
# Adds an alert to the digest.  An alert causes the browser to signal 
# the user (for example, by beeping) that new, important information 
# has arrived.  Alerts are listed in a built-in alert card in the browser.   
# When you add an alert you specify a URL that the user can request by 
# choosing the alert in the alert card.
#
# SYNOPSIS:
# require Digest;
# . . .
# $digest->addAlert($url, $type,$msg);
#
# ARGUMENTS:
# $url			(string) The URL of the deck associated with the alert. \
#				This can be either absolute or relative.
# $type			(string) The type of alert, i.e. how to signal the user \
#				the alert has arrived.  This string contains a comma \
#				separated list of alert types from the following: \
#						DEFAULT, SOUND1, SOUND2, SOUND3, SOUND4, \
#						VISUAL1, VISUAL2, VISUAL3, VISUAL4, \
#						VIBRATE1
# $msg			(string) The test message to display in the alert list
# $charset		(Optional)(string) The charset used in the alert
#
# EXAMPLE:
# $digest->addAlert("newMessages.cgi", "DEFAULT", "You have new mail");
#
# $digest->addAlert("sirens.cgi", "SOUND3,VIBRATE1", "The house is burning!");
#
# CAVEATS:
# The browser coalesces alerts by URL.  This allows the application to
# update the text of a previous alert.
#
######################################################################
sub addAlert
{
	my($self) = shift;
	my($url) = shift;
	my($type) = shift;
	my($msg) = shift;
	my($charset) = shift;
	my($contentType);

	#
	# Convert type to abbreviated form which is a four-character
	# string of the form DSBV where
	#	D ->	'D' = DEFAULT, otherwise '-'
	#	S ->	'1' = SOUND1, '2' = SOUND2, ..., otherwise '-'
	#	B ->	'1' = VISUAL1, '2' = VISUAL2, ..., otherwise '-'
	#	V ->	'1' = VIBRATE1, otherwise '-'
	#
	# Example:
	#	DEFAULT  ==  "D---"
	#   SOUND1,VISUAL1 == "-11-"
	#   SOUND3,VIBRATE1 == "-3-1"
	#
	my($default) = my($beep) = my($blink) = my($vibrate) = "-";
	foreach $_ (split(",", $type)) {
		if (/\s*DEFAULT\s*/) { $default = "D" };
		if (/\s*SOUND([1-4])\s*/) { $beep = $1 };
		if (/\s*VISUAL([1-4])\s*/) { $blink = $1 };
		if (/\s*VIBRATE(1)\s*/) { $vibrate = $1 };
	}
	$type = $default . $beep . $blink . $vibrate;

	# Add entry to the digest
	my($entry, $body);
	$body = "Type=";
	$body .= &AppUtils::HTTPEscapeString($type);
	$body .= "&Title=";
	$body .= &AppUtils::HTTPEscapeString($msg);
	$body .= "&URL=";
	$body .= &AppUtils::HTTPEscapeString($url);
	$body .= "\n";
	
	if (! defined($charset)) {
		$charset = $CONTENTTYPE_CHARSET;
	}

	$contentType = "application/x-up-alert";
 
    # Prepend the 'charset=' part if not present in the string.
    #
    if (length($charset) > 0) {
        if ($charset =~ /charset\=/i) {
            $contentType = $contentType . ";" . $charset;
        }
        else {
            $contentType = $contentType . ";charset=" . $charset;
        }
    }

	$entry = MsgEntity->new({"Content-type" => $contentType},
							$body);
	$self->appendSubpart($entry);
}


######################################################################
#
# METHOD:
# addImage
#
# INDEX: (lists the categories this function is indexed under in the docs)
# digests:adding images to;images, adding to digests
# 
# DESCRIPTION:
# Adds an image to a digest.
#
# SYNOPSIS:
# require Digest;
# . . .
# $digest->addHDMLDeck($type, $uri, $image);
#
# ARGUMENTS:
# $type			(string) The MIME content type of the image
# $uri			(string) The URI of the image.  This URI is relative to \
#				the requested URI.  Specifying an absolute URI will \
#				result in an UP.Link error.
# $image			(string) The image data
#
#
# EXAMPLE:
# $digest->addHDMLDeck($Digest::GIFImage, "images/foo.gif", $image);
#
# CAVEATS:
# Sending an image to the browser whose type is not listed in the
# Accepts header will result in an error.
#
######################################################################

sub addImage
{
	my($self) = shift;
	my($type) = shift;
	my($uri) = shift;
	my($image) = shift;
	my($entry);

	if ($uri ne "") {
		$entry = MsgEntity->new({"Content-type" => $type,
								 "Content-location" => $uri},
								$image);
	} 
	else {
		$entry = MsgEntity->new({"Content-type" => $type},
								$image);
	}
	$self->appendSubpart($entry);
}


######################################################################
#
# METHOD:
# addDeviceData
#
# INDEX: (lists the categories this function is indexed under in the docs)
# digests:adding device data to;device data, adding to digests
# 
# DESCRIPTION:
# Adds device data to a digest.
#
# SYNOPSIS:
# require Digest;
# . . .
# $digest->addDeviceData($name, $data);
#
# ARGUMENTS:
# $name			(string) The name of the data.  The name is interpreted
#				in a browser-specific manner to determine what to do
#				with the data
# $data			(string) The device data
#
# EXAMPLE:
# $digest->addDeviceData("phonebook", $phoneBookData);
#
######################################################################
sub addDeviceData
{
	my($self) = shift;
	my($name) = shift;
	my($data) = shift;
	my($entry);

	$entry = MsgEntity->new({"Content-type" => "application/x-up-device;" .
											   "name=$name"},
							$data);
	$self->appendSubpart($entry);
}

######################################################################
#
# METHOD:
# addFax
#
# INDEX: (lists the categories this function is indexed under in the docs)
# digests:adding fax to;fax:adding to digests
# 
# DESCRIPTION:
# Adds a fax to the Digest.
#
# SYNOPSIS:
# require Digest;
# . . .
# $digest->addFax($fax);
#
# ARGUMENTS:
# $fax			(data) The Fax to be added
#
# EXAMPLE:
# $fax = new Fax
# $digest->addFax($fax);
#
######################################################################
sub addFax
{
	my($self) = shift;
	my($fax) = shift;

	my $faxResponse = MultipartMsg->new({"Content-type" => "multipart/x-up-fax"});

	# Add the digest and the fax as subparts
	$faxResponse->appendSubpart($self);
	$faxResponse->appendSubpart($fax);

	$self->{'fax-response'} = $faxResponse;
	$self->{'has-fax'} = 1;

}

######################################################################
#
# METHOD:
# asString
#
# INDEX: (lists the categories this function is indexed under in the docs)
# multipart messages:printing as strings
# 
# DESCRIPTION:
# Converts the digest message to a string for printing.
#
# RETURN:
# $content		The entire message as a single scalar string
#
# EXAMPLE:
# $content = $digest->asString();
#
# CAVEAT:
# On non-Unix systems, you should call "binmode" on a file descriptor
# before writing this string out to it.
#
######################################################################
sub asString
{
	my($self) = shift;
	
	my $output;

	if ($self->{'has-fax'} == 1 && $self->{'printing-now'} == 0) {
		#Set the following variable so that when the fax response
		#is being serialized, this function is called again and only
		#the digest part should be serialized
		$self->{'printing-now'} = 1;
		$output = \($self->{'fax-response'}->asString());
		$self->{'printing-now'} = 0;
		return $$output;
	}
	else {
		return $self->Digest::SUPER::asString();
	}		
}

1;

