~mro/pin4sha.cgi

01ff41a269f9f922d7d67f22792415715c5aa2dc — Marcus Rohrmoser 4 years ago 126ea96 pinboard.in/go
clean
21 files changed, 9 insertions(+), 1245 deletions(-)

M doap.rdf
D patches/sebsauvage/Shaarli/archive/master/001.patch
D scripts/assert.sh
D scripts/categories.rng
D scripts/download.sh
D scripts/form2post.rb
D scripts/run-tests.sh
D scripts/tagcloud-html2atom.xslt
D tests/test-atom-empty.sh
D tests/test-delete-all-ok.sh.xslt
D tests/test-delete-ok.sh.xslt
D tests/test-index.sh
D tests/test-login-ok.sh
D tests/test-pinboard-info.sh
D tests/test-post.sh
D tests/test-tagcloud.sh
D tests/test-title.sh
D tests/test_delete-all-ok.sh
D tests/test_delete-ok.sh
D tests/test_login-fail.sh
D tmp/.gitkeep
M doap.rdf => doap.rdf +9 -8
@@ 2,21 2,22 @@
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
   xmlns="http://usefulinc.com/ns/doap#">
  <Project>
    <bug-database rdf:resource="https://code.mro.name/mro/pinboard4shaarli/issues"/>
    <homepage rdf:resource="https://code.mro.name/mro/pinboard4shaarli/"/>
    <implements rdf:resource="v1/openapi.yaml"/>
    <license rdf:resource="https://code.mro.name/mro/pinboard4shaarli/src/master/LICENSE"/>
    <bug-database rdf:resource="https://code.mro.name/mro/pin4sha/issues"/>
    <homepage rdf:resource="https://code.mro.name/mro/pin4sha/"/>
		<implements rdf:resource="pinboard.in/v1/openapi.yaml"/>
    <license rdf:resource="LICENSE"/>
    <maintainer rdf:resource="https://code.mro.name/mro"/>
    <name>pinboard4shaarli.cgi</name>
    <name>pin4sha.cgi</name>
    <programming-language>golang</programming-language>
    <programming-language>ocaml</programming-language>
    <repository>
      <GitRepository>
        <browse rdf:resource="https://code.mro.name/mro/pinboard4shaarli"/>
        <location rdf:resource="https://code.mro.name/mro/pinboard4shaarli.git"/>
        <browse rdf:resource="https://code.mro.name/mro/pin4sha"/>
        <location rdf:resource="https://code.mro.name/mro/pin4sha.git"/>
      </GitRepository>
    </repository>
    <short-description xml:lang="en">Make the essential subset for posting to a shaarli available via a subset of the pinboard/delicious API.</short-description>
    <short-description xml:lang="fr">Rendre le sous-ensemble essentiel pour l'affichage dans un shaarli disponible via un sous-ensemble de l'API pinboard/delicious mature et éprouvée.</short-description>
    <wiki rdf:resource="https://code.mro.name/mro/pinboard4shaarli/wiki"/>
    <wiki rdf:resource="https://code.mro.name/mro/pin4sha/wiki"/>
  </Project>
</rdf:RDF>

D patches/sebsauvage/Shaarli/archive/master/001.patch => patches/sebsauvage/Shaarli/archive/master/001.patch +0 -66
@@ 1,66 0,0 @@
diff --git a/index.php b/index.php
index c102e42..bafec87 100644
--- a/index.php
+++ b/index.php
@@ -43,7 +43,7 @@ define('WEB_PATH', substr($_SERVER["REQUEST_URI"], 0, 1+strrpos($_SERVER["REQUES
 // Force cookie path (but do not change lifetime)
 $cookie=session_get_cookie_params();
 $cookiedir = ''; if(dirname($_SERVER['SCRIPT_NAME'])!='/') $cookiedir=dirname($_SERVER["SCRIPT_NAME"]).'/';
-session_set_cookie_params($cookie['lifetime'],$cookiedir,$_SERVER['HTTP_HOST']); // Set default cookie expiration and path.
+session_set_cookie_params($cookie['lifetime'],$cookiedir,$_SERVER['SERVER_NAME']); // Set default cookie expiration and path.
 
 // Set session parameters on server side.
 define('INACTIVITY_TIMEOUT',3600); // (in seconds). If the user does not access any page within this time, his/her session is considered expired.
@@ -413,14 +413,14 @@ if (isset($_POST['login']))
             $_SESSION['expires_on']=time()+$_SESSION['longlastingsession'];  // Set session expiration on server-side.
 
             $cookiedir = ''; if(dirname($_SERVER['SCRIPT_NAME'])!='/') $cookiedir=dirname($_SERVER["SCRIPT_NAME"]).'/';
-            session_set_cookie_params($_SESSION['longlastingsession'],$cookiedir,$_SERVER['HTTP_HOST']); // Set session cookie expiration on client side
+            session_set_cookie_params($_SESSION['longlastingsession'],$cookiedir,$_SERVER['SERVER_NAME']); // Set session cookie expiration on client side
             // Note: Never forget the trailing slash on the cookie path !
             session_regenerate_id(true);  // Send cookie with new expiration date to browser.
         }
         else // Standard session expiration (=when browser closes)
         {
             $cookiedir = ''; if(dirname($_SERVER['SCRIPT_NAME'])!='/') $cookiedir=dirname($_SERVER["SCRIPT_NAME"]).'/';
-            session_set_cookie_params(0,$cookiedir,$_SERVER['HTTP_HOST']); // 0 means "When browser closes"
+            session_set_cookie_params(0,$cookiedir,$_SERVER['SERVER_NAME']); // 0 means "When browser closes"
             session_regenerate_id(true);
         }
         // Optional redirect after login:
@@ -452,7 +452,7 @@ function serverUrl()
 {
     $https = (!empty($_SERVER['HTTPS']) && (strtolower($_SERVER['HTTPS'])=='on')) || $_SERVER["SERVER_PORT"]=='443'; // HTTPS detection.
     $serverport = ($_SERVER["SERVER_PORT"]=='80' || ($https && $_SERVER["SERVER_PORT"]=='443') ? '' : ':'.$_SERVER["SERVER_PORT"]);
-    return 'http'.($https?'s':'').'://'.$_SERVER['HTTP_HOST'].$serverport;
+    return 'http'.($https?'s':'').'://'.$_SERVER['SERVER_NAME'].$serverport;
 }
 
 // Returns the absolute URL of current script, without the query.
@@ -1302,7 +1302,7 @@ function renderPage()
         if (is_numeric($_GET['linksperpage'])) { $_SESSION['LINKS_PER_PAGE']=abs(intval($_GET['linksperpage'])); }
         // Make sure the referer is from Shaarli itself.
         $referer = '?';
-        if (!empty($_SERVER['HTTP_REFERER']) && strcmp(parse_url($_SERVER['HTTP_REFERER'],PHP_URL_HOST),$_SERVER['HTTP_HOST'])==0)
+        if (!empty($_SERVER['HTTP_REFERER']) && strcmp(parse_url($_SERVER['HTTP_REFERER'],PHP_URL_HOST),$_SERVER['SERVER_NAME'])==0)
             $referer = $_SERVER['HTTP_REFERER'];
         header('Location: '.$referer);
         exit;
@@ -1321,7 +1321,7 @@ function renderPage()
         }
         // Make sure the referer is from Shaarli itself.
         $referer = '?';
-        if (!empty($_SERVER['HTTP_REFERER']) && strcmp(parse_url($_SERVER['HTTP_REFERER'],PHP_URL_HOST),$_SERVER['HTTP_HOST'])==0)
+        if (!empty($_SERVER['HTTP_REFERER']) && strcmp(parse_url($_SERVER['HTTP_REFERER'],PHP_URL_HOST),$_SERVER['SERVER_NAME'])==0)
             $referer = $_SERVER['HTTP_REFERER'];
         header('Location: '.$referer);
         exit;
@@ -2059,7 +2059,7 @@ function lazyThumbnail($url,$href=false)
 function install()
 {
     // On free.fr host, make sure the /sessions directory exists, otherwise login will not work.
-    if (endsWith($_SERVER['HTTP_HOST'],'.free.fr') && !is_dir($_SERVER['DOCUMENT_ROOT'].'/sessions')) mkdir($_SERVER['DOCUMENT_ROOT'].'/sessions',0705);
+    if (endsWith($_SERVER['SERVER_NAME'],'.free.fr') && !is_dir($_SERVER['DOCUMENT_ROOT'].'/sessions')) mkdir($_SERVER['DOCUMENT_ROOT'].'/sessions',0705);
 
 
     // This part makes sure sessions works correctly.

D scripts/assert.sh => scripts/assert.sh +0 -61
@@ 1,61 0,0 @@
#
#  Copyright (c) 2015 Marcus Rohrmoser http://mro.name/me. All rights reserved.
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU 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 General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

# insipired by https://github.com/lehmannro/assert.sh but much more primitive.

# terminal colors (require bash)
# http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x329.html
# http://wiki.bash-hackers.org/scripting/terminalcodes
FGC_NONE="\033[0m"
FGC_GRAY="\033[1;30m"
FGC_RED="\033[1;31m"
FGC_GREEN="\033[1;32m"
FGC_YELLOW="\033[1;33m"
FGC_BLUE="\033[1;34m"
FGC_PURPLE="\033[1;35m"
FGC_CYAN="\033[1;36m"
FGC_WHITE="\033[1;37m"
BGC_GRAY="\033[7;30m"
BGC_RED="\033[7;31m"
BGC_GREEN="\033[7;32m"
BGC_YELLOW="\033[7;33m"
BGC_BLUE="\033[7;34m"
BGC_PURPLE="\033[7;35m"
BGC_CYAN="\033[7;36m"
BGC_WHITE="\033[7;37m"

assert_fail() {
  local CODE="${1}" MESSAGE="${2}"
  echo -e "${BGC_RED}assert_fail (${CODE}): ${MESSAGE}${FGC_NONE}"
  exit $1
}

assert_equal() {
  local EXPECTED="${1}" ACTUAL="${2}" CODE="${3}" MESSAGE="${4}"
  if [ ! "${EXPECTED}" = "${ACTUAL}" ] ; then
    echo -e "${BGC_RED}assert_equal${FGC_NONE} (${CODE}): ${MESSAGE}  expected: \"${FGC_GREEN}${EXPECTED}${FGC_NONE}\" != actual: \"${FGC_RED}${ACTUAL}${FGC_NONE}\""
    exit $3
  fi
}

assert_fgrep() {
  local PATTERN="${1}" ACTUAL="${2}" CODE="${3}" MESSAGE="${4}"
  echo "${ACTUAL}" | fgrep "${PATTERN}" 1> /dev/null 2>&1 || { \
    echo -e "${BGC_RED}assert_equal${FGC_NONE} (${CODE}): ${MESSAGE}  pattern: \"${FGC_GREEN}${PATTERN}${FGC_NONE}\" != actual: \"${FGC_RED}${ACTUAL}${FGC_NONE}\"" \
    && exit $3 ; \
  }
}

D scripts/categories.rng => scripts/categories.rng +0 -139
@@ 1,139 0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
  https://tools.ietf.org/html/rfc5023#appendix-B
  not: https://web.archive.org/web/20130716150512/http://www.atomenabled.org:80/developers/protocol/atom-protocol-spec.php#rfc.section.8.3.3
-->
<!-- -*- rnc -*- # RELAX NG Compact Syntax Grammar for the Atom Protocol -->
<grammar xmlns:app="http://www.w3.org/2007/app" ns="http://www.w3.org/2005/Atom" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:atom="http://www.w3.org/2005/Atom" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
  <start>
    <ref name="appCategories"/>
  </start>
  <define name="atomCommonAttributes">
    <optional>
      <attribute name="xml:base">
        <ref name="atomURI"/>
      </attribute>
    </optional>
    <optional>
      <attribute name="xml:lang">
        <ref name="atomLanguageTag"/>
      </attribute>
    </optional>
    <zeroOrMore>
      <ref name="undefinedAttribute"/>
    </zeroOrMore>
  </define>
  <define name="undefinedAttribute">
    <attribute>
      <anyName>
        <except>
          <name>xml:base</name>
          <name>xml:lang</name>
          <nsName ns=""/>
        </except>
      </anyName>
    </attribute>
  </define>
  <define name="atomURI">
    <text/>
  </define>
  <define name="atomLanguageTag">
    <data type="string">
      <param name="pattern">([A-Za-z]{1,8}(-[A-Za-z0-9]{1,8})*)?</param>
    </data>
  </define>
  <define name="atomCategory">
    <element name="atom:category">
      <ref name="atomCommonAttributes"/>
      <attribute name="term"/>
      <optional>
        <attribute name="scheme">
          <ref name="atomURI"/>
        </attribute>
      </optional>
      <optional>
        <attribute name="label"/>
      </optional>
      <ref name="undefinedContent"/>
    </element>
  </define>
  <define name="appInlineCategories">
    <element name="app:categories">
      <optional>
        <attribute name="fixed">
          <choice>
            <value>yes</value>
            <value>no</value>
          </choice>
        </attribute>
      </optional>
      <optional>
        <attribute name="scheme">
          <ref name="atomURI"/>
        </attribute>
      </optional>
      <group>
        <zeroOrMore>
          <ref name="atomCategory"/>
        </zeroOrMore>
        <ref name="undefinedContent"/>
      </group>
    </element>
  </define>
  <define name="appOutOfLineCategories">
    <element name="app:categories">
      <attribute name="href">
        <ref name="atomURI"/>
      </attribute>
      <empty/>
    </element>
  </define>
  <define name="appCategories">
    <choice>
      <ref name="appInlineCategories"/>
      <ref name="appOutOfLineCategories"/>
    </choice>
  </define>
  <!-- Extensibility -->
  <define name="undefinedContent">
    <zeroOrMore>
      <choice>
        <text/>
        <ref name="anyForeignElement"/>
      </choice>
    </zeroOrMore>
  </define>
  <define name="anyElement">
    <element>
      <anyName/>
      <zeroOrMore>
        <choice>
          <attribute>
            <anyName/>
          </attribute>
          <text/>
          <ref name="anyElement"/>
        </choice>
      </zeroOrMore>
    </element>
  </define>
  <define name="anyForeignElement">
    <element>
      <anyName>
        <except>
          <nsName/>
        </except>
      </anyName>
      <zeroOrMore>
        <choice>
          <attribute>
            <anyName/>
          </attribute>
          <text/>
          <ref name="anyElement"/>
        </choice>
      </zeroOrMore>
    </element>
  </define>
</grammar>
<!-- EOF -->

D scripts/download.sh => scripts/download.sh +0 -24
@@ 1,24 0,0 @@
#!/bin/sh
#
#  Copyright (c) 2015 Marcus Rohrmoser http://mro.name/me. All rights reserved.
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU 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 General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
cd "$(dirname "$0")/.." || exit 1

[ "${GITHUB}" != "" ] || { echo 'I need ${GITHUB}, e.g. shaarli/Shaarli/archive/v0.0.40beta' && exit 2; }

# Download the tarball...
GITHUB_SRC_URL="https://github.com/${GITHUB}.tar.gz"
curl --location --output source.tar.gz --url "${GITHUB_SRC_URL}" || { echo "ouch" && exit 3; }

D scripts/form2post.rb => scripts/form2post.rb +0 -23
@@ 1,23 0,0 @@
#!/usr/bin/env ruby
#
#  Copyright (c) 2015 Marcus Rohrmoser http://mro.name/me. All rights reserved.
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU 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 General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
require 'rexml/document'
require 'cgi'

REXML::Document.new($stdin).elements.each('//textarea | //input[@type = "text" or @type = "hidden"]') do |element|
  puts "#{CGI.escape(element.attributes['name'] || '')}=#{CGI.escape(element.attributes['value'] || '')}&"
end

D scripts/run-tests.sh => scripts/run-tests.sh +0 -126
@@ 1,126 0,0 @@
#!/bin/sh
#
#  Copyright (c) 2015-2016 Marcus Rohrmoser http://mro.name/me. All rights reserved.
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU 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 General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

# Check preliminaries
curl --version >/dev/null || { echo "I need curl." && exit 101 ; }
xmllint --version 2> /dev/null || { echo "I need xmllint." && exit 102 ; }
ruby --version > /dev/null || { echo "I need ruby." && exit 103 ; }

[ "${GITHUB_SRC_SUBDIR}" != "" ] || { echo 'I need ${GITHUB_SRC_SUBDIR}, e.g. Shaarli-*' && exit 2; }
[ "${BASE_URL}" != "" ] || { echo 'I need ${BASE_URL}, e.g. http://127.0.0.1:8000' && exit 2; }
[ "${USERNAME}" != "" ] || { echo 'I need ${USERNAME}, e.g. tast' && exit 2; }
[ "${PASSWORD}" != "" ] || { echo 'I need ${PASSWORD}, e.g. tust' && exit 2; }

cd "$(dirname "$0")/.." || exit 1
CWD="$(pwd)"
WORK_DIR="${CWD}/tmp"

# terminal colors (require bash)
# http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x329.html
# http://wiki.bash-hackers.org/scripting/terminalcodes
FGC_NONE="\033[0m"
FGC_GRAY="\033[1;30m"
FGC_RED="\033[1;31m"
FGC_GREEN="\033[1;32m"
FGC_YELLOW="\033[1;33m"
FGC_BLUE="\033[1;34m"
FGC_PURPLE="\033[1;35m"
FGC_CYAN="\033[1;36m"
FGC_WHITE="\033[1;37m"
BGC_GRAY="\033[7;30m"
BGC_RED="\033[7;31m"
BGC_GREEN="\033[7;32m"
BGC_YELLOW="\033[7;33m"
BGC_BLUE="\033[7;34m"
BGC_PURPLE="\033[7;35m"
BGC_CYAN="\033[7;36m"
BGC_WHITE="\033[7;37m"

echo "\$ curl --version" ; curl --version

status_code=0
test_counter=1
echo "1..$(ls "${CWD}/tests"/test-*.sh | wc -l)"
for tst in "${CWD}/tests"/test-*.sh
do
  test_name="$(basename "${tst}")"
  echo -n "travis_fold:start:${test_name}\r"
  echo -n "# run ${test_counter} - ${test_name} "

  # prepare a clean test environment from scratch
  cd "${CWD}"
  rm -rf "${WORK_DIR}" && mkdir "${WORK_DIR}"
  cd "${WORK_DIR}"

  # ...and unpack into directory 'WebAppRoot'...
  tar -xzf "${CWD}/source.tar.gz" || { echo "ouch" && exit 1 ; }
  mv ${GITHUB_SRC_SUBDIR} "WebAppRoot"
  cp "${CWD}/pinboard.cgi" "WebAppRoot/"

  for patchfile in "${CWD}/patches/${GITHUB}"/*.patch
  do
    [ -r "${patchfile}" ] && patch -p1 -d "WebAppRoot" < "${patchfile}"
  done

  # https://github.com/shaarli/Shaarli/issues/613
  cd "WebAppRoot" && composer update --no-dev ; cd "${WORK_DIR}"

  # http://robbiemackay.com/2013/05/03/automating-behat-and-mink-tests-with-travis-ci/
  # webserver setup
  php -S 127.0.0.1:8000 -t "WebAppRoot" 1> php.stdout 2> php.stderr &
  sleep 1 # how could we get rid of this stupid sleep?

  ls -l "WebAppRoot/index.php" >/dev/null || { echo "ouch" && exit 2 ; }

  curl --silent --show-error \
    --url "${BASE_URL}" \
    --data-urlencode "setlogin=${USERNAME}" \
    --data-urlencode "setpassword=${PASSWORD}" \
    --data-urlencode "continent=Europe" \
    --data-urlencode "city=Brussels" \
    --data-urlencode "title=Review Shaarli" \
    --data-urlencode "Save=Save config" \
    --output /dev/null

  # execute each test
  /usr/bin/env bash "${tst}"
  code=$?

  killall php 1>/dev/null 2>&1
  wait
  cd "${WORK_DIR}"

  if [ ${code} -ne 0 ] ; then
    for f in curl.* WebAppRoot/data/log.txt WebAppRoot/data/ipbans.php WebAppRoot/data/config.php ; do
      printf " _\$_cat%-50s\n" "_${f}_" | tr ' _' '# '
      cat "${f}"
    done
    echo " "
  fi
  echo -n "travis_fold:end:${test_name}\r"

  if [ ${code} -eq 0 ] ; then
    echo "${FGC_GREEN}ok ${test_counter}${FGC_NONE} - ${test_name}"
  else
    echo "${FGC_RED}not ok ${test_counter}${FGC_NONE} - ${test_name} (code: ${code})"
    status_code=1
  fi
  test_counter=$((test_counter+1))
done

exit ${status_code}

D scripts/tagcloud-html2atom.xslt => scripts/tagcloud-html2atom.xslt +0 -47
@@ 1,47 0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--

 $ curl "https://links.mro.name/?do=tagcloud" | xsltproc - -html tagcloud-html2atom.xslt - | xmllint - -relaxng categories.rng -

 http://www.w3.org/TR/xslt/
 http://www.w3.org/TR/xpath/
 https://tools.ietf.org/html/rfc5023#appendix-B
-->
<xsl:stylesheet
  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  xmlns:dctype="http://purl.org/dc/dcmitype/"
  xmlns="http://www.w3.org/2005/Atom"
  xmlns:app="http://www.w3.org/2007/app"
  xmlns:foo="foo"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  exclude-result-prefixes="dctype rdf"
  version="1.0">

  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="/html">
    <xsl:comment>
  https://tools.ietf.org/html/rfc5023#appendix-B
  http://atomenabled.org/developers/protocol/#appCategories1
  https://web.archive.org/web/20130716150512/http://www.atomenabled.org:80/developers/protocol/atom-protocol-spec.php#rfc.section.8.3.3
  https://web.archive.org/web/20130716150512/http://www.atomenabled.org:80/developers/protocol/atom-protocol-spec.php#schema
</xsl:comment>
    <app:categories scheme="?searchtags=">
      <xsl:variable name="traditional_count_prefix" select="1 = count(.//a[starts-with(@href, '?searchtags=')][1]/preceding-sibling::*[1])"/>

      <xsl:for-each select=".//a[starts-with(@href, '?searchtags=')]">
        <xsl:sort select="." data-type="text" order="ascending"/>

        <xsl:variable name="count">
          <xsl:choose>
            <xsl:when test="$traditional_count_prefix"><xsl:value-of select="preceding-sibling::*[1]"/></xsl:when>
            <xsl:otherwise><xsl:value-of select="following-sibling::*[1]"/></xsl:otherwise>
          </xsl:choose>
        </xsl:variable>

        <category term="{.}" foo:count="{$count}"/>
      </xsl:for-each>
    </app:categories>
  </xsl:template>

</xsl:stylesheet>

D tests/test-atom-empty.sh => tests/test-atom-empty.sh +0 -28
@@ 1,28 0,0 @@
#!/bin/sh
#
#  Copyright (c) 2015-2016 Marcus Rohrmoser http://mro.name/me. All rights reserved.
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU 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 General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
cd "$(dirname "$0")/../tmp"
. ../scripts/assert.sh

# Check preliminaries
curl --version >/dev/null       || assert_fail 101 "I need curl."
xmllint --version 2> /dev/null  || assert_fail 102 "I need xmllint (libxml2)."
[ "${BASE_URL}" != "" ]         || assert_fail 1 "How strange, BASE_URL is unset."

curl --silent --show-error --output "curl.tmp.atom" "${BASE_URL}/?do=atom"
entries=$(xmllint --xpath 'count(/*/*[local-name()="entry"])' "curl.tmp.atom")
assert_equal 1 "${entries}" 28 "expected exactly one <entry>"

D tests/test-delete-all-ok.sh.xslt => tests/test-delete-all-ok.sh.xslt +0 -1
@@ 1,1 0,0 @@
test-delete-ok.sh.xslt
\ No newline at end of file

D tests/test-delete-ok.sh.xslt => tests/test-delete-ok.sh.xslt +0 -47
@@ 1,47 0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--

 Copyright (c) 2015-2016 Marcus Rohrmoser http://mro.name/me. All rights reserved.

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU 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 General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.


 Find all 'delete_link' POST forms and list lf_linkdate and token.
 
 $ xsltproc - -html ../tests/test-delete-ok.sh.xslt curl.tmp.html

 http://www.w3.org/TR/xslt
-->
<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    exclude-result-prefixes="xsl"
    version="1.0">
  <xsl:output method="text"/>

  <xsl:template match="/">
    <xsl:for-each select="html/body//form[.//input/@name='delete_link' and .//input/@name='lf_linkdate' and .//input/@name='token']">
      <xsl:variable name="lf_linkdate" select=".//input[@name='lf_linkdate']/@value"/>
      <xsl:variable name="token" select=".//input[@name='token']/@value"/>
      <xsl:value-of select="$lf_linkdate"/><xsl:text> </xsl:text><xsl:value-of select="$token"/><xsl:text>
</xsl:text>
    </xsl:for-each>
    <xsl:for-each select="html/body//a[contains(@href, '?delete_link=') and contains(@href, '&amp;token=')]">
      <xsl:variable name="lf_linkdate" select="substring-before(substring-after(@href,'?delete_link='), '&amp;token=')"/>
      <xsl:variable name="token" select="substring-after(@href,'&amp;token=')"/>
      <xsl:value-of select="$lf_linkdate"/><xsl:text> </xsl:text><xsl:value-of select="$token"/><xsl:text>
</xsl:text>
    </xsl:for-each>
  </xsl:template>

</xsl:stylesheet>

D tests/test-index.sh => tests/test-index.sh +0 -28
@@ 1,28 0,0 @@
#!/bin/sh
#
#  Copyright (c) 2015-2016 Marcus Rohrmoser http://mro.name/me. All rights reserved.
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU 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 General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
cd "$(dirname "$0")/../tmp"
. ../scripts/assert.sh

# Check preliminaries
curl --version >/dev/null       || assert_fail 101 "I need curl."
xmllint --version 2> /dev/null  || assert_fail 102 "I need xmllint (libxml2)."
[ "${BASE_URL}" != "" ]         || assert_fail 1 "How strange, BASE_URL is unset."

curl --silent "${BASE_URL}/" | xmllint --html --encode utf8 --format - 2>/dev/null >/dev/null

exit $?

D tests/test-login-ok.sh => tests/test-login-ok.sh +0 -90
@@ 1,90 0,0 @@
#!/bin/sh
#
#  Copyright (c) 2015-2016 Marcus Rohrmoser http://mro.name/me. All rights reserved.
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU 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 General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
cd "$(dirname "$0")/../tmp"
. ../scripts/assert.sh

# Check preliminaries
curl --version >/dev/null       || assert_fail 101 "I need curl."
xmllint --version 2> /dev/null  || assert_fail 102 "I need xmllint (libxml2)."
[ "${USERNAME}" != "" ]         || assert_fail 1 "How strange, USERNAME is unset."
[ "${PASSWORD}" != "" ]         || assert_fail 2 "How strange, PASSWORD is unset."
[ "${BASE_URL}" != "" ]         || assert_fail 3 "How strange, BASE_URL is unset."

echo "###################################################"
echo "## non-logged-in GET /?post return: 302 "
http_code=$(curl --url "${BASE_URL}/?post" \
  --cookie curl.cook --cookie-jar curl.cook \
  --output curl.tmp.html \
  --trace-ascii curl.tmp.trace --dump-header curl.tmp.head \
  --write-out '%{http_code}' 2>/dev/null)
assert_equal 302 "${http_code}" 35 "login check."

echo "####################################################"
echo "## Step 1: fetch token to login "
echo "GET ${BASE_URL}?do=login"
rm curl.tmp.*
# http://unix.stackexchange.com/a/157219
LOCATION=$(curl --get --url "${BASE_URL}/?do=login" \
  --cookie curl.cook --cookie-jar curl.cook \
  --location --output curl.tmp.html \
  --trace-ascii curl.tmp.trace --dump-header curl.tmp.head \
  --write-out '%{url_effective}' 2>/dev/null)
# todo:
errmsg=$(xmllint --html --nowarning --xpath 'string(/html[1 = count(*)]/head[1 = count(*)]/script[starts-with(.,"alert(")])' curl.tmp.html)
assert_equal "" "${errmsg}" 58 "error: '${errmsg}'"
TOKEN=$(xmllint --html --nowarning --xpath 'string(/html/body//form[@name="loginform"]//input[@name="token"]/@value)' curl.tmp.html)
# string(..) http://stackoverflow.com/a/18390404

# the precise length doesn't matter, it just has to be significantly larger than ''
assert_equal 40 $(printf "%s" ${TOKEN} | wc -c) 63 "found TOKEN=${TOKEN}"

echo "######################################################"
echo "## Step 2: follow the redirect, do the login and redirect to ?do=changepasswd "
echo "POST ${LOCATION}"
rm curl.tmp.*
LOCATION=$(curl --url "${LOCATION}" \
  --data-urlencode "login=${USERNAME}" \
  --data-urlencode "password=${PASSWORD}" \
  --data-urlencode "token=${TOKEN}" \
  --data-urlencode "returnurl=${BASE_URL}/?do=changepasswd" \
  --cookie curl.cook --cookie-jar curl.cook \
  --location --output curl.tmp.html \
  --trace-ascii curl.tmp.trace --dump-header curl.tmp.head \
  --write-out '%{url_effective}' 2>/dev/null)
# todo:
errmsg=$(xmllint --html --nowarning --xpath 'string(/html[1 = count(*)]/head[1 = count(*)]/script[starts-with(.,"alert(")])' curl.tmp.html)
assert_equal "" "${errmsg}" 80 "error during login"
assert_equal "${BASE_URL}/?do=changepasswd" "${LOCATION}" 81 "redirect after login"

# [ 1 -eq $(xmllint --html --nowarning --xpath "count(/html/body//a[@href = '?do=logout'])" curl.tmp.html 2>/dev/null) ] || assert_fail 13 "I expected a logout link."

# check presence of various mandatory form fields:
for field in oldpassword setpassword token
do
  assert_equal 1 $(xmllint --html --nowarning --xpath "count(/html/body//form[@name = 'changepasswordform']//input[@name='${field}'])" curl.tmp.html) 88 "expected to have a '${field}'"
done


echo "###################################################"
echo "## logged-in GET /?post return: 200 "
http_code=$(curl --url "${BASE_URL}/?post" \
  --cookie curl.cook --cookie-jar curl.cook \
  --output curl.tmp.html \
  --trace-ascii curl.tmp.trace --dump-header curl.tmp.head \
  --write-out '%{http_code}' 2>/dev/null)
assert_equal 200 "${http_code}" 90 "login check."

D tests/test-pinboard-info.sh => tests/test-pinboard-info.sh +0 -26
@@ 1,26 0,0 @@
#!/bin/sh
#
#  Copyright (c) 2019-2019 Marcus Rohrmoser https://code.mro.name/mro/Shaarli-API-test. All rights reserved.
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU 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 General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
cd "$(dirname "$0")/../tmp"
. ../scripts/assert.sh

# Check preliminaries
curl --version >/dev/null       || assert_fail 101 "I need curl."
xmllint --version 2> /dev/null  || assert_fail 102 "I need xmllint (libxml2)."
[ "${BASE_URL}" != "" ]         || assert_fail 1 "How strange, BASE_URL is unset."

curl --url "${BASE_URL}/pinboard.cgi/v1/info" 2>/dev/null | file -

D tests/test-post.sh => tests/test-post.sh +0 -119
@@ 1,119 0,0 @@
#!/bin/sh
#
#  Copyright (c) 2015-2016 Marcus Rohrmoser http://mro.name/me. All rights reserved.
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU 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 General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
cd "$(dirname "$0")/../tmp"
. ../scripts/assert.sh

# Check preliminaries
curl --version >/dev/null       || assert_fail 101 "I need curl."
xmllint --version 2> /dev/null  || assert_fail 102 "I need xmllint (libxml2)."
ruby --version > /dev/null      || assert_fail 103 "I need ruby."
[ "${USERNAME}" != "" ]         || assert_fail 1 "How strange, USERNAME is unset."
[ "${PASSWORD}" != "" ]         || assert_fail 2 "How strange, PASSWORD is unset."
[ "${BASE_URL}" != "" ]         || assert_fail 3 "How strange, BASE_URL is unset."
assert_equal "" "$(echo "${BASE_URL}" | egrep -e "/$")" 28 "BASE_URL must be without trailing /"

echo "###################################################"
echo "## Non-logged-in Atom feed before adding a link (should have only the initial public default entry):"
curl --silent --show-error --output curl.tmp.atom "${BASE_URL}/?do=atom"
xmllint --encode utf8 --format curl.tmp.atom
entries=$(xmllint --xpath 'count(/*/*[local-name()="entry"])' curl.tmp.atom)
assert_equal 1 "${entries}" 35 "Atom feed expected 1 = ${entries}"

echo "####################################################"
echo "## Step 1: fetch token to login and add a new link: "
echo "GET ${BASE_URL}?post=..."
rm curl.tmp.*
# http://unix.stackexchange.com/a/157219
LOCATION=$(curl --get --url "${BASE_URL}" \
  --data-urlencode "post=https://github.com/sebsauvage/Shaarli/commit/450342737ced8ef2864b4f83a4107a7fafcc4add" \
  --data-urlencode "title=Initial Commit to Shaarli on Github." \
  --data-urlencode "source=Source Text" \
  --cookie curl.cook --cookie-jar curl.cook \
  --location --output curl.tmp.html \
  --trace-ascii curl.tmp.trace --dump-header curl.tmp.head \
  --write-out '%{url_effective}' 2>/dev/null)
# todo:
errmsg=$(xmllint --html --nowarning --xpath 'string(/html[1 = count(*)]/head[1 = count(*)]/script[starts-with(.,"alert(")])' curl.tmp.html)
assert_equal "" "${errmsg}" 52 "error: '${errmsg}'"
TOKEN=$(xmllint --html --nowarning --xpath 'string(/html/body//form[@name="loginform"]//input[@name="token"]/@value)' curl.tmp.html)
# string(..) http://stackoverflow.com/a/18390404

# the precise length doesn't matter, it just has to be significantly larger than ''
assert_equal 40 $(printf "%s" ${TOKEN} | wc -c) 57 "expected TOKEN of 40 characters, but found ${TOKEN} of $(printf "%s" ${TOKEN} | wc -c)"

echo "######################################################"
echo "## Step 2: follow the redirect, do the login and get the post form: "
echo "POST ${LOCATION}"
rm curl.tmp.*
LOCATION=$(curl --url "${LOCATION}" \
  --data-urlencode "login=${USERNAME}" \
  --data-urlencode "password=${PASSWORD}" \
  --data-urlencode "token=${TOKEN}" \
  --cookie curl.cook --cookie-jar curl.cook \
  --location --output curl.tmp.html \
  --trace-ascii curl.tmp.trace --dump-header curl.tmp.head \
  --write-out '%{url_effective}' 2>/dev/null)
# todo:
errmsg=$(xmllint --html --nowarning --xpath 'string(/html[1 = count(*)]/head[1 = count(*)]/script[starts-with(.,"alert(")])' curl.tmp.html)
assert_equal "" "${errmsg}" 73 "error: '${errmsg}'"
# check presence of various mandatory form fields:
for field in lf_url lf_title lf_linkdate lf_tags token
do
  assert_equal 1 $(xmllint --html --nowarning --xpath "count(/html/body//form[@name = 'linkform']//input[@name='${field}'])" curl.tmp.html) 77 "expected to have a '${field}'"
done
for field in lf_description
do
  assert_equal 1 $(xmllint --html --nowarning --xpath "count(/html/body//form[@name = 'linkform']//textarea[@name='${field}'])" curl.tmp.html) 81 "expected to have a '${field}'"
done

# turn form field data into curl post data file
xmllint --html --nowarning --xmlout curl.tmp.html | xmllint --xpath '/html/body//form[@name="linkform"]' - | /usr/bin/env ruby ../scripts/form2post.rb > curl.post

echo "######################################################"
echo "## Step 3: finally post the link: "
echo "POST ${LOCATION}"
rm curl.tmp.*
LOCATION=$(curl --url "${LOCATION}" \
  --data "@curl.post" \
  --data-urlencode "lf_linkdate=20130226_100941" \
  --data-urlencode "lf_source=$0" \
  --data-urlencode "lf_description=Must be older because http://sebsauvage.github.io/Shaarli/ mentions 'Copyright (c) 2011 Sébastien SAUVAGE (sebsauvage.net)'." \
  --data-urlencode "lf_tags=t1 t2" \
  --data-urlencode "save_edit=Save" \
  --cookie curl.cook --cookie-jar curl.cook \
  --output curl.tmp.html \
  --trace-ascii curl.tmp.trace --dump-header curl.tmp.head \
  --write-out '%{redirect_url}' 2>/dev/null)
# don't use --location and url_effective because this strips /?#... on curl 7.30.0 (x86_64-apple-darwin13.0)
echo "final ${LOCATION}"
# todo:
errmsg=$(xmllint --html --nowarning --xpath 'string(/html[1 = count(*)]/head[1 = count(*)]/script[starts-with(.,"alert(")])' curl.tmp.html 2>/dev/null)
assert_equal "" "${errmsg}" 106 "error: '${errmsg}'"
echo "${LOCATION}" | egrep -e "^${BASE_URL}/\?#[a-zA-Z0-9@_-]{6}\$" || assert_fail 108 "expected link hash url, but got '${LOCATION}'"
# don't follow the redirect => no html => no logout link [ 1 -eq "$(xmllint --html --nowarning --xpath "count(/html/body//a[@href = '?do=logout'])" curl.tmp.html 2>/dev/null)" ] || assert_fail 13 "I expected a logout link."

#####################################################
# TODO: watch out for error messages like e.g. ip bans or the like.

# check post-condition - there must be more entries now:
echo "###################################################"
echo "## Non-logged-in Atom feed after adding a link (should have the added + the initial public default entry):"
curl --silent --show-error --output curl.tmp.atom "${BASE_URL}/?do=atom"
xmllint --encode utf8 --format curl.tmp.atom
entries=$(xmllint --xpath 'count(/*/*[local-name()="entry"])' curl.tmp.atom)
assert_equal 2 "${entries}" 119 "Atom feed expected 2 = ${entries}"

D tests/test-tagcloud.sh => tests/test-tagcloud.sh +0 -29
@@ 1,29 0,0 @@
#!/bin/sh
#
#  Copyright (c) 2015-2016 Marcus Rohrmoser http://mro.name/me. All rights reserved.
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU 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 General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
cd "$(dirname "$0")/../tmp"
. ../scripts/assert.sh

# Check preliminaries
curl --version >/dev/null       || assert_fail 101 "I need curl."
xmllint --version 2> /dev/null  || assert_fail 102 "I need xmllint (libxml2)."
[ "${BASE_URL}" != "" ]         || assert_fail 1 "How strange, BASE_URL is unset."

entries=$(curl --silent "${BASE_URL}/?do=tagcloud" | xsltproc --html ../scripts/tagcloud-html2atom.xslt - | xmllint --relaxng ../scripts/categories.rng - | xmllint --xpath 'count(/*/*[local-name()="category"])' -)
assert_equal "2" "${entries}" 27 "Categories"

exit $?

D tests/test-title.sh => tests/test-title.sh +0 -35
@@ 1,35 0,0 @@
#!/bin/sh
#
#  Copyright (c) 2015-2016 Marcus Rohrmoser http://mro.name/me. All rights reserved.
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU 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 General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
cd "$(dirname "$0")/../tmp"
. ../scripts/assert.sh

# Check preliminaries
curl --version >/dev/null       || assert_fail 101 "I need curl."
xmllint --version 2> /dev/null  || assert_fail 102 "I need xmllint (libxml2)."
[ "${BASE_URL}" != "" ]         || assert_fail 1 "How strange, BASE_URL is unset."

curl --url "${BASE_URL}" \
  --cookie curl.cook --cookie-jar curl.cook \
  --location --output curl.html \
  --trace-ascii curl.trace --dump-header curl.head \
  2>/dev/null

title="$(xmllint --html --xpath 'normalize-space(string(/html/body//*[@id="shaarli_title"]))' curl.html 2>/dev/null)"
# ignore @id, use a/@href
title="$(xmllint --html --xpath 'normalize-space(string(/html/body//a[@href="?"]))' curl.html 2>/dev/null)"
assert_equal "Review Shaarli" "${title}" 33 "expected 'Review Shaarli' found '${title}'"

D tests/test_delete-all-ok.sh => tests/test_delete-all-ok.sh +0 -110
@@ 1,110 0,0 @@
#!/bin/sh
#
#  Copyright (c) 2015-2016 Marcus Rohrmoser http://mro.name/me. All rights reserved.
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU 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 General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
cd "$(dirname "$0")/../tmp"
. ../scripts/assert.sh

# Check preliminaries
curl --version >/dev/null       || assert_fail 101 "I need curl."
xmllint --version 2> /dev/null  || assert_fail 102 "I need xmllint (libxml2)."
xsltproc --version 1>/dev/null  || assert_fail 102 "I need xsltproc (libxml2)."
[ "${USERNAME}" != "" ]         || assert_fail 1 "How strange, USERNAME is unset."
[ "${PASSWORD}" != "" ]         || assert_fail 2 "How strange, PASSWORD is unset."
[ "${BASE_URL}" != "" ]         || assert_fail 3 "How strange, BASE_URL is unset."
[ -r "$0".xslt ]                || assert_fail 4 "How strange, helper xslt script '$0.xslt' not readable."

echo "####################################################"
echo "## Step 1: fetch token to login "
echo "GET ${BASE_URL}?do=login"
rm curl.tmp.*
# http://unix.stackexchange.com/a/157219
LOCATION=$(curl --get --url "${BASE_URL}/?do=login" \
  --cookie curl.cook --cookie-jar curl.cook \
  --location --output curl.tmp.html \
  --trace-ascii curl.tmp.trace --dump-header curl.tmp.head \
  --write-out '%{url_effective}' 2>/dev/null)
# todo:
errmsg=$(xmllint --html --nowarning --xpath 'string(/html[1 = count(*)]/head[1 = count(*)]/script[starts-with(.,"alert(")])' curl.tmp.html)
assert_equal "" "${errmsg}" 42 "fetch token error"
TOKEN=$(xmllint --html --nowarning --xpath 'string(/html/body//form[@name="loginform"]//input[@name="token"]/@value)' curl.tmp.html)
# string(..) http://stackoverflow.com/a/18390404

# the precise length doesn't matter, it just has to be significantly larger than ''
assert_equal 40 $(printf "%s" ${TOKEN} | wc -c) 47 "expected TOKEN of 40 characters, but found ${TOKEN} of $(printf "%s" ${TOKEN} | wc -c)"

echo "######################################################"
echo "## Step 2: follow the redirect, do the login and redirect to ${BASE_URL}/? "
echo "POST ${LOCATION}"
rm curl.tmp.*
LOCATION=$(curl --url "${LOCATION}" \
  --data-urlencode "login=${USERNAME}" \
  --data-urlencode "password=${PASSWORD}" \
  --data-urlencode "token=${TOKEN}" \
  --data-urlencode "returnurl=${BASE_URL}/?" \
  --cookie curl.cook --cookie-jar curl.cook \
  --location --output curl.tmp.html \
  --trace-ascii curl.tmp.trace --dump-header curl.tmp.head \
  --write-out '%{url_effective}' 2>/dev/null)
errmsg=$(xmllint --html --nowarning --xpath 'string(/html[1 = count(*)]/head[1 = count(*)]/script[starts-with(.,"alert(")])' curl.tmp.html)
assert_equal "" "${errmsg}" 64 "do login error"
assert_equal "${BASE_URL}/?" "${LOCATION}" 65 "redirect to BASE_URL"

# check pre-condition
echo "###################################################"
echo "## Logged-in Atom feed prior doing anything (should have 2 entries)"
curl --url "${BASE_URL}/?do=atom" \
  --silent --show-error \
  --cookie curl.cook --cookie-jar curl.cook \
  --output curl.tmp.atom
entries=$(xmllint --xpath 'count(/*/*[local-name()="entry"])' curl.tmp.atom)
assert_equal "2" "${entries}" 74 "Atom feed entries"

# now figure out the precise lf_linkdate and token for each entry to delete
while true
do
  # re-extract the token from the most recent HTTP response as it's consumed after each
  # HTTP request. So a simple for loop doesn't do the trick.

  line="$(xsltproc --html --nonet "${0}".xslt curl.tmp.html 2>/dev/null | head -n 1)"
  [ "" = "$line" ] && break

  echo "${line}" | while read lf_linkdate token
  do
    echo "lf_linkdate=${lf_linkdate} token=${token}"
    http_code=$(curl --url "${BASE_URL}/" \
      --data-urlencode "lf_linkdate=${lf_linkdate}" \
      --data-urlencode "token=${token}" \
      --data-urlencode "delete_link=" \
      --cookie curl.cook --cookie-jar curl.cook \
      --location --output curl.tmp.html \
      --trace-ascii curl.tmp.trace --dump-header curl.tmp.head \
      --write-out '%{http_code}' 2>/dev/null)
    assert_equal "200" "${http_code}" 92 "POST lf_linkdate=${lf_linkdate}&token=..."
    break # process only one line at a time.
  done
done


# check post-condition
echo "###################################################"
echo "## Logged-in Atom feed after deleting all entries"
curl --url "${BASE_URL}/?do=atom" \
  --silent --show-error \
  --cookie curl.cook --cookie-jar curl.cook \
  --output curl.tmp.atom
entries=$(xmllint --xpath 'count(/*/*[local-name()="entry"])' curl.tmp.atom)
assert_equal "0" "${entries}" 103 "Atom feed entries"

D tests/test_delete-ok.sh => tests/test_delete-ok.sh +0 -104
@@ 1,104 0,0 @@
#!/bin/sh
#
#  Copyright (c) 2015-2016 Marcus Rohrmoser http://mro.name/me. All rights reserved.
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU 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 General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
cd "$(dirname "$0")/../tmp"
. ../scripts/assert.sh

# Check preliminaries
curl --version >/dev/null       || assert_fail 101 "I need curl."
xmllint --version 2> /dev/null  || assert_fail 102 "I need xmllint (libxml2)."
xsltproc --version 1>/dev/null  || assert_fail 102 "I need xsltproc (libxml2)."
[ "${USERNAME}" != "" ]         || assert_fail 1 "How strange, USERNAME is unset."
[ "${PASSWORD}" != "" ]         || assert_fail 2 "How strange, PASSWORD is unset."
[ "${BASE_URL}" != "" ]         || assert_fail 3 "How strange, BASE_URL is unset."
[ -r "$0".xslt ]                || assert_fail 4 "How strange, helper xslt script '$0.xslt' not readable."

echo "####################################################"
echo "## Step 1: fetch token to login "
echo "GET ${BASE_URL}?do=login"
rm curl.tmp.*
# http://unix.stackexchange.com/a/157219
LOCATION=$(curl --get --url "${BASE_URL}/?do=login" \
  --cookie curl.cook --cookie-jar curl.cook \
  --location --output curl.tmp.html \
  --trace-ascii curl.tmp.trace --dump-header curl.tmp.head \
  --write-out '%{url_effective}' 2>/dev/null)
# todo:
errmsg=$(xmllint --html --nowarning --xpath 'string(/html[1 = count(*)]/head[1 = count(*)]/script[starts-with(.,"alert(")])' curl.tmp.html)
assert_equal "" "${errmsg}" 42 "fetch token error"
TOKEN=$(xmllint --html --nowarning --xpath 'string(/html/body//form[@name="loginform"]//input[@name="token"]/@value)' curl.tmp.html)
# string(..) http://stackoverflow.com/a/18390404

# the precise length doesn't matter, it just has to be significantly larger than ''
assert_equal 40 $(printf "%s" ${TOKEN} | wc -c) 47 "expected TOKEN of 40 characters, but found ${TOKEN} of $(printf "%s" ${TOKEN} | wc -c)"

echo "######################################################"
echo "## Step 2: follow the redirect, do the login and redirect to ${BASE_URL}/? "
echo "POST ${LOCATION}"
rm curl.tmp.*
LOCATION=$(curl --url "${LOCATION}" \
  --data-urlencode "login=${USERNAME}" \
  --data-urlencode "password=${PASSWORD}" \
  --data-urlencode "token=${TOKEN}" \
  --data-urlencode "returnurl=${BASE_URL}/?" \
  --cookie curl.cook --cookie-jar curl.cook \
  --location --output curl.tmp.html \
  --trace-ascii curl.tmp.trace --dump-header curl.tmp.head \
  --write-out '%{url_effective}' 2>/dev/null)
errmsg=$(xmllint --html --nowarning --xpath 'string(/html[1 = count(*)]/head[1 = count(*)]/script[starts-with(.,"alert(")])' curl.tmp.html)
assert_equal "" "${errmsg}" 64 "do login error"
assert_equal "${BASE_URL}/?" "${LOCATION}" 65 "redirect to BASE_URL"

# check pre-condition
echo "###################################################"
echo "## Logged-in Atom feed prior doing anything (should have 2 entries)"
curl --url "${BASE_URL}/?do=atom" \
  --silent --show-error \
  --cookie curl.cook --cookie-jar curl.cook \
  --output curl.tmp.atom
entries=$(xmllint --xpath 'count(/*/*[local-name()="entry"])' curl.tmp.atom)
assert_equal "2" "${entries}" 74 "Atom feed entries"


# That's maybe a quirk here – deleting consumes the token, but subsequent
# deletes fail silently. So the loop below deletes only the first entry.

# now figure out the precise lf_linkdate and token for each entry to delete
xsltproc --html --nonet "$0".xslt curl.tmp.html 2>/dev/null | while read lf_linkdate token
do
  echo "lf_linkdate=${lf_linkdate}  token=${token}"
  http_code=$(curl --url "${BASE_URL}/" \
    --data-urlencode "lf_linkdate=${lf_linkdate}" \
    --data-urlencode "token=${token}" \
    --data-urlencode "delete_link=" \
    --cookie curl.cook --cookie-jar curl.cook \
    --location --output curl.tmp.b.html \
    --trace-ascii curl.tmp.b.trace --dump-header curl.tmp.b.head \
    --write-out '%{http_code}' 2>/dev/null)
  assert_equal "200" "${http_code}" 92 "POST lf_linkdate=${lf_linkdate}&token=..."
done

# check post-condition
echo "###################################################"
echo "## Logged-in Atom feed after deleting one entry"
curl --url "${BASE_URL}/?do=atom" \
  --silent --show-error \
  --cookie curl.cook --cookie-jar curl.cook \
  --output curl.tmp.atom
entries=$(xmllint --xpath 'count(/*/*[local-name()="entry"])' curl.tmp.atom)
assert_equal "1" "${entries}" 103 "Atom feed entries"
assert_equal "My secret stuff... - Pastebin.com" "$(xmllint --xpath 'string(/*/*[local-name()="entry"]/*[local-name()="title"])' curl.tmp.atom)" 104 "remaining Atom feed entry title"

D tests/test_login-fail.sh => tests/test_login-fail.sh +0 -134
@@ 1,134 0,0 @@
#!/bin/sh
#
#  Copyright (c) 2015-2016 Marcus Rohrmoser http://mro.name/me. All rights reserved.
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU 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 General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
cd "$(dirname "$0")/../tmp"
. ../scripts/assert.sh

# Check preliminaries
curl --version >/dev/null       || assert_fail 101 "I need curl."
xmllint --version 2> /dev/null  || assert_fail 102 "I need xmllint (libxml2)."
[ "${USERNAME}" != "" ]         || assert_fail 1 "How strange, USERNAME is unset."
[ "${PASSWORD}" != "" ]         || assert_fail 2 "How strange, PASSWORD is unset."
[ "${BASE_URL}" != "" ]         || assert_fail 3 "How strange, BASE_URL is unset."

fetch_token() {
  echo "GET $1" 1>&2
  # http://unix.stackexchange.com/a/157219
  LOCATION=$(curl --get --url "$1" \
    --cookie curl.cook --cookie-jar curl.cook \
    --location --output curl.tmp.html \
    --trace-ascii curl.tmp.trace --dump-header curl.tmp.head \
    --write-out '%{url_effective}' 2>/dev/null)
  # todo:
  errmsg=$(xmllint --html --nowarning --xpath 'string(/html[1 = count(*)]/head[1 = count(*)]/script[starts-with(.,"alert(")])' curl.tmp.html)
  assert_equal "" "${errmsg}" 107 "error: '${errmsg}'"
  echo $(xmllint --html --nowarning --xpath 'string(/html/body//form[@name="loginform"]//input[@name="token"]/@value)' curl.tmp.html)
  # string(..) http://stackoverflow.com/a/18390404
}

echo "#### Test wrong token"
rm curl.*
LOCATION="${BASE_URL}/?do=login"
TOKEN="just some bogus"

echo "POST ${LOCATION}"
LOCATION=$(curl --url "${LOCATION}" \
  --data-urlencode "login=${USERNAME}" \
  --data-urlencode "password=${PASSWORD}" \
  --data-urlencode "token=${TOKEN}" \
  --data-urlencode "returnurl=${BASE_URL}/?do=changepasswd" \
  --cookie curl.cook --cookie-jar curl.cook \
  --location --output curl.tmp.html \
  --trace-ascii curl.tmp.trace --dump-header curl.tmp.head \
  --write-out '%{url_effective}' 2>/dev/null)
errmsg=$(xmllint --html --nowarning --xpath 'string(/html[1 = count(*)]/head[1 = count(*)]/script[starts-with(.,"alert(")])' curl.tmp.html)
assert_fgrep "alert(\"Wrong login/password.\");document.location='?do=login" "${errmsg}" 59 "expected failure"



echo "#### Test wrong username"
rm curl.*
LOCATION="${BASE_URL}/?do=login"
TOKEN="$(fetch_token "${LOCATION}")"
# the precise length doesn't matter, it just has to be significantly larger than ''
assert_equal 40 $(printf "%s" ${TOKEN} | wc -c) 68 "found TOKEN=${TOKEN}"

echo "POST ${LOCATION}"
LOCATION=$(curl --url "${LOCATION}" \
  --data-urlencode "login=f o o" \
  --data-urlencode "password=${PASSWORD}" \
  --data-urlencode "token=${TOKEN}" \
  --data-urlencode "returnurl=${BASE_URL}/?do=changepasswd" \
  --cookie curl.cook --cookie-jar curl.cook \
  --location --output curl.tmp.html \
  --trace-ascii curl.tmp.trace --dump-header curl.tmp.head \
  --write-out '%{url_effective}' 2>/dev/null)
errmsg=$(xmllint --html --nowarning --xpath 'string(/html[1 = count(*)]/head[1 = count(*)]/script[starts-with(.,"alert(")])' curl.tmp.html)
assert_fgrep "alert(\"Wrong login/password.\");document.location='?do=login" "${errmsg}" 81 "expected failure"



echo "#### Test wrong password"
rm curl.*
LOCATION="${BASE_URL}/?do=login"
TOKEN="$(fetch_token "${LOCATION}")"
# the precise length doesn't matter, it just has to be significantly larger than ''
assert_equal 40 $(printf "%s" ${TOKEN} | wc -c) 90 "found TOKEN=${TOKEN}"

echo "POST ${LOCATION}"
LOCATION=$(curl --url "${LOCATION}" \
  --data-urlencode "login=${USERNAME}" \
  --data-urlencode "password=f o o" \
  --data-urlencode "token=${TOKEN}" \
  --data-urlencode "returnurl=${BASE_URL}/?do=changepasswd" \
  --cookie curl.cook --cookie-jar curl.cook \
  --location --output curl.tmp.html \
  --trace-ascii curl.tmp.trace --dump-header curl.tmp.head \
  --write-out '%{url_effective}' 2>/dev/null)
errmsg=$(xmllint --html --nowarning --xpath 'string(/html[1 = count(*)]/head[1 = count(*)]/script[starts-with(.,"alert(")])' curl.tmp.html)
assert_fgrep "alert(\"Wrong login/password.\");document.location='?do=login" "${errmsg}" 103 "expected failure"



echo "#### Test wrong password (again)"
rm curl.*
LOCATION="${BASE_URL}/?do=login"
TOKEN="$(fetch_token "${LOCATION}")"
# the precise length doesn't matter, it just has to be significantly larger than ''
assert_equal 40 $(printf "%s" ${TOKEN} | wc -c) 112 "found TOKEN=${TOKEN}"

echo "POST ${LOCATION}"
LOCATION=$(curl --url "${LOCATION}" \
  --data-urlencode "login=${USERNAME}" \
  --data-urlencode "password=f o o" \
  --data-urlencode "token=${TOKEN}" \
  --data-urlencode "returnurl=${BASE_URL}/?do=changepasswd" \
  --cookie curl.cook --cookie-jar curl.cook \
  --location --output curl.tmp.html \
  --trace-ascii curl.tmp.trace --dump-header curl.tmp.head \
  --write-out '%{url_effective}' 2>/dev/null)
errmsg=$(xmllint --html --nowarning --xpath 'string(/html[1 = count(*)]/head[1 = count(*)]/script[starts-with(.,"alert(")])' curl.tmp.html)
assert_fgrep "alert(\"Wrong login/password.\");document.location='?do=login" "${errmsg}" 125 "expected failure"



echo "#### Test banned ip (4 previous failures)"
rm curl.*
LOCATION="${BASE_URL}/?do=login"
TOKEN="$(fetch_token "${LOCATION}")"
errmsg=$(xmllint --html --nowarning --xpath 'string(normalize-space(/html/body//*[@id="headerform"]))' curl.tmp.html)
assert_equal "You have been banned from login after too many failed attempts. Try later." "${errmsg}" 134 "expected failure"

D tmp/.gitkeep => tmp/.gitkeep +0 -0