SHARE
TWEET

Plowshare bug patch

a guest Sep 19th, 2011 269 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #!/bin/bash
  2. #
  3. # Common set of functions used by modules
  4. # Copyright (c) 2010-2011 Plowshare team
  5. #
  6. # This file is part of Plowshare.
  7. #
  8. # Plowshare is free software: you can redistribute it and/or modify
  9. # it under the terms of the GNU General Public License as published by
  10. # the Free Software Foundation, either version 3 of the License, or
  11. # (at your option) any later version.
  12. #
  13. # Plowshare is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16. # GNU General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU General Public License
  19. # along with Plowshare.  If not, see <http://www.gnu.org/licenses/>.
  20.  
  21. set -o pipefail
  22.  
  23. # Global error codes
  24. # 0 means success or link alive
  25. ERR_FATAL=1                      # Unexpected result (upstream site updated, etc)
  26. ERR_NOMODULE=2                   # No module found for processing request
  27. ERR_NETWORK=3                    # Specific network error (socket reset, curl, etc)
  28. ERR_LOGIN_FAILED=4               # Correct login/password argument is required
  29. ERR_MAX_WAIT_REACHED=5           # Refer to plowdown wait timeout (see -t/--timeout command line option)
  30. ERR_MAX_TRIES_REACHED=6          # Refer to plowdown max tries reached (see --max-retries command line option)
  31. ERR_CAPTCHA=7                    # Captcha solving failure
  32. ERR_SYSTEM=8                     # System failure (missing executable, local filesystem, wrong behavior, etc)
  33. ERR_LINK_TEMP_UNAVAILABLE=10     # Link alive but temporarily unavailable
  34.                                  # (also refer to plowdown --no-arbitrary-wait command line option)
  35. ERR_LINK_PASSWORD_REQUIRED=11    # Link alive but requires a password
  36. ERR_LINK_NEED_PERMISSIONS=12     # Link alive but requires some authentication (premium link)
  37.                                  # or operation not allowed for anonymous user
  38. ERR_LINK_DEAD=13                 #
  39. ERR_FATAL_MULTIPLE=100           # 100 + (n) with n = first error code (when multiple arguments)
  40.  
  41. # Global variables used (defined in other .sh)
  42. #   - VERBOSE          Verbose log level (0=none, 1, 2, 3, 4)
  43. #   - INTERFACE        Network interface (used by curl)
  44. #   - LIMIT_RATE       Network speed (used by curl)
  45. #   - GLOBAL_COOKIES   User provided cookie
  46. #   - LIBDIR           Absolute path to plowshare's libdir
  47. #
  48. # Global variables defined here:
  49. #   - PS_TIMEOUT       Timeout (in seconds) for one URL download
  50. #   - PS_RETRY_LIMIT   Number of tries for loops (mainly for captchas)
  51. #   - RECAPTCHA_SERVER Server URL
  52. #
  53. # Logs are sent to stderr stream.
  54. # Policies:
  55. # - error: modules errors (when return 1), lastest plowdown curl call
  56. # - notice: core messages (ocr, wait, timeout, retries), lastest plowdown curl call
  57. # - debug: modules messages, curl (intermediate) calls
  58. # - report: debug plus curl content (html pages, cookies)
  59.  
  60. # log_report for a file
  61. # $1: filename
  62. logcat_report() {
  63.     if test -s "$1"; then
  64.         local STRING=$(sed -e 's/^[[:space:]]*//; s/[[:space:]]*$//' \
  65.                            -e 's/^/rep:/' "$1")
  66.         test $(verbose_level) -ge 4 && stderr "$STRING"
  67.     fi
  68.     return 0
  69. }
  70.  
  71. # This should not be called within modules
  72. log_report() {
  73.     test $(verbose_level) -ge 4 && stderr "rep: $@"
  74.     return 0
  75. }
  76.  
  77. log_debug() {
  78.     test $(verbose_level) -ge 3 && stderr "dbg: $@"
  79.     return 0
  80. }
  81.  
  82. # This should not be called within modules
  83. log_notice() {
  84.     test $(verbose_level) -ge 2 && stderr "$@"
  85.     return 0
  86. }
  87.  
  88. log_error() {
  89.     test $(verbose_level) -ge 1 && stderr "$@"
  90.     return 0
  91. }
  92.  
  93. ## ----------------------------------------------------------------------------
  94.  
  95. ##
  96. ## All helper functions below can be called by modules
  97. ## (see documentation...)
  98. ##
  99.  
  100. # Wrapper for curl: debug and infinite loop control
  101. # $1..$n are curl arguments
  102. curl() {
  103.     local -a OPTIONS=(--insecure --speed-time 600 --connect-timeout 300)
  104.  
  105.     # Check if caller as specified a User-Agent, if so don't put one
  106.     local exist=0
  107.     for e; do
  108.         if [ "$e" = '-A' -o "$e" = '--user-agent' ]; then
  109.             exist=1
  110.             break
  111.         fi
  112.     done
  113.     if [ "$exist" -eq 0 ]; then
  114.         OPTIONS[5]='--user-agent'
  115.         OPTIONS[6]='Mozilla/5.0 (X11; Linux x86_64; rv:6.0) Gecko/20100101 Firefox/6.0'
  116.     fi
  117.  
  118.     local DRETVAL=0
  119.  
  120.     # no verbose unless debug level; don't show progress meter for report level too
  121.     test $(verbose_level) -ne 3 && OPTIONS=("${OPTIONS[@]}" "--silent")
  122.  
  123.     test -n "$INTERFACE" && OPTIONS=("${OPTIONS[@]}" "--interface" "$INTERFACE")
  124.     test -n "$LIMIT_RATE" && OPTIONS=("${OPTIONS[@]}" "--limit-rate" "$LIMIT_RATE")
  125.  
  126.     if test -z "$GLOBAL_COOKIES"; then
  127.         set -- $(type -P curl) "${OPTIONS[@]}" "$@"
  128.     else
  129.         set -- $(type -P curl) "-b $GLOBAL_COOKIES" "${OPTIONS[@]}" "$@"
  130.     fi
  131.  
  132.     if test $(verbose_level) -lt 4; then
  133.         "$@" || DRETVAL=$?
  134.     else
  135.         local TEMPCURL=$(create_tempfile)
  136.         log_report "$@"
  137.         "$@" --show-error 2>&1 | tee "$TEMPCURL" || DRETVAL=$?
  138.         FILESIZE=$(get_filesize "$TEMPCURL")
  139.         log_report "Received $FILESIZE bytes"
  140.         log_report "=== CURL BEGIN ==="
  141.         logcat_report "$TEMPCURL"
  142.         log_report "=== CURL END ==="
  143.         rm -rf "$TEMPCURL"
  144.     fi
  145.  
  146.     case "$DRETVAL" in
  147.         0)
  148.            ;;
  149.         # Partial file / HTTP retrieve error / Operation timeout
  150.         18 | 22 | 28)
  151.             log_error "curl retrieve error"
  152.             return $ERR_NETWORK
  153.             ;;
  154.         # Write error
  155.         23)
  156.             log_error "write failed, disk full?"
  157.             return $ERR_SYSTEM
  158.             ;;
  159.         *)
  160.             log_error "curl failed ($DRETVAL)"
  161.             return $ERR_NETWORK
  162.             ;;
  163.     esac
  164.     return 0
  165. }
  166.  
  167. # Force debug verbose level (unless -v0/-q specified)
  168. curl_with_log() {
  169.     local TEMP_VERBOSE=$(verbose_level)
  170.  
  171.     if [ "$TEMP_VERBOSE" -eq 0 ]; then
  172.         TEMP_VERBOSE=0
  173.     elif [ "$TEMP_VERBOSE" -lt 3 ]; then
  174.         TEMP_VERBOSE=3
  175.     fi
  176.  
  177.     VERBOSE=$TEMP_VERBOSE curl "$@"
  178. }
  179.  
  180. # Substring replacement (replace all matches)
  181. #
  182. # stdin: input string
  183. # $1: substring to find (this is not a regexp)
  184. # $2: replacement string (this is not a regexp)
  185. replace() {
  186.     S="$(cat)"
  187.     # We must escape '\' character
  188.     FROM="${1//\\/\\\\}"
  189.     echo "${S//$FROM/$2}"
  190. }
  191.  
  192. # Delete leading and trailing spaces, tabs, \r, ...
  193. # stdin: input string (can be multiline)
  194. # stdout: result string
  195. strip() {
  196.     sed 's/^[[:space:]]*//; s/[[:space:]]*$//'
  197. }
  198.  
  199. # Return uppercase string : tr '[:lower:]' '[:upper:]'
  200. # Note: Busybox "tr" command may not have classes support (CONFIG_FEATURE_TR_CLASSES)
  201. uppercase() {
  202.     tr '[a-z]' '[A-Z]'
  203. }
  204.  
  205. # Return lowercase string : tr '[:upper:]' '[:lower:]'
  206. lowercase() {
  207.     tr '[A-Z]' '[a-z]'
  208. }
  209.  
  210. # Grep first line of a text
  211. # stdin: input string (multiline)
  212. first_line() {
  213.     # equivalent to `sed -e 1q`
  214.     head -n1
  215. }
  216.  
  217. # Grep last line of a text
  218. # stdin: input string (multiline)
  219. last_line() {
  220.     # equivalent to `sed -ne '$p'`
  221.     tail -n1
  222. }
  223.  
  224. # Grep nth line of a text
  225. # stdin: input string (multiline)
  226. # $1: line number (start at index 1)
  227. nth_line() {
  228.    # equivalent to `sed -e "${1}q;d"`
  229.    sed -ne "${1}p"
  230. }
  231.  
  232. # Check if a string ($2) matches a regexp ($1)
  233. # This is case sensitive.
  234. #
  235. # $? is zero on success
  236. match() {
  237.     grep -q "$1" <<< "$2"
  238. }
  239.  
  240. # Check if a string ($2) matches a regexp ($1)
  241. # This is not case sensitive.
  242. #
  243. # $? is zero on success
  244. matchi() {
  245.     grep -iq "$1" <<< "$2"
  246. }
  247.  
  248. # Get lines that match filter+match regular expressions and extract string from it.
  249. #
  250. # stdin: text data
  251. # $1: POSIX-regexp to filter (get only the first matching line).
  252. # $2: POSIX-regexp to match (use parenthesis) on the matched line.
  253. parse_all() {
  254.     local STRING=$(sed -n "/$1/s/^.*$2.*$/\1/p")
  255.     test "$STRING" && echo "$STRING" ||
  256.         { log_error "parse failed: sed -n \"/$1/$2\""; return $ERR_FATAL; }
  257. }
  258.  
  259. # Like parse_all, but get only first match
  260. parse() {
  261.     parse_all "$@" | head -n1
  262. }
  263.  
  264. # Like parse_all, but get only last match
  265. parse_last() {
  266.     parse_all "$@" | tail -n1
  267. }
  268.  
  269. # Like parse, but hide output to stderr
  270. parse_quiet() {
  271.     parse "$@" 2>/dev/null
  272. }
  273.  
  274. # Get lines that first filter regex, then apply match regex on the line after
  275. #
  276. # stdin: text data
  277. # $1: POSIX-regexp to filter (get only the first matching line).
  278. # $2: POSIX-regexp to match (use parenthesis) on the matched line.
  279. parse_line_after_all() {
  280.     local STRING=$(sed -n "/$1/{n;s/^.*$2.*$/\1/p}")
  281.     test "$STRING" && echo "$STRING" ||
  282.         { log_error "parse failed: sed -n \"/$1/$2\""; return $ERR_FATAL; }
  283. }
  284.  
  285. # Like parse_line_after_all, but get only first match
  286. parse_line_after() {
  287.     parse_line_after_all "$@" | head -n1
  288. }
  289.  
  290. # Grep first "Location" (of http header)
  291. #
  292. # stdin: result of curl request (with -i/--include, -D/--dump-header or
  293. #        or -I/--head flag)
  294. grep_http_header_location() {
  295.     sed -n 's/^[Ll]ocation:[[:space:]]\+\([^ ]*\)/\1/p' 2>/dev/null | tr -d "\r"
  296. }
  297.  
  298. grep_http_header_content_location() {
  299.     sed -n 's/^[Cc]ontent-[Ll]ocation:[[:space:]]\+\([^ ]*\)/\1/p' 2>/dev/null | tr -d "\r"
  300. }
  301.  
  302. grep_http_header_content_type() {
  303.     sed -n 's/^[Cc]ontent-[Tt]ype:[[:space:]]\+\([^ ]*\)/\1/p' 2>/dev/null | tr -d "\r"
  304. }
  305.  
  306. # Grep first "Content-Disposition" (of http header)
  307. #
  308. # stdin: same as grep_http_header_location() below
  309. # stdout: attachement filename
  310. grep_http_header_content_disposition() {
  311.     parse "[Cc]ontent-[Dd]isposition:" 'filename="\(.*\)"' 2>/dev/null
  312. }
  313.  
  314. # Extract a specific form from a HTML content.
  315. # We assume here that start marker <form> and end marker </form> are one separate lines.
  316. # HTML comments are just ignored. But it's enough for our needs.
  317. #
  318. # $1: (X)HTML data
  319. # $2: (optionnal) Nth <form> (default is 1)
  320. # stdout: result
  321. grep_form_by_order() {
  322.     local DATA="$1"
  323.     local N=${2:-"1"}
  324.  
  325.     while [ "$N" -gt "1" ]; do
  326.         ((N--))
  327.         DATA=$(echo "$DATA" | sed -ne '/<\/form>/,$p' | sed -e '1s/<\/form>/<_form>/1')
  328.     done
  329.  
  330.     # FIXME: sed will be greedy, if other forms are remaining they will be returned
  331.     echo "$DATA" | sed -ne '/<form /,/<\/form>/p'
  332. }
  333.  
  334. # Extract a named form from a HTML content.
  335. # If several forms have the same name, take first one.
  336. #
  337. # $1: (X)HTML data
  338. # $2: "name" attribute of <form> marker
  339. # stdout: result
  340. grep_form_by_name() {
  341.     local DATA="$1"
  342.  
  343.     if [ -n "$2" ]; then
  344.         # FIXME: sed will be greedy, if other forms are remaining they will be returned
  345.         echo "$DATA" | sed -ne "/<[Ff][Oo][Rr][Mm][[:space:]].*name=\"\?$2\"\?/,/<\/[Ff][Oo][Rr][Mm]>/p"
  346.     fi
  347. }
  348.  
  349. # Extract a id-specified form from a HTML content.
  350. # If several forms have the same id, take first one.
  351. #
  352. # $1: (X)HTML data
  353. # $2: "id" attribute of <form> marker
  354. # stdout: result
  355. grep_form_by_id() {
  356.     local DATA="$1"
  357.  
  358.     if [ -n "$2" ]; then
  359.         # FIXME: sed will be greedy, if other forms are remaining they will be returned
  360.         echo "$DATA" | sed -ne "/<[Ff][Oo][Rr][Mm][[:space:]].*id=\"\?$2\"\?/,/<\/[Ff][Oo][Rr][Mm]>/p"
  361.     fi
  362. }
  363.  
  364. # Split into several lines html markers.
  365. # Insert a new line after ending marker.
  366. #
  367. # stdin: (X)HTML data
  368. # stdout: result
  369. break_html_lines() {
  370.     sed 's/\(<\/[^>]*>\)/\1\n/g'
  371. }
  372.  
  373. # Split into several lines html markers.
  374. # Insert a new line after each (beginning or ending) marker.
  375. #
  376. # stdin: (X)HTML data
  377. # stdout: result
  378. break_html_lines_alt() {
  379.     sed 's/\(<[^>]*>\)/\1\n/g'
  380. }
  381.  
  382. # Return value of html attribute
  383. parse_attr() {
  384.     parse "$1" "$2=[\"']\?\([^\"'>]*\)"
  385. }
  386.  
  387. # Like parse_attr, but hide output to stderr
  388. parse_attr_quiet() {
  389.     parse_attr "$@" 2>/dev/null
  390. }
  391.  
  392. # Return value of html attribute
  393. parse_all_attr() {
  394.     parse_all "$1" "$2=[\"']\?\([^\"'>]*\)"
  395. }
  396.  
  397. # Retreive "action" attribute (URL) from a <form> marker
  398. #
  399. # stdin: (X)HTML data (idealy, call grep_form_by_xxx before)
  400. # stdout: result
  401. parse_form_action() {
  402.     parse '<[Ff][Oo][Rr][Mm]' 'action="\([^"]*\)"'
  403. }
  404.  
  405. # Retreive "value" attribute from a named <input> marker
  406. #
  407. # $1: name attribute of <input> marker
  408. # stdin: (X)HTML data
  409. # stdout: result (can be null string if <input> has no value attribute)
  410. parse_form_input_by_name() {
  411.     parse_quiet "<input\([[:space:]]*[^ ]*\)*name=[\"']\?$1[\"']\?" "value=[\"']\?\([^'\">]*\)"
  412. }
  413.  
  414. # Retreive "value" attribute from a typed <input> marker
  415. #
  416. # $1: type attribute of <input> marker (for example: "submit")
  417. # stdin: (X)HTML data
  418. # stdout: result (can be null string if <input> has no value attribute)
  419. parse_form_input_by_type() {
  420.     parse_quiet "<input\([[:space:]]*[^ ]*\)*type=[\"']\?$1[\"']\?" "value=[\"']\?\([^'\">]*\)"
  421. }
  422.  
  423. # Retreive "id" attributes from typed <input> marker(s)
  424. parse_all_form_input_by_type_with_id() {
  425.     parse_all "<input\([[:space:]]*[^ ]*\)*type=[\"']\?$1[\"']\?" "id=[\"']\?\([^'\">]*\)" 2>/dev/null
  426. }
  427.  
  428. # Get accessor for cookies
  429. # Example: LANG=$(parse_cookie "lang" < "$COOKIES")
  430. parse_cookie() {
  431.     parse_quiet "\t$1\t[^\t]*\$" "\t$1\t\(.*\)"
  432. }
  433.  
  434. # Return base of URL
  435. # Example: http://www.host.com/a/b/c/d => http://www.host.com
  436. # Note: Avoid using Bash regexp or `expr` for portability purposes
  437. #
  438. # $1: URL
  439. basename_url() {
  440.     sed -e 's/\(https\?:\/\/[^\/]*\).*/\1/' <<<"$1"
  441. }
  442.  
  443. # Return basename of file path
  444. # Example: /usr/bin/foo.bar => foo.bar
  445. #
  446. # $1: filename
  447. basename_file() {
  448.     # `basename -- "$1"` may be screwed on some BusyBox versions
  449.     echo "${1##*/}"
  450. }
  451.  
  452. # HTML entities will be translated
  453. #
  454. # stdin: data
  455. # stdout: data (converted)
  456. html_to_utf8() {
  457.     if check_exec 'recode'; then
  458.         log_report "html_to_utf8: use recode"
  459.         recode html..utf8
  460.     elif check_exec 'perl'; then
  461.         log_report "html_to_utf8: use perl"
  462.         perl -n -mHTML::Entities \
  463.              -e 'BEGIN { eval{binmode(STDOUT,q[:utf8]);}; }; print HTML::Entities::decode_entities($_);'
  464.     else
  465.         log_notice "recode binary not found, pass-through"
  466.         cat
  467.     fi
  468. }
  469.  
  470. # Encode a text to include into an url.
  471. # - Reserved Characters (18): !*'();:@&=+$,/?#[]
  472. # - Check for percent (%) & space character
  473. #
  474. # - Unreserved Characters: ALPHA / DIGIT / "-" / "." / "_" / "~"
  475. # - Unsafe characters (RFC2396) should not be percent-encoded anymore: <>{}|\^`
  476. #
  477. # stdin: data (example: relative URL)
  478. # stdout: data (should complain RFC3986)
  479. uri_encode_strict() {
  480.     sed -e 's/\%/%25/g'   -e 's/\x20/%20/g' \
  481.         -e 's/\x21/%21/g' -e 's/\x2A/%2A/g' -e 's/\x27/%27/g' \
  482.         -e 's/\x28/%28/g' -e 's/\x29/%29/g' -e 's/\x3B/%3B/g' \
  483.         -e 's/\x3A/%3A/g' -e 's/\x40/%40/g' -e 's/\x26/%26/g' \
  484.         -e 's/\x3D/%3D/g' -e 's/\x2B/%2B/g' -e 's/\$/%24/g'   \
  485.         -e 's/\x2C/%2C/g' -e 's|/|%2F|g'    -e 's/\x3F/%3F/g' \
  486.         -e 's/\x23/%23/g' -e 's/\[/%5B/g'   -e 's/\]/%5D/g'
  487. }
  488.  
  489. # Encode a complete url.
  490. # - check for space character and squares brackets
  491. # - do not check for "reserved characters" (use "uri_encode_strict" for that)
  492. #
  493. # Bad encoded URL request can lead to HTTP error 400.
  494. # curl doesn't do any checks, whereas wget convert provided url.
  495. #
  496. # stdin: data (example: absolute URL)
  497. # stdout: data (nearly complain RFC3986)
  498. uri_encode() {
  499.     sed -e 's/\x20/%20/g' -e 's/\[/%5B/g' -e 's/\]/%5D/g'
  500. }
  501.  
  502. # Decode a complete url.
  503. # - check for space character and round/squares brackets
  504. # - reserved characters: only coma is checked
  505. #
  506. # stdin: data (example: absolute URL)
  507. # stdout: data (nearly complain RFC3986)
  508. uri_decode() {
  509.     sed -e 's/%20/\x20/g' -e 's/%5B/\[/g' -e 's/%5D/\]/g' \
  510.         -e 's/%2C/,/g' -e 's/%28/(/g' -e 's/%29/)/g' -e 's/%2B/+/g'
  511. }
  512.  
  513. # Retrieves size of file
  514. #
  515. # $1: filename
  516. # stdout: file length (in bytes)
  517. get_filesize() {
  518.     local SIZE=`stat -c %s "$1" 2>/dev/null`
  519.     if [ -z "$SIZE" ]; then
  520.         log_error "stat binary not found"
  521.         echo "-1"
  522.     else
  523.         echo "$SIZE"
  524.     fi
  525. }
  526.  
  527. # Create a tempfile and return path
  528. #
  529. # $1: Suffix
  530. create_tempfile() {
  531.     SUFFIX=$1
  532.     FILE="${TMPDIR:-/tmp}/$(basename_file $0).$$.$RANDOM$SUFFIX"
  533.     :> "$FILE" || return $ERR_SYSTEM
  534.     echo "$FILE"
  535. }
  536.  
  537. # User password entry
  538. #
  539. # stdout: entered password (can be null string)
  540. # $? is non zero if no password
  541. prompt_for_password() {
  542.     local PASSWORD
  543.  
  544.     log_notice "No password specified, enter it now"
  545.     stty -echo
  546.     read -p "Enter password: " PASSWORD
  547.     stty echo
  548.  
  549.     echo "$PASSWORD"
  550.     test -n "$PASSWORD" || return $ERR_LINK_PASSWORD_REQUIRED
  551. }
  552.  
  553. # Login and return cookie.
  554. # A non empty cookie file does not means that login is successful.
  555. #
  556. # $1: String 'username:password' (password can contain semicolons)
  557. # $2: Cookie filename (see create_tempfile() modules)
  558. # $3: Postdata string (ex: 'user=\$USER&password=\$PASSWORD')
  559. # $4: URL to post
  560. # $5: Additional curl arguments (optional)
  561. # stdout: html result (can be null string)
  562. # $? is zero on success
  563. post_login() {
  564.     local AUTH=$1
  565.     local COOKIE=$2
  566.     local POSTDATA=$3
  567.     local LOGINURL=$4
  568.     local CURL_ARGS=$5
  569.  
  570.     if test "$GLOBAL_COOKIES"; then
  571.         REGEXP=$(echo "$LOGINURL" | grep -o "://[^/]*" | grep -o "[^.]*\.[^.]*$")
  572.         if grep -q "^\.\?$REGEXP" "$GLOBAL_COOKIES" 2>/dev/null; then
  573.             log_debug "cookies for site ($REGEXP) found in cookies file, login skipped"
  574.             return
  575.         fi
  576.         log_debug "cookies not found for site ($REGEXP), continue login process"
  577.     fi
  578.  
  579.     # Seem faster than
  580.     # IFS=":" read USER PASSWORD <<< "$AUTH"
  581.     USER=$(echo "${AUTH%%:*}" | uri_encode_strict)
  582.     PASSWORD=$(echo "${AUTH#*:}" | uri_encode_strict)
  583.  
  584.     if [ -z "$PASSWORD" -o "$AUTH" == "$PASSWORD" ]; then
  585.         PASSWORD=$(prompt_for_password) || true
  586.     fi
  587.  
  588.     log_notice "Starting login process: $USER/$(sed 's/./*/g' <<< "$PASSWORD")"
  589.  
  590.     DATA=$(eval echo $(echo "$POSTDATA" | sed "s/&/\\\\&/g"))
  591.  
  592.     # Yes, no quote around $CURL_ARGS
  593.     local RESULT=$(curl --cookie-jar "$COOKIE" --data "$DATA" $CURL_ARGS "$LOGINURL")
  594.  
  595.     # For now "-z" test is kept.
  596.     # There is no known case of a null $RESULT on successful login.
  597.     if [ -z "$RESULT" -o ! -s "${GLOBAL_COOKIES:-$COOKIE}" ]; then
  598.         log_error "login request failed"
  599.         return $ERR_LOGIN_FAILED
  600.     fi
  601.  
  602.     log_report "=== COOKIE BEGIN ==="
  603.     logcat_report "$COOKIE"
  604.     log_report "=== COOKIE END ==="
  605.  
  606.     echo "$RESULT"
  607.     return 0
  608. }
  609.  
  610. # Execute javascript code
  611. #
  612. # stdin: js script
  613. # stdout: script results
  614. # $?: boolean
  615. javascript() {
  616.     local JS_PRG TEMPSCRIPT
  617.  
  618.     JS_PRG=$(detect_javascript) || return
  619.     TEMPSCRIPT=$(create_tempfile) || return
  620.  
  621.     cat > $TEMPSCRIPT
  622.  
  623.     log_report "interpreter:$JS_PRG"
  624.     log_report "=== JAVASCRIPT BEGIN ==="
  625.     logcat_report "$TEMPSCRIPT"
  626.     log_report "=== JAVASCRIPT END ==="
  627.  
  628.     $JS_PRG "$TEMPSCRIPT"
  629.     rm -rf "$TEMPSCRIPT"
  630.     return 0
  631. }
  632.  
  633. # Dectect if a JavaScript interpreter is installed
  634. #
  635. # stdout: path of executable
  636. # $?: boolean (0 means found)
  637. detect_javascript() {
  638.     if ! check_exec 'js'; then
  639.         log_notice "Javascript interpreter not found"
  640.         return $ERR_SYSTEM
  641.     fi
  642.     type -P 'js'
  643. }
  644.  
  645. # Dectect if a Perl interpreter is installed
  646. #
  647. # stdout: path of executable
  648. # $?: boolean (0 means found)
  649. detect_perl() {
  650.     if ! check_exec 'perl'; then
  651.         log_notice "Perl interpreter not found"
  652.         return $ERR_SYSTEM
  653.     fi
  654.     type -P 'perl'
  655. }
  656.  
  657. # Wait some time
  658. # Related to --timeout plowdown command line option
  659. #
  660. # $1: Sleep duration
  661. # $2: Unit (seconds | minutes)
  662. wait() {
  663.     local VALUE=$1
  664.     local UNIT=$2
  665.  
  666.     if test "$VALUE" = '0'; then
  667.         log_debug "wait called with null duration"
  668.         return
  669.     fi
  670.  
  671.     if [ "$UNIT" = "minutes" ]; then
  672.         UNIT_SECS=60
  673.         UNIT_STR=minutes
  674.     else
  675.         UNIT_SECS=1
  676.         UNIT_STR=seconds
  677.     fi
  678.     local TOTAL_SECS=$((VALUE * UNIT_SECS))
  679.  
  680.     timeout_update $TOTAL_SECS || return
  681.  
  682.     local REMAINING=$TOTAL_SECS
  683.     local MSG="Waiting $VALUE $UNIT_STR..."
  684.     local CLEAR="     \b\b\b\b\b"
  685.     if test -t 2; then
  686.       while [ "$REMAINING" -gt 0 ]; do
  687.           log_notice -ne "\r$MSG $(splitseconds $REMAINING) left${CLEAR}"
  688.           sleep 1
  689.           (( REMAINING-- ))
  690.       done
  691.       log_notice -e "\r$MSG done${CLEAR}"
  692.     else
  693.       log_notice "$MSG"
  694.       sleep $TOTAL_SECS
  695.     fi
  696. }
  697.  
  698. # Related to --max-retries plowdown command line option
  699. retry_limit_not_reached() {
  700.     test -z "$PS_RETRY_LIMIT" && return
  701.     log_notice "Tries left: $PS_RETRY_LIMIT"
  702.     (( PS_RETRY_LIMIT-- ))
  703.     test "$PS_RETRY_LIMIT" -ge 0 || return $ERR_MAX_TRIES_REACHED
  704. }
  705.  
  706. # Related to --no-arbitrary-wait plowdown command line option
  707. no_arbitrary_wait() {
  708.     if test "$NOARBITRARYWAIT"; then
  709.         log_debug "File temporarily unavailable"
  710.         return $ERR_LINK_TEMP_UNAVAILABLE
  711.     fi
  712.     log_debug "Arbitrary wait"
  713.     return 0
  714. }
  715.  
  716. # OCR of an image.
  717. #
  718. # $1: optional varfile
  719. # stdin: image (binary)
  720. # stdout: result OCRed text
  721. ocr() {
  722.     local OPT_CONFIGFILE="$LIBDIR/tesseract/plowshare_nobatch"
  723.     local OPT_VARFILE="$LIBDIR/tesseract/$1"
  724.     test -f "$OPT_VARFILE" || OPT_VARFILE=''
  725.  
  726.     # Tesseract somewhat "peculiar" arguments requirement makes impossible
  727.     # to use pipes or process substitution. Create temporal files
  728.     # instead (*sigh*).
  729.     TIFF=$(create_tempfile ".tif")
  730.     TEXT=$(create_tempfile ".txt")
  731.  
  732.     convert - tif:- > $TIFF
  733.     LOG=$(tesseract $TIFF ${TEXT/%.txt} $OPT_CONFIGFILE $OPT_VARFILE 2>&1)
  734.     if [ $? -ne 0 ]; then
  735.         rm -f $TIFF $TEXT
  736.         log_error "$LOG"
  737.         return $ERR_SYSTEM
  738.     fi
  739.  
  740.     cat $TEXT
  741.     rm -f $TIFF $TEXT
  742. }
  743.  
  744. # $1: local image filename (with full path). No specific image format expected.
  745. # $2 (optional): view method
  746. # stdout: captcha answer (or nothing depending $2)
  747. #
  748. # Note: reCAPTCHA image are 300x57.
  749. captcha_process() {
  750.     local FILENAME="$1"
  751.     local METHOD_VIEW=
  752.     local METHOD_SOLVE=
  753.  
  754.     local TEXT1='Leave this field blank and hit enter to get another captcha image'
  755.     local TEXT2='Enter captcha response (drop punctuation marks, case insensitive): '
  756.  
  757.     if [ -z "$METHOD_VIEW" ]; then
  758.         # X11 server installed ?
  759.         if [ -n "$DISPLAY" ]; then
  760.             if check_exec 'display'; then
  761.                 METHOD_VIEW=Xdisplay
  762.             else
  763.                 log_notice "no X11 image viewer found, to display captcha image"
  764.             fi
  765.         fi
  766.         if [ -z "$METHOD_VIEW" ]; then
  767.             log_debug "no X server available, try ascii display"
  768.             # libcaca
  769.             if check_exec img2txt; then
  770.                 METHOD_VIEW=img2txt
  771.             # terminal image view (perl script using Image::Magick)
  772.             elif check_exec tiv; then
  773.                 METHOD_VIEW=tiv
  774.             # libaa
  775.             elif check_exec aview; then
  776.                 METHOD_VIEW=aview
  777.             else
  778.                 log_notice "no ascii viewer found to display captcha image"
  779.                 METHOD_VIEW=none
  780.             fi
  781.         fi
  782.     fi
  783.  
  784.     # Try to maximize the image size on terminal
  785.     local MAX_OUTPUT_WIDTH MAX_OUTPUT_HEIGHT
  786.     if [ "${METHOD_VIEW:0:1}" != "X" ]; then
  787.         if check_exec tput; then
  788.             MAX_OUTPUT_WIDTH=`tput cols`
  789.             MAX_OUTPUT_HEIGHT=`tput lines`
  790.             if check_exec identify; then
  791.                 local DIMENSION=$(identify -quiet "$FILENAME" | cut -d' ' -f3)
  792.                 local W=${DIMENSION%x*}
  793.                 local H=${DIMENSION#*x}
  794.                 [ "$W" -lt "$MAX_OUTPUT_WIDTH" ] && MAX_OUTPUT_WIDTH=$W
  795.                 [ "$H" -lt "$MAX_OUTPUT_HEIGHT" ] && MAX_OUTPUT_HEIGHT=$H
  796.             fi
  797.         else
  798.             MAX_OUTPUT_WIDTH=150
  799.             MAX_OUTPUT_HEIGHT=57
  800.         fi
  801.     fi
  802.  
  803.     local PRGPID=
  804.  
  805.     # How to display image
  806.     case "$METHOD_VIEW" in
  807.         none)
  808.             log_debug "image: $FILENAME"
  809.             ;;
  810.         aview)
  811.             local IMG_PNM=$(create_tempfile)
  812.             convert "$FILENAME" -negate -depth 8 pnm:$IMG_PNM
  813.             aview -width $MAX_OUTPUT_WIDTH -height $MAX_OUTPUT_HEIGHT \
  814.                 -kbddriver stdin -driver stdout "$IMG_PNM" 2>/dev/null <<< "q" | \
  815.                 sed  -e '1d;/\x0C/,/\x0C/d' | grep -v "^[[:space:]]*$" 1>&2
  816.             rm -f "$IMG_PNM"
  817.             ;;
  818.         tiv)
  819.             tiv -a -w $MAX_OUTPUT_WIDTH -h $MAX_OUTPUT_HEIGHT "$FILENAME" 1>&2
  820.             ;;
  821.         img2txt)
  822.             img2txt -W $MAX_OUTPUT_WIDTH -H $MAX_OUTPUT_HEIGHT "$FILENAME" 1>&2
  823.             ;;
  824.         Xdisplay)
  825.             display "$FILENAME" &
  826.             PRGPID=$!
  827.             ;;
  828.         *)
  829.             log_error "unknown method: $METHOD_VIEW"
  830.             ;;
  831.     esac
  832.  
  833.     [ -z "$METHOD_SOLVE" ] && METHOD_SOLVE=prompt
  834.  
  835.     # How to solve captcha
  836.     case "$METHOD_SOLVE" in
  837.         none)
  838.             ;;
  839.         prompt)
  840.             log_notice $TEXT1
  841.             read -p "$TEXT2" RESPONSE
  842.             [ -n "$PRGPID" ] && disown $(kill -9 $PRGPID) 2>&1 1>/dev/null
  843.             echo "$RESPONSE"
  844.             ;;
  845.         *)
  846.             log_error "unknown method: $METHOD_SOLVE"
  847.             ;;
  848.     esac
  849. }
  850.  
  851. ##
  852. ## reCAPTCHA functions (can be called from modules)
  853. ## Main engine: http://api.recaptcha.net/js/recaptcha.js
  854. ##
  855. RECAPTCHA_SERVER="http://www.google.com/recaptcha/api/"
  856.  
  857. # $1: reCAPTCHA site public key
  858. # stdout: image path
  859. recaptcha_load_image() {
  860.     local URL="${RECAPTCHA_SERVER}challenge?k=${1}&ajax=1"
  861.     log_debug "reCaptcha URL: $URL"
  862.  
  863.     local VARS=$(curl -L "$URL")
  864.  
  865.     if [ -n "$VARS" ]; then
  866.         local server=$(echo "$VARS" | parse_quiet 'server' "server[[:space:]]\?:[[:space:]]\?'\([^']*\)'")
  867.         local challenge=$(echo "$VARS" | parse_quiet 'challenge' "challenge[[:space:]]\?:[[:space:]]\?'\([^']*\)'")
  868.  
  869.         log_debug "reCaptcha server: $server"
  870.         log_debug "reCaptcha challenge: $challenge"
  871.  
  872.         # Image dimension: 300x57
  873.         local FILENAME="${TMPDIR:-/tmp}/recaptcha.${challenge}.jpg"
  874.         local CAPTCHA_URL="${server}image?c=${challenge}"
  875.         log_debug "reCaptcha image URL: $CAPTCHA_URL"
  876.         curl "$CAPTCHA_URL" -o "$FILENAME"
  877.  
  878.         log_debug "reCaptcha image: $FILENAME"
  879.         echo "$FILENAME"
  880.     fi
  881. }
  882.  
  883. # $1: reCAPTCHA image filename
  884. # stdout: challenge (string)
  885. recaptcha_get_challenge_from_image() {
  886.     basename_file "$1" | cut -d. -f2
  887. }
  888.  
  889. # $1: reCAPTCHA site public key
  890. # $2: reCAPTCHA image filename
  891. # stdout: new image path
  892. recaptcha_reload_image() {
  893.     FILENAME="$2"
  894.  
  895.     if [ -n "$FILENAME" ]; then
  896.         local challenge=$(recaptcha_get_challenge_from_image "$FILENAME")
  897.         local server="$RECAPTCHA_SERVER"
  898.  
  899.         local STATUS=$(curl "${server}reload?k=$1&c=${challenge}&reason=r&type=image&lang=en")
  900.         local challenge=$(echo "$STATUS" | parse_quiet 'finish_reload' "('\([^']*\)")
  901.  
  902.         local FILENAME="${TMPDIR:-/tmp}/recaptcha.${challenge}.jpg"
  903.         local CAPTCHA_URL="${server}image?c=${challenge}"
  904.         log_debug "reCaptcha image URL: $CAPTCHA_URL"
  905.         curl "$CAPTCHA_URL" -o "$FILENAME"
  906.  
  907.         log_debug "reCaptcha new image: $FILENAME"
  908.         echo "$FILENAME"
  909.     fi
  910. }
  911.  
  912. ## ----------------------------------------------------------------------------
  913.  
  914. ##
  915. ## Miscellaneous functions that can be called from core:
  916. ## download.sh, upload.sh, delete.sh, list.sh
  917. ##
  918.  
  919. # Remove all temporal files created by the script
  920. # (with create_tempfile)
  921. remove_tempfiles() {
  922.     rm -f "${TMPDIR:-/tmp}/$(basename_file $0).$$.*"
  923. }
  924.  
  925. # Exit callback (task: clean temporal files)
  926. set_exit_trap() {
  927.     trap "remove_tempfiles" EXIT
  928. }
  929.  
  930. # Check existance of executable in path
  931. # Better than "which" (external) executable
  932. #
  933. # $1: Executable to check
  934. # $?: zero means not found
  935. check_exec() {
  936.     type -P $1 >/dev/null || return 1 && return 0
  937. }
  938.  
  939. # Related to --timeout plowdown command line option
  940. timeout_init() {
  941.     PS_TIMEOUT=$1
  942. }
  943.  
  944. # Related to --max-retries plowdown command line option
  945. retry_limit_init() {
  946.     PS_RETRY_LIMIT=$1
  947. }
  948.  
  949. # Show help info for options
  950. #
  951. # $1: options
  952. # $2: indent string
  953. print_options() {
  954.     local OPTIONS=$1
  955.     while read OPTION; do
  956.         test "$OPTION" || continue
  957.         IFS="," read VAR SHORT LONG VALUE HELP <<< "$OPTION"
  958.         STRING=$2
  959.         test "$SHORT" && {
  960.             STRING="$STRING-${SHORT%:}"
  961.             test "$VALUE" && STRING="$STRING $VALUE"
  962.         }
  963.         test "$LONG" -a "$SHORT" && STRING="$STRING, "
  964.         test "$LONG" && {
  965.             STRING="$STRING--${LONG%:}"
  966.             test "$VALUE" && STRING="$STRING=$VALUE"
  967.         }
  968.         echo "$STRING: $HELP"
  969.     done <<< "$OPTIONS"
  970. }
  971.  
  972. # Show usage info for modules
  973. #
  974. # $1: module name list (one per line)
  975. # $2: option family name (string, example:UPLOAD)
  976. print_module_options() {
  977.     while read MODULE; do
  978.         OPTIONS=$(get_module_options "$MODULE" "$2")
  979.         if test "$OPTIONS"; then
  980.             echo
  981.             echo "Options for module <$MODULE>:"
  982.             echo
  983.             print_options "$OPTIONS" '  '
  984.         fi
  985.     done <<< "$1"
  986. }
  987.  
  988. # Get all modules options with specified family name.
  989. # Note: All lines are prefix with "!" character.
  990. #
  991. # $1: module name list (one per line)
  992. # $2: option family name (string, example:UPLOAD)
  993. get_all_modules_options() {
  994.     while read MODULE; do
  995.         get_module_options "$MODULE" "$2" | while read OPTION; do
  996.             if test "$OPTION"; then echo "!$OPTION"; fi
  997.         done
  998.     done <<< "$1"
  999. }
  1000.  
  1001. # Get module name from URL link
  1002. #
  1003. # $1: url
  1004. # $2: module name list (one per line)
  1005. get_module() {
  1006.     while read MODULE; do
  1007.         local M=$(uppercase <<< "$MODULE")
  1008.         local VAR="MODULE_${M}_REGEXP_URL"
  1009.         if match "${!VAR}" "$1"; then
  1010.             echo $MODULE
  1011.             break;
  1012.         fi
  1013.     done <<< "$2"
  1014.     return 0
  1015. }
  1016.  
  1017. # Straighforward options and arguments processing using getopt style
  1018. # $1: program name (used for error message printing)
  1019. # $2: command-line arguments list
  1020. #
  1021. # Example:
  1022. # $ set -- -a user:password -q arg1 arg2
  1023. # $ eval "$(process_options module "
  1024. #           AUTH,a:,auth:,USER:PASSWORD,Help for auth
  1025. #           QUIET,q,quiet,,Help for quiet" "$@")"
  1026. # $ echo "$AUTH / $QUIET / $1 / $2"
  1027. # user:password / 1 / arg1 / arg2
  1028. process_options() {
  1029.     local NAME=$1
  1030.     local OPTIONS=$2
  1031.     shift 2
  1032.  
  1033.     # Strip spaces in options
  1034.     OPTIONS=$(echo "$OPTIONS" | strip | drop_empty_lines)
  1035.  
  1036.     # Function is called from a module which has no option
  1037.     test -z "$OPTIONS" && return 0
  1038.  
  1039.     while read VAR; do
  1040.         if test "${VAR:0:1}" = "!"; then
  1041.             VAR=${VAR:1}
  1042.         fi
  1043.         # faster than `cut -d',' -f1`
  1044.         unset "${VAR%%,*}"
  1045.     done <<< "$OPTIONS"
  1046.  
  1047.     local SHORT_OPTS=$(echo "$OPTIONS" | cut -d',' -f2)
  1048.     local LONG_OPTS=$(echo "$OPTIONS" | cut -d',' -f3)
  1049.     local ARGUMENTS="$(getopt -o "$SHORT_OPTS" --long "$LONG_OPTS" -n "$NAME" -- "$@")"
  1050.  
  1051.     # To correctly process whitespace and quotes.
  1052.     eval set -- "$ARGUMENTS"
  1053.  
  1054.     local -a UNUSED_OPTIONS=()
  1055.     while true; do
  1056.         test "$1" = "--" && { shift; break; }
  1057.         while read OPTION; do
  1058.             IFS="," read VAR SHORT LONG VALUE HELP <<< "$OPTION"
  1059.             UNUSED=0
  1060.             if test "${VAR:0:1}" = "!"; then
  1061.                 UNUSED=1
  1062.                 VAR=${VAR:1}
  1063.             fi
  1064.             if test "$1" = "-${SHORT%:}" -o "$1" = "--${LONG%:}"; then
  1065.                 if test "${SHORT:${#SHORT}-1:1}" = ":" -o \
  1066.                         "${LONG:${#LONG}-1:1}" = ":"; then
  1067.  
  1068.                     test -z "$VALUE" && \
  1069.                         stderr "process_options ($VAR): VALUE should not be empty!"
  1070.  
  1071.                     if test "$UNUSED" = 0; then
  1072.                         echo "$VAR=$(quote "$2")"
  1073.                     else
  1074.                         if test "${1:0:2}" = "--"; then
  1075.                             UNUSED_OPTIONS=("${UNUSED_OPTIONS[@]}" "$1=$2")
  1076.                         else
  1077.                             UNUSED_OPTIONS=("${UNUSED_OPTIONS[@]}" "$1" "$2")
  1078.                         fi
  1079.                     fi
  1080.                     shift
  1081.                 else
  1082.                     if test "$UNUSED" = 0; then
  1083.                         echo "$VAR=1"
  1084.                     else
  1085.                         UNUSED_OPTIONS=("${UNUSED_OPTIONS[@]}" "$1")
  1086.                     fi
  1087.                 fi
  1088.                 break
  1089.             fi
  1090.         done <<< "$OPTIONS"
  1091.         shift
  1092.     done
  1093.     echo "$(declare -p UNUSED_OPTIONS)"
  1094.     echo "set -- $(quote "$@")"
  1095. }
  1096.  
  1097. # Get module list according to capability
  1098. # Note1: use global variable LIBDIR
  1099. # Note2: VERBOSE (log_debug) not initialised yet
  1100. #
  1101. # $1: keyword to grep (must not contain '|' char)
  1102. # stdout: return module list (one name per line)
  1103. grep_list_modules() {
  1104.    local CONFIG="$LIBDIR/modules/config"
  1105.  
  1106.    if [ ! -f "$CONFIG" ]; then
  1107.        stderr "can't find config file"
  1108.        return $ERR_SYSTEM
  1109.    fi
  1110.  
  1111.    sed -ne "/^[^#].*|[[:space:]]*$1[[:space:]]*|/p" $CONFIG | \
  1112.        cut -d'|' -f1 | strip
  1113. }
  1114.  
  1115. # $1: section name in ini-style file ("General" will be considered too)
  1116. # $2: command-line arguments list
  1117. # Note: VERBOSE (log_debug) not initialised yet
  1118. process_configfile_options() {
  1119.     local CONFIG OPTIONS SECTION LINE NAME VALUE OPTION
  1120.  
  1121.     CONFIG="$HOME/.config/plowshare/plowshare.conf"
  1122.     test ! -f "$CONFIG" && CONFIG="/etc/plowshare.conf"
  1123.     test -f "$CONFIG" || return 0
  1124.  
  1125.     # Strip spaces in options
  1126.     OPTIONS=$(echo "$2" | strip | drop_empty_lines)
  1127.  
  1128.     SECTION=$(sed -ne "/\[$1\]/,/^\[/p" -ne "/\[General\]/,/^\[/p" "$CONFIG" | \
  1129.               sed -e '/^\(#\|\[\|[[:space:]]*$\)/d')
  1130.  
  1131.     if [ -n "$SECTION" -a -n "$OPTIONS" ]; then
  1132.         while read LINE; do
  1133.             NAME=$(echo "${LINE%%=*}" | strip)
  1134.             VALUE=$(echo "${LINE#*=}" | strip)
  1135.  
  1136.             # Look for optional double quote (protect leading/trailing spaces)
  1137.             if [ "${VALUE:0:1}" = '"' -a "${VALUE:(-1):1}" = '"' ]; then
  1138.                 VALUE="${VALUE%?}"
  1139.                 VALUE="${VALUE:1}"
  1140.             fi
  1141.  
  1142.             # Look for 'long_name' in options list
  1143.             OPTION=$(echo "$OPTIONS" | grep ",${NAME}:\?," | sed '1q') || true
  1144.             if [ -n "$OPTION" ]; then
  1145.                 local VAR="${OPTION%%,*}"
  1146.                 eval "$VAR=$(quote "$VALUE")"
  1147.             fi
  1148.         done <<< "$SECTION"
  1149.     fi
  1150. }
  1151.  
  1152. # $1: section name in ini-style file ("General" will be considered too)
  1153. # $2: module name
  1154. # $3: option family name (string, example:DOWNLOAD)
  1155. process_configfile_module_options() {
  1156.     local CONFIG OPTIONS SECTION OPTION LINE VALUE
  1157.  
  1158.     CONFIG="$HOME/.config/plowshare/plowshare.conf"
  1159.     test ! -f "$CONFIG" && CONFIG="/etc/plowshare.conf"
  1160.     test -f "$CONFIG" || return 0
  1161.  
  1162.     log_report "use $CONFIG"
  1163.  
  1164.     # Strip spaces in options
  1165.     OPTIONS=$(get_module_options "$2" "$3" | strip | drop_empty_lines)
  1166.  
  1167.     SECTION=$(sed -ne "/\[$1\]/,/^\[/p" -ne "/\[General\]/,/^\[/p" "$CONFIG" | \
  1168.               sed -e '/^\(#\|\[\|[[:space:]]*$\)/d')
  1169.  
  1170.     if [ -n "$SECTION" -a -n "$OPTIONS" ]; then
  1171.         local M=$(echo "$2" | lowercase)
  1172.  
  1173.         # For example:
  1174.         # AUTH,a:,auth:,USER:PASSWORD,Free or Premium account"
  1175.         while read OPTION; do
  1176.             IFS="," read VAR SHORT LONG VALUE_HELP <<< "$OPTION"
  1177.             SHORT=$(sed -e 's/:$//' <<< "$SHORT")
  1178.             LONG=$(sed -e 's/:$//' <<< "$LONG")
  1179.  
  1180.             # Look for 'module/option_name' (short or long) in section list
  1181.             LINE=$(echo "$SECTION" | grep "^$M/\($SHORT\|$LONG\)[[:space:]]*=" | sed -n '$p') || true
  1182.             if [ -n "$LINE" ]; then
  1183.                 VALUE=$(echo "${LINE#*=}" | strip)
  1184.  
  1185.                 # Look for optional double quote (protect leading/trailing spaces)
  1186.                 if [ "${VALUE:0:1}" = '"' -a "${VALUE:(-1):1}" = '"' ]; then
  1187.                     VALUE="${VALUE%?}"
  1188.                     VALUE="${VALUE:1}"
  1189.                 fi
  1190.  
  1191.                 eval "$VAR=$(quote "$VALUE")"
  1192.                 log_debug "$M: take --$LONG option from configuration file"
  1193.             fi
  1194.         done <<< "$OPTIONS"
  1195.     fi
  1196. }
  1197.  
  1198. # Get system information
  1199. log_report_info() {
  1200.     if test $(verbose_level) -ge 4; then
  1201.         log_report '=== SYSTEM INFO BEGIN ==='
  1202.         log_report "[mach] `uname -a`"
  1203.         log_report "[bash] `echo $BASH_VERSION`"
  1204.         if check_exec 'curl'; then
  1205.             log_report "[curl] `$(type -P curl) --version | sed 1q`"
  1206.         else
  1207.             log_report '[curl] not found!'
  1208.         fi
  1209.         log_report '=== SYSTEM INFO END ==='
  1210.     fi
  1211. }
  1212.  
  1213. ## ----------------------------------------------------------------------------
  1214.  
  1215. ##
  1216. ## Private ('static') functions
  1217. ## Can be called from this script only.
  1218. ##
  1219.  
  1220. verbose_level() {
  1221.     echo ${VERBOSE:-0}
  1222. }
  1223.  
  1224. stderr() {
  1225.     echo "$@" >&2;
  1226. }
  1227.  
  1228. quote() {
  1229.     for ARG in "$@"; do
  1230.         echo -n "$(declare -p ARG | sed "s/^declare -- ARG=//") "
  1231.     done | sed "s/ $//"
  1232. }
  1233.  
  1234. # Delete blank lines
  1235. # stdin: input (multiline) string
  1236. # stdout: result string
  1237. drop_empty_lines() {
  1238.     sed '/^[    ]*$/d'
  1239. }
  1240.  
  1241. # Look for a configuration module variable
  1242. # Example: MODULE_ZSHARE_DOWNLOAD_OPTIONS (result can be multiline)
  1243. # $1: module name
  1244. # $2: option family name (string, example:UPLOAD)
  1245. # stdout: options list (one per line)
  1246. get_module_options() {
  1247.     local MODULE=$(uppercase <<< "$1")
  1248.     local VAR="MODULE_${MODULE}_${2}_OPTIONS"
  1249.     echo "${!VAR}"
  1250. }
  1251.  
  1252. # Example: 12345 => "3h25m45s"
  1253. # $1: duration (integer)
  1254. splitseconds() {
  1255.     local DIV_H=$(( $1 / 3600 ))
  1256.     local DIV_M=$(( ($1 % 3600) / 60 ))
  1257.     local DIV_S=$(( $1 % 60 ))
  1258.  
  1259.     [ "$DIV_H" -eq 0 ] || echo -n "${DIV_H}h"
  1260.     [ "$DIV_M" -eq 0 ] || echo -n "${DIV_M}m"
  1261.     [ "$DIV_S" -eq 0 ] && echo || echo "${DIV_S}s"
  1262. }
  1263.  
  1264. # called by wait
  1265. timeout_update() {
  1266.     local WAIT=$1
  1267.     test -z "$PS_TIMEOUT" && return
  1268.     log_notice "Time left to timeout: $PS_TIMEOUT secs"
  1269.     if [[ "$PS_TIMEOUT" -lt "$WAIT" ]]; then
  1270.         log_debug "timeout reached (asked $WAIT secs to wait, but remaining time is $PS_TIMEOUT)"
  1271.         return $ERR_MAX_WAIT_REACHED
  1272.     fi
  1273.     (( PS_TIMEOUT -= WAIT ))
  1274. }
  1275.  
  1276. # Check if a string ($2) matches a regexp ($1)
  1277. # and return the content inside the first parentheses.
  1278. # This is case sensitive.
  1279. match_capture() {
  1280.     if [[ $2 =~ $1 ]]; then
  1281.         echo ${BASH_REMATCH[1]}
  1282.     else
  1283.         echo $2
  1284.     fi
  1285. }
RAW Paste Data
Pastebin PRO Autumn Special!
Get 40% OFF on Pastebin PRO accounts!
Top