#!/usr/local/bin/perl
#--------------------------------------------------------------------
#
#  MODULE NAME: 
#      Main
#  PROGRAM NAME: 
#      Thread
#  FILE NAME: 
#      thread
#  DESCRIPTION: 
#      This is the main CGI script that handles nearly all of
#      Thread's functions, including reading and posting.  Below are
#      included a complete description of all the implementation details
#  FUNCTION LIST: 
#      ViewMessage, Post, WritePart, ViewThread, ThreadStub, GetUser,
#      GetPart, LoginReply, PostForm, UsernameForm, OpeningMessage, Init
#  AUTHOR(S):
#      Matthew Gray <mkgray@mit.edu>
#  ORIGINAL DATE: 
#      11/28/94
#  LAST MODIFIED DATE: 
#      11/30/94
#  NOTES: 
#--------------------------------------------------------------------
# This software is the property of net.Genesis Corp.
# Copyright (c) net.Genesis 1994
#
# It may not be copied, ditributed or modified, in part or in whole, by
# any means whatsoever, without the explicit written permission of
# net.Genesis Corp. This copyright notice MUST be included in all
# copies or portions of the software.
#
# THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL NET.GENESIS CORP. BE HELD LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN ACTIOIN OF CONTRACT, TORT OR
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
# THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
#
#---------------------------------------------------------------------

########
# TODO #
########
#
# fix paths to use SCRIPT_NAME (I think mostly done)
#

######################################################################
# Info stored about users
######################################################################
######################################################################

######################################################################
# How messages are represented internally
######################################################################
#
# Each message is it's own directory, with the following items in the
# directory:
#   Files
# 	Title
#	Body
#	Author
#	Description
#	NOREPLY		(if this file exists, the message cannot be
# 			 replied to)
#	Attachments
#   Sub-directories
#	numbered subdirectories are used for children of a message
#	The parent of a message is also obtained by the directory
# 	structure
#########################################################################

##########################################################################
# Actions
##########################################################################
# ""			The null action returns the opening message to the
#			user
# "/usernameform"	Gives the Username Form
# "/login"		Log in to Thread
# "/threadview/..."     View message "..." in Thread Mode
# "/message/..."	View message "..." in Message Mode
# "/postform/..."	View a Post form for a response to "..."
# "/post/..."		Post response to "..."
##########################################################################

##########################################################################
# Other Logistical stuff
##########################################################################
# message paths do not begin with or end with a slash
# the threadRoot does not end with a slash
##########################################################################

# =========================== REQUIRES =============================== #

# This package deals with the escaped and formatted form input
# such as foo=bar&baz=quux%20quux
require 'parseform.pl';

# ======================= GLOBAL VARIABLES =========================== #

$threadRoot = "/tmp/thread";	# Specifies where the Thread discussion
				# data is stored

$action = $ENV{'PATH_INFO'};	# This is the Tread action being
			    	# taken, for example a view, a post or
			    	# whatever.  See the description of
			    	# the various kinds of actions above

$state = $ENV{'QUERY_STRING'};	# Certain state information, such as
			      	# the username, the degree of detail,
			      	# and the depth being viewed are
			      	# stored in the query string.  This is
			      	# encoded here as 'state' for ease of
			      	# tacking on to the end of URLs in
			      	# links.

$sn = $ENV{'SCRIPT_NAME'};	# Some links can't be accomplished
			  	# with relative links so the full URL
			  	# sometimes needs to be reconstructed.
			  	# This allows that.

%form = &parseform($state);
				# This puts the state info into an
				# associative array for easy access of
				# the individual elements

$form{'depth'} = 4 unless $form{'depth'};
				# Set a default depth of 4.  I pulled
				# the number 4 out of my butt.

$form{'detail'} = 'descriptions' unless $form{'detail'};
				# Set a default detail of
				# 'descriptions' cuz I think that's
				# reasonable.

# ========================== MAIN CODE =============================== #


&Init;				# Initialize some stuff, duh
$user = &GetUser();		# Make sure we have a username
				# somewhere, somehow, or set it to
				# Unknown if they haven't given us one

&OpeningMessage if !$action;	# Default behavior.  Gives them
			    	# somewhere to start

# The following lines dispatch various actions

&UsernameForm 		if($action eq "/usernameform");
&LoginReply 		if($action eq "/login");
&ViewThread($1) 	if($action =~ m,^/threadview/(.*),);
&ViewMessage($1) 	if($action =~ m,/message/(.*),);
&PostForm($1)		if($action =~ m,/postform/(.*),);
&Post($1)		if($action =~ m,/post/(.*),);

print("Thread Exiting Abnormally, but it's probably ok\n");
exit;				# The functions should exit
				# themselves, but this is here as a
				# safety measure in case for some
				# reason they don't (like we forget to
				# make them exit under all circumstances)


# ========================== FUNCTIONS =============================== #

#----------------------------------------------------------------
#
#  FUNCTION NAME:
#      ViewMessage
#  DESCRIPTION:
#      This function provides basic ability to view a message.  Spits
#      out the important information and contents of the message as
#      well as doing any relevant formatting or graphics stuff.  It
#      also provides a list of links to child messages of the one
#      being viewed.
#  REQUIRES:
#      None
#  ARGUMENTS:
#     A message path.  That is, the message to be viewed.
#  RANGE OF INPUTS: 
#    No ranges.  Any valid message path will do.  Things that aren't
#    message paths will confuse it and probably not cause any real
#    problems, except for it printing a message that has "Foo
#    unknown" for each part Foo
#  RETURN VALUES:
#     Doesn't return anything.  Exits.
#  INVARIANT:
#     Everything.  Is just a display function.
#  NOTES:
#     Needs to be added to and fixed.  Certain fields need to be added.  It
#     needs to include things like the Author and probably some
#     buttons or something as specified in the spec, but each of
#     those features is one line of code.
#     Pretty graphics should appear identifying each part and the
#     Thread logo should appear at the top and stuff.
#----------------------------------------------------------------

sub ViewMessage {
    local($message) = @_;

				# Print Title, Description and Body
				# Separated by horizontal rules
    print("<h1>", &GetPart("Title", $message), "</h1>\n");
    print("<hr>", &GetPart("Description", $message), "<hr>\n");
    print(&GetPart("Body", $message));
    print("<hr>\n");

				# List Replies
    print("<h1>Replies</h1>\n");
    &ThreadStub($message, 0);
    exit;
}

#----------------------------------------------------------------
#
#  FUNCTION NAME:
#     Post
#  DESCRIPTION:
#     Handle posting of new messages to Thread.
#  REQUIRES:
#     Nothing
#  ARGUMENTS:
#     Message path.  Contents of the post and the like are part of the
#     associative array, %form
#  RANGE OF INPUTS: 
#     Any valid message path.
#  RETURN VALUES:
#     None.  Exits.
#  INVARIANT:
#     Most things.  It just creates the message in the file system.
#  NOTES:
#     Needs to be added to lots.  At the moment it takes a post and
#     adds it, and that's it.  It actually only adds a certain set of
#     fields, but I think those are all of them.  (Yes?)  At the
#     moment, it only spits back some reassuring message saying that
#     your transaction has been posted.  This should probably be
#     expanded to include the text of what you posted and then a
#     list of options to keep reading or something.
#----------------------------------------------------------------

sub Post {
    local($message) = @_;

				# Find out the last sibling message
				# and assign this one a value of that
				# plus 1.  There might be a faster way
				# of doing this, but I don't know if
				# this is really that slow.
    opendir(THREADDIR, "$threadRoot/$message") || 
	print("Can't open \"$message\"<br>");
    (@dirs) = readdir(THREADDIR);
    $max = 0;
    for $d (@dirs){
	$max = ($d >$max) ? $d: $max;
    }
    $newmsg = $max+1;

				# Create the message (the directory
				# itself) and add all the parts.
    mkdir("$threadRoot/$message/$newmsg", 0755);
    &WritePart("Title", $form{'title'}, "$message/$newmsg");
    &WritePart("Author", $form{'user'}, "$message/$newmsg");
    &WritePart("Description", $form{'description'}, "$message/$newmsg");
    &WritePart("Body", $form{'body'}, "$message/$newmsg");

				# Give the user reassuring feedback
				# and exit.
    print("<h1>Posted, I think</h1>\n");
    exit
}

#----------------------------------------------------------------
#
#  FUNCTION NAME:
#     WritePart
#  DESCRIPTION:
#     This is a handy dandy utility function for actually writing out
#     parts of messages to the appropriate files.
#  REQUIRES:
#     None.
#  ARGUMENTS:
#     Part: This is the part that is being written, for example the
#     Title, or Description or whatever
#
#     Contents: What to write to this part
#
#     Message: What message this part is associated with
#  RANGE OF INPUTS: 
#     Part: anything.  This becomes the filename.  It should therefore
#     be one of the ones described above in the 'internal
#     representation of messages'  comment block.  These are the only
#     ones that should be used.   Others will be ignored.  Numeric
#     part names should be avoided as numerics are used for
#     directories (ie, children of the message)
#
#     Contents: anything.  This becomes the contents of the file.
#
#     Message: any valid message path
#  RETURN VALUES:
#     Nothing.  Maybe this should be changed to return a value
#     confirming the part was successfully written, but that would
#     also require adding error checking every time it is called.
#  INVARIANT:
#     Creates a file.  That's it.
#  NOTES:
#     This is complete, except for the possibility of a return value.
#     It's a utility function.  It only gets so complex.
#----------------------------------------------------------------

sub WritePart {
    local($part, $contents, $message) = @_;

    open(MSG, ">>$threadRoot/$message/$part");
    print MSG $contents;
    close(MSG);
}

#----------------------------------------------------------------
#
#  FUNCTION NAME:
#     ViewThread
#  DESCRIPTION:
#     This is the basic function for viewing a set of transactions in
#     a sort of 'overview' mode in Thread.  (that is, Thread View
#     Mode)  It gives a <UL> to the depth specified in the state of
#     all the transactions that are children of the message in the
#     argument, and displays that message at the top of the page.
#  REQUIRES:
#    Nothing.
#  ARGUMENTS:
#     Message.  All other info is in the state (as reflected in %form
#     for easy access)
#  RANGE OF INPUTS: 
#     Any valid message path.
#  RETURN VALUES:
#     Nothing.  Displays Thread to user.
#  INVARIANT:
#     makes no changes to anything
#  NOTES:
#     This should be prettied up.  Graphics should be added and all
#     that stuff.  It should also probably indicate somehow when
#     individual threads go deeper even though they are not
#     displayed (due to a depth restriction)
#----------------------------------------------------------------

sub ViewThread {
    local($message) = @_;

				# First, display the top message we
				# are viewing, along with the option
				# to reply to it.  Should this be
				# formatted and should it depend on
				# the detail value?
    $title = &GetPart("Title", $message);
    $description = &GetPart("Description", $message);
    print("<h1>Thread View</h1><h1><a href=\"$sn/message/$message?$state\">$title</a></h1>\n");
    print("<a href=\"$sn/postform/$message?$state\">[Post Followup]</a>\n");
    print("<hr>$description<hr>\n");

				# Now actually display the list of
				# children and their children, etc.
    &ThreadStub($message, 0);
    exit;
}

#----------------------------------------------------------------
#
#  FUNCTION NAME:
#     ThreadStub
#  DESCRIPTION:
#     Function that actually traverses the Thread trees and displays
#     the transactions to the user.  Functions recursively.  It's a
#     tree traversal, duh.
#  REQUIRES:
#     Nothing.
#  ARGUMENTS:
#     The message, and a depth.
#  RANGE OF INPUTS: 
#     The message is any valid message path and the depth is some
#     integer value.
#  RETURN VALUES:
#     Returns nothing.  Displays the traversed tree to user.
#  INVARIANT:
#     Just displays output.
#  NOTES:
#     This should probably actually be split into two functions, one
#     which does the tree traversal and another called something like
#     DisplayMessage or DisplayMessageInList which would do the
#     actual formatting depending on the desired detail level.  Pretty
#     graphics and whatnot need to be added as well.
#----------------------------------------------------------------

sub ThreadStub {
    local($message, $howdeep) = @_;
    local($messageslash);
    local(*THREADDIR);

				# Make sure we haven't gone too deep
    return if $howdeep > $form{'depth'};

				# This is a dumb and annoying hack to
				# deal with the fact that the root
				# message is 'special'.  That is, for
				# all messages but the root message,
				# "something/$message/more/stuff" is a
				# valid path, as any valid message
				# path does not begin or end with a
				# slash.  However, the root message
				# has a path of "" which yields a
				# total path with a double slash in
				# it.  This fixes that, albeit kludgily.
    $messageslash = $message."/" if $message;

				# Begin a list
    print("<ul>\n");
    @dirs= ();

				# Open up and read the directory.
				# This should do something more
				# intelligent if it for some reason
				# cannot open a message directory,
				# although theoretically, that should
				# never happen, so perhaps this sort
				# of response is appropriate.
    opendir(THREADDIR, "$threadRoot/$message") || 
	print("Can't open \"$message\"<br>");
    (@dirs) = readdir(THREADDIR);

				# Find all the children
    $max = 0;
    for $d (@dirs){
	$max = ($d >$max) ? $d: $max;
    }

				# View the children in order of posting
    for $postnum (1..$max) {
				# This could become a function of it's
				# own to add some flexibility of
				# formatting and some modularity.
	print("<li><a href=\"$messageslash$postnum?$state\">", &GetPart("Title", "$messageslash$postnum"), "</a> (MSG $postnum [$message])\n");
	if($form{'detail'} eq "descriptions"){
	    print("<br>\n", &GetPart("Description", "$messageslash$postnum"));
	}
	if($form{'detail'} eq "full"){
	    print("<br><br>\n", &GetPart("Body", "$messageslash$postnum"));
	}
				# Display any of it's children
	&ThreadStub("$messageslash$postnum", $howdeep++);
    }

				# All done.  Clean up with this set of
				# children.
    close(THREADDIR);
    print("</ul>\n");
}

#----------------------------------------------------------------
#
#  FUNCTION NAME:
#     GetUser
#  DESCRIPTION:
#     Get's the username (which is stored in the state usually) or
#     returns 'Unknown' if it's not for some reason.  The only reason
#     this deserves to be a full fledged function is that eventually
#     it will include the ability to determine the user by some
#     other way in which the client passes it, like the From header
#     or whatever.
#  REQUIRES:
#     None.
#  ARGUMENTS:
#    None.
#  RANGE OF INPUTS: 
#    bluh
#  RETURN VALUES:
#    Returns either the username of the user or the string "Unknown"
#  INVARIANT:
#    err....
#  NOTES:
#    At the moment, this is silly to have as a function but the
#    reasons described above in the description I think justify it.
#    If not, it is called in only one place, so could be removed and
#    wouldn't really change anything except adding a small amount of
#    complexity to the top level of the code.
#----------------------------------------------------------------

sub GetUser {
    # Determines what user is posting, 
    # based on $action and $ENV{'QUERY_STRING'}
	
    if(!$action || ($action eq "/usernameform" || !$form{'user'})){
	"Unknown";
    }
    else{
	$form{'user'};
    }
}

#----------------------------------------------------------------
#
#  FUNCTION NAME:
#     GetPart
#  DESCRIPTION:
#     Counterpart of WritePart.  Reads parts of messages (like Titles
#     and such) from the filesystem.
#  REQUIRES:
#     nothing.
#  ARGUMENTS:
#     The desired part and the message.
#  RANGE OF INPUTS: 
#     The part needs to be a valid message part and the message a
#     valid message path.
#  RETURN VALUES:
#     Returns the contents of the part.
#  INVARIANT:
#     err...
#  NOTES:
#     This should probably do some error reporting or something.   I
#     think at the moment, if an invalid part is requested, you just
#     get "Part is unavailable" or something, which isn't all that
#     bad, but would be nice if something better were done...
#----------------------------------------------------------------

sub GetPart {
    local($part, $message) = @_;

    $msgpart=""; $nofile = 0;
    open(MSG, "$threadRoot/$message/$part") || ($nofile = 1);
    while(<MSG>){
	$msgpart .=$_;
    }
    close(MSG);
    if($nofile){
	$msgpart = "$part not available";
    }
    $msgpart;
}

#----------------------------------------------------------------
#
#  FUNCTION NAME:
#     LoginReply
#  DESCRIPTION:
#     This deals with a user login.  Gives the user a reply and adds
#     them to the database appropriately, or if the username
#     requested already exists, logs them in as that user.
#     At the moment, it only actually adds the username to their
#     state.  Yipee.
#  REQUIRES:
#     nothing.
#  ARGUMENTS:
#     none.  All relevant info is in the state (as it was entered in a form)
#  RANGE OF INPUTS: 
#     uh, none.
#  RETURN VALUES:
#     Displayes the response to the login to the user
#  INVARIANT:
#     all.
#  NOTES:
#     This doesn't really do anything right now.  It should, I guess.
#----------------------------------------------------------------

sub LoginReply {
    # Actually make sure the name isn't already taken or something
    # For now, just say "ok, thanks, here's a link to Thread"
    print <<REPLYEND;
<h1>Thanks.  For now, it doesn't matter.</h1>
<ul>
<li><a href="usernameform?$state">Log in</a>
<li><a href="threadview/?$state">Start reading from root</a>
</ul>
REPLYEND
}

#----------------------------------------------------------------
#
#  FUNCTION NAME:
#     PostForm, UsernameForm, OpeningMessage
#  DESCRIPTION:
#     This are such bland functions they don't get a full writeup
#     each.  They spew some text straight out to the user.
#  REQUIRES:
#     Nothing
#  ARGUMENTS:
#     None.
#  RANGE OF INPUTS: 
#     none.
#  RETURN VALUES:
#    Displays a document to the user.
#  INVARIANT:
#    hrm.
#  NOTES:
#    These display static documents.  They should have graphics and
#    stuff added to them.  PostForm needs a lot of stuff from the
#    spec added to it, and the Post function needs to be updated
#    accordingly.  At the moment I can't think of why they every
#    would, but theoretically these could at somepoint need to
#    become dynamic documents of some sort.
#----------------------------------------------------------------

sub PostForm {
    local($message) =@_;
    
    $newtitle=$title=&GetPart("Title", $message);
    $newtitle="Re: ".$newtitle unless $newtitle=~/^Re: /;


    print <<FORMEND;
<h1>Posting Response to "$title"</h1>
Author: $user

<form action=$sn/post/$message>
<input type=hidden name=user value=$user>
Title: <input name=title value="$newtitle"><p>
Description:
<textarea rows=4 cols=30 name=description></textarea>
<p>
Body: <p>
<textarea name=body rows=15 cols=40></textarea>
<p>
<input type=submit value=Post> <input type=reset value="Clear Form">
</form>
FORMEND
    exit;
}

sub UsernameForm {
    # Spit out the username form
    print <<FORMEND;
<h1>Thread Login</h1>
<form action="login">
Login: <input name="user"><p>
<input type=submit value=Login>
</form>
FORMEND
    exit;
}

sub OpeningMessage {
	# Spit out opening message to user
    print <<MSGEND;
<h1>Welcome to Thread</h1>
<ul>
<li><a href="thread/usernameform?$state">Log in</a>
<li><a href="thread/threadview/?$state">Start reading from root</a>
</ul>
MSGEND
    exit;
}

#----------------------------------------------------------------
#
#  FUNCTION NAME:
#     Init
#  DESCRIPTION:
#     Do some basic initialization stuff, like create the Thread dir
#     if it doesn't exist yet and spew the basic header that says
#     we're sending an HTML document.
#  REQUIRES:
#    none.
#  ARGUMENTS:
#    none.
#  RANGE OF INPUTS: 
#    none.
#  RETURN VALUES:
#    none.
#  INVARIANT:
#    creates a directory
#  NOTES:
#    This is mostly here to add stuff to if need be and to put this
#    otherwise fairly yucky logistical stuff
#----------------------------------------------------------------

sub Init {
    if(!-e $threadRoot){
	mkdir($threadRoot, 0755);
    }

    print("Content-Type: text/html\n\n");
}
