]> Pileus Git - vpaste/commitdiff
Add view command, embedding, and caching
authorAndy Spencer <andy753421@gmail.com>
Mon, 7 Jan 2013 05:32:57 +0000 (05:32 +0000)
committerAndy Spencer <andy753421@gmail.com>
Mon, 7 Jan 2013 05:43:00 +0000 (05:43 +0000)
This is mostly related to embedding pastes in external pages. Script
tags can be used to link an existing paste, or format a new paste based
on the text enclosed in the script tag. Code tags can be converted to
pastes as well.

When formatting text that is not in an existing paste, the text is not
saved and is not shown on the main page. This is done using the view
command.

Additionally, caching was added to improve load times, specifically for
text enclosed in script/code tags. The TOhtml output is saved in a file
based on the md5sum of the text and some system version information.

A new response function was also added to simplify some processing. It
combines the message and header functions and supports additional things
like gzipped input files (for cached pages) and Access Control headers.

.gitignore
embed.js [new file with mode: 0644]
index.cgi
vimrc

index b085d33055148b2fc1aadf60272b64d1771caeba..eaf04f615bf9e966abecad562ec852d9f4b8fb29 100644 (file)
@@ -1,5 +1,6 @@
 *~
 *.swp
 favicon.ico
 *~
 *.swp
 favicon.ico
+cache/*
 db/*
 spam/*
 db/*
 spam/*
diff --git a/embed.js b/embed.js
new file mode 100644 (file)
index 0000000..d949c42
--- /dev/null
+++ b/embed.js
@@ -0,0 +1,154 @@
+// Copyright (C) 2013 Andy Spencer
+//
+// This program is free software: you can redistribute it and/or modify it under
+// the terms of the GNU Affero General Public License as published by the Free
+// Software Foundation, either version 3 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 Affero General Public License for more
+// details.
+
+/* Constants */
+var vpaste = 'http://vpaste.net/';
+
+var light  = '#f4f4f4';
+var dark   = '#111133';
+var border = '#8888dd';
+
+var styles = [
+       'background: ' + light + ';',
+       'border: solid 1px ' + border + ';',
+       'padding: 0.5em;',
+       'overflow: auto;',
+       'display: block;',
+       'white-space: pre;',
+       'text-align: left;',
+];
+
+/* Globals */
+var query;
+
+/* Update Style Sheet */ 
+function update_styles(style, name) {
+       var text  = style.innerHTML;
+       var lines = text.split(/\n/);
+       for (var i = 0; i < lines.length; i++) {
+               var line = lines[i];
+               line = line.replace(/#ffffff/, light);
+               line = line.replace(/#000000/, dark);
+               if (line.match(/^pre/))
+                       line = line.replace(/^pre/, '.'+name);
+               else if (line.match(/^\./))
+                       line = '.'+name+' '+line;
+               else
+                       line = '';
+               lines[i] = line;
+       }
+       style.innerHTML = lines.join('\n');
+}
+
+/* Embed paste into page  */
+function embed_paste(ajax, style, html, name) {
+       if (ajax.readyState != 4 && ajax.readyState != 'complete')
+               return;
+       if (!ajax.responseXML)
+               throw new Error('No response XML: ' + ajax.responseText);
+       var xml    = ajax.responseXML;
+       var vstyle = xml.getElementsByTagName('style')[0];
+       var vhtml  = xml.getElementsByTagName('pre')[0];
+       update_styles(vstyle, name);
+       style.innerHTML += vstyle.innerHTML;
+       html.innerHTML   = vhtml.innerHTML.replace(/^\n*/, '');
+       html.style.visibility = 'visible';
+}
+
+/* Strip whitespace from a paste */
+function strip_spaces(text) {
+       var prefix = null;
+       var lines  = text.replace(/^\s*\n|\n\s*$/g,'').split('\n');
+       for (i in lines) {
+               if (lines[i].match(/^\s*$/))
+                       continue;
+               var white = lines[i].replace(/\S.*/, '')
+               if (prefix === null || white.length < prefix.length)
+                       prefix = white;
+       }
+       for (i in lines)
+               lines[i] = lines[i].replace(prefix, '');
+       return lines.join('\n');
+}
+
+/* Start embedding a paste */
+function query_paste(method, url, body, html, name) {
+       /* Add style tag box */
+       var style = document.createElement('style');
+       style.type      = "text/css";
+       style.innerHTML = '.' + name + ' { ' + styles.join(' ') + ' }';
+       document.head.insertBefore(style, document.head.firstChild);
+
+       /* Get AJAX object */
+       var ajax = null;
+       if (!ajax) ajax = new XMLHttpRequest();
+       if (!ajax) ajax = new ActiveXObject('Microsoft.XMLHTTP');
+       if (!ajax) throw new Error('Cannot get AJAX object');
+
+       /* Insert default query */
+       if (query)
+               url = url.replace(/[?]/, '?' + query + ',');
+
+       /* Run AJAX Request */
+       ajax.onreadystatechange = function() {
+               embed_paste(ajax, style, html, name) };
+       ajax.open(method, url, true);
+       ajax.setRequestHeader('Accept', 'text/html');
+       ajax.overrideMimeType('application/xhtml+xml');
+       ajax.send(body);
+}
+
+/* Start embedding a paste */
+function start_embed() {
+       /* Get current paste information */
+       var scripts = document.getElementsByTagName('script');
+       var script  = scripts[scripts.length-1];
+       var text    = strip_spaces(script.innerHTML);
+       var name    = 'vpaste_s' + scripts.length;
+       var regex   = /^[^?]*[?]?(([a-zA-Z0-9.]*)[?&, ]?(.*))$/;
+       var parts   = script.src.match(regex);
+
+       /* Handle header tags */
+       if (!text && !parts[2] || !document.body)
+               return query = parts[1];
+
+       /* Add paste box */
+       var html = document.createElement('pre');
+       html.innerHTML = 'Loading..';
+       html.className = script.className + ' vpaste ' + name;
+       document.body.appendChild(html);
+
+       /* Query the paste */
+       if (text)
+               query_paste('POST', vpaste+'view?'+parts[1],
+                       text, html, name);
+       else
+               query_paste('GET', vpaste+parts[2]+'?'+parts[3],
+                       null, html, name);
+}
+
+/* Convert all code tags to pastes */
+function format_code(type) {
+       var tags = document.getElementsByTagName('code');
+       for (var i = 0; i < tags.length; i++) {
+               var tag = tags[i];
+               if (type && tag.className != type)
+                       continue;
+               var name = 'vpaste_c' + i;
+               var text = strip_spaces(tag.innerHTML);
+               tag.className     += ' ' + name;
+               query_paste('POST', vpaste+'view?'+tag.title,
+                               text, tag, name);
+       }
+}
+
+start_embed();
index aacc8751cd754ef7f0562586a87c2f5e33117437..2fea18e08777fc4ae33a01767a8d85c74b72d4fb 100755 (executable)
--- a/index.cgi
+++ b/index.cgi
@@ -1,6 +1,6 @@
 #!/bin/bash
 
 #!/bin/bash
 
-# Copyright (C) 2009-2012 Andy Spencer
+# Copyright (C) 2009-2013 Andy Spencer
 #
 # This program is free software: you can redistribute it and/or modify it under
 # the terms of the GNU Affero General Public License as published by the Free
 #
 # This program is free software: you can redistribute it and/or modify it under
 # the terms of the GNU Affero General Public License as published by the Free
@@ -44,31 +44,114 @@ function cut_file {
        ' | head -c $((128*1024)) # Limit size to 128K
 }
 
        ' | head -c $((128*1024)) # Limit size to 128K
 }
 
-# Print out a generic header
-function header {
-       echo "Content-Type: $1; charset=UTF-8"
+# Respond to a request
+function respond {
+       local allow ctype heads files texts gzip h f
+       while [ "$1" ]; do
+               case $1 in
+                       -y) allow="true"; ;;
+                       -c) ctype="$2"; shift ;;
+                       -h) heads=("${heads[@]}" "$2"); shift ;;
+                       -f) files=("${files[@]}" "$2"); shift ;;
+                       *)  texts=("${texts[@]}" "$1"); ;;
+               esac
+               shift
+       done
+
+       # Check if the browser supports gzip
        if [[ "$HTTP_ACCEPT_ENCODING" == *'gzip'* ]]; then
        if [[ "$HTTP_ACCEPT_ENCODING" == *'gzip'* ]]; then
+               gzip=true
+       fi
+
+       # Output header
+       if [ "$ctype" ]; then
+               echo "Content-Type: $ctype; charset=UTF-8"
+       else
+               echo "Content-Type: text/plain; charset=UTF-8"
+       fi
+       if [ "$gzip" ]; then
                echo "Content-Encoding: gzip"
                echo "Content-Encoding: gzip"
-               echo
+       fi
+       if [ "$allow" ]; then
+               echo "Access-Control-Allow-Origin: *"
+               echo "Access-Control-Allow-Headers: Content-Type"
+       fi
+       for h in "${heads[@]}"; do
+               echo "$h"
+       done
+       echo
+
+       # Output text messages
+       if [ "$texts" ]; then
+               if [ "$gzip" ]; then
+                       echo "${texts[@]}" | gzip
+               else
+                       echo "${texts[@]}"
+               fi
+               exit
+       fi
+
+       # Output body files
+       if [ "$files" ]; then
+               for f in "${files[@]}"; do
+                       if   [[   "$gzip" && "$f" != *'.gz' ]]; then
+                               gzip < "$f"
+                       elif [[ ! "$gzip" && "$f" == *'.gz' ]]; then
+                               zcat < "$f"
+                       else
+                               cat "$f"
+                       fi
+               done
+               exit
+       fi
+
+       # Gzip remaining stream
+       if [ "$gzip" ]; then
                exec 1> >(gzip)
                exec 1> >(gzip)
-       else
-               echo
        fi
 }
 
        fi
 }
 
-# Print plain message and exit
-function message {
-       while [ "$1" == '-h' ]; do
-               shift; echo "$1"; shift
-       done
-       header text/plain
-       echo "$*"
-       exit
+# Format and output a file
+function format {
+       # Create a temp file with the provided modeline
+       tmp="$(mktemp)"
+       sed "\$avim: $(get_modeline)" "$1" > "$tmp"
+
+       # Determine cache name
+       md5="$(cat index.cgi vimrc "$tmp" /usr/bin/ex | md5sum -b)"
+       out="cache/${md5% *}.htm"
+       zip="$out.gz"
+
+       # Cache the file, if needed
+       if [ ! -f "$zip" ]; then
+               # - I have some plugins in ~/.vim
+               # - Run ex in pty to trick it into thinking that it
+               #   has a real terminal, note that we also have to set
+               #   term=xterm-256color in vimrc
+               HOME=/home/andy \
+               /home/vpaste/bin/pty \
+               /usr/bin/ex -nXZ -i NONE -u vimrc \
+                       '+sil! set fde= fdt= fex= inde= inex= key= pa= pexpr=' \
+                       '+sil! set iconstring= ruf= stl= tal=' \
+                       "+sil! set titlestring=$1\ -\ vpaste.net" \
+                       '+sil! set noml' \
+                       '+sil! $d|'$2    \
+                       '+sil! %s/\r//g' \
+                       '+sil! TOhtml'   \
+                       "+sav! $out"     \
+                       '+qall!'         \
+                       "$tmp" >/dev/null 2>&1
+               gzip "$out"
+       fi
+       rm "$tmp"
+
+       # Output the file
+       respond -y -c "text/html" -f "$zip"
 }
 
 # List previous pastes
 function do_cmd {
 }
 
 # List previous pastes
 function do_cmd {
-       header text/plain
+       respond
        case "$1" in
        ls)
                ls -t db | column
        case "$1" in
        ls)
                ls -t db | column
@@ -106,47 +189,34 @@ function do_print {
                input="db/$1"
                trim='1,/^$/d' # sed command to remove cruft
        else
                input="db/$1"
                trim='1,/^$/d' # sed command to remove cruft
        else
-               message -h 'Status: 404 Not Found' \
+               respond -h 'Status: 404 Not Found' \
                        "File '$1' not found"
        fi
 
                        "File '$1' not found"
        fi
 
+       # Check for javascript
+       if [[ "$input" == 'embed.js' &&
+             "$HTTP_ACCEPT" != *'html'* ]]; then
+               respond -c text/javascript -f "$input"
+       fi
+
        # Check for raw paste
        if [[ "$QUERY_STRING" == 'raw'* ||
              "$REQUEST_URI"  != *'?'* &&
              ( "$input"       != 'db/'* ||
                "$HTTP_ACCEPT" != *'html'* ) ]]; then
        # Check for raw paste
        if [[ "$QUERY_STRING" == 'raw'* ||
              "$REQUEST_URI"  != *'?'* &&
              ( "$input"       != 'db/'* ||
                "$HTTP_ACCEPT" != *'html'* ) ]]; then
-               header text/plain
+               respond
                sed "$trim" "$input"
                exit
        fi
 
                sed "$trim" "$input"
                exit
        fi
 
-       # Create a temp file with the provided modeline
-       output="$(mktemp)"
-       tmp="$(mktemp)"
-       sed "\$avim: $(get_modeline)" "$input" > "$tmp"
-
-       # - I have some plugins in ~/.vim
-       # - Run ex in screen to trick it into thinking that it
-       #   has a real terminal, note that we also have to set
-       #   term=xterm-256color in vimrc
-       HOME=/home/andy \
-       screen -D -m ex -nXZ -i NONE -u vimrc \
-               '+sil! set fde= fdt= fex= inde= inex= key= pa= pexpr=' \
-               '+sil! set iconstring= ruf= stl= tal=' \
-               "+sil! set titlestring=$1\ -\ vpaste.net" \
-               '+sil! set noml'     \
-               '+sil! $d|'$trim     \
-               '+sil! %s/\r//g' \
-               '+sil! TOhtml'       \
-               "+sav! $output" \
-               '+qall!'        \
-               "$tmp"
-
-       header text/html
-       cat "$output"
-       rm "$tmp" "$output"
+       # Output the file
+       format "$input" "$trim"
 }
 
 }
 
+# Format a file for viewing
+function do_view {
+       format -
+}
 
 # Upload handler
 function do_upload {
 
 # Upload handler
 function do_upload {
@@ -154,9 +224,9 @@ function do_upload {
        spam=$(echo -n "$body" | cut_file "ignoreme")
        text=$(echo -n "$body" | cut_file "(text|x)")
        bans=$(echo -n "$REMOTE_ADDR" | grep -f blacklist)
        spam=$(echo -n "$body" | cut_file "ignoreme")
        text=$(echo -n "$body" | cut_file "(text|x)")
        bans=$(echo -n "$REMOTE_ADDR" | grep -f blacklist)
-       [ ! -z "$spam" ] && message "Spam check.."
-       [ ! -z "$bans" ] && message "You have been banned"
-       [   -z "$text" ] && message "No text pasted"
+       [ ! -z "$spam" ] && respond "Spam check.."
+       [ ! -z "$bans" ] && respond "You have been banned"
+       [   -z "$text" ] && respond "No text pasted"
 
        # Format and save message
        output="$(mktemp db/XXXXX)"
 
        # Format and save message
        output="$(mktemp db/XXXXX)"
@@ -171,7 +241,7 @@ function do_upload {
 
        # Redirect user
        uri="$url$(basename "$output")"
 
        # Redirect user
        uri="$url$(basename "$output")"
-       message -h 'Status: 302 Found' \
+       respond -h 'Status: 302 Found' \
                -h "Location: $uri"    \
                "$uri"
 }
                -h "Location: $uri"    \
                "$uri"
 }
@@ -188,7 +258,7 @@ function do_help {
        vpaste='<a href="vpaste?ft=sh">vpaste</a>'
        repo='https://lug.rose-hulman.edu/svn/misc/trunk/htdocs/vpaste/'
 
        vpaste='<a href="vpaste?ft=sh">vpaste</a>'
        repo='https://lug.rose-hulman.edu/svn/misc/trunk/htdocs/vpaste/'
 
-       header text/html
+       respond -c text/html
        cat <<-EOF
        <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
        cat <<-EOF
        <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
@@ -313,14 +383,15 @@ function do_help {
 
                        <div class="box" id="devel">
                                <h1>License</h1>
 
                        <div class="box" id="devel">
                                <h1>License</h1>
-                               <p>Copyright © 2009-2012
+                               <p>Copyright © 2009-2013
                                   Andy Spencer &lt;andy753421@gmail.com&gt;</p>
                                <p>See individual files for licenses</p>
 
                                <h1>Source code</h1>
                                <dl>
                                        <dt>Client</dt>
                                   Andy Spencer &lt;andy753421@gmail.com&gt;</p>
                                <p>See individual files for licenses</p>
 
                                <h1>Source code</h1>
                                <dl>
                                        <dt>Client</dt>
-                                       <dd><a href="vpaste?ft=sh">vpaste</a></dd>
+                                       <dd><a href="vpaste?ft=sh">vpaste</a>
+                                           <a href="embed.js?ft=javascript">embed.js</a></dd>
                                        <dt>Server</dt>
                                        <dd><a href="index.cgi?ft=sh">index.cgi</a>
                                            <a href="vimrc?ft=vim">vimrc</a>
                                        <dt>Server</dt>
                                        <dd><a href="index.cgi?ft=sh">index.cgi</a>
                                            <a href="vimrc?ft=vim">vimrc</a>
@@ -372,6 +443,8 @@ elif [ "$pathinfo" = head ]; then
        do_cmd head
 elif [ "$pathinfo" = stat ]; then
        do_cmd stat
        do_cmd head
 elif [ "$pathinfo" = stat ]; then
        do_cmd stat
+elif [ "$pathinfo" = view ]; then
+       do_view
 elif [ "$pathinfo" ]; then
        do_print "$pathinfo"
 elif [ "$CONTENT_TYPE" ]; then
 elif [ "$pathinfo" ]; then
        do_print "$pathinfo"
 elif [ "$CONTENT_TYPE" ]; then
diff --git a/vimrc b/vimrc
index 074722e08fc5837f22f736c3c64751c5cf0f46bc..ac81a9ed701c6665cc5937e4e20e1fffe1f5633b 100644 (file)
--- a/vimrc
+++ b/vimrc
@@ -1,4 +1,4 @@
-" Andy Spencer 2009-2011 - Public domain
+" Andy Spencer 2009-2013 - Public domain
 
 filetype plugin indent on
 syntax on
 
 filetype plugin indent on
 syntax on
@@ -20,6 +20,7 @@ let g:html_use_css        = 1
 let g:html_use_encoding   = "UTF-8"
 let g:html_no_progress    = 1
 let g:html_dynamic_folds  = 1
 let g:html_use_encoding   = "UTF-8"
 let g:html_no_progress    = 1
 let g:html_dynamic_folds  = 1
+let g:html_use_xhtml      = 1
 
 " Misc
 let g:is_bash             = 1
 
 " Misc
 let g:is_bash             = 1