diff options
| author | cashmere <cashmere@autistici.org> | 2026-09-04 09:05:24 +0200 |
|---|---|---|
| committer | cashmere <cashmere@autistici.org> | 2026-09-04 09:05:24 +0200 |
| commit | 4f49b16079b27d7ce633619e4cddfdf2bffbc952 (patch) | |
| tree | c296c1e7f3c3e2f6859c6d3a1b1b208779b89679 /src/captcha.c | |
| download | relic-4f49b16079b27d7ce633619e4cddfdf2bffbc952.tar.gz | |
Diffstat (limited to 'src/captcha.c')
| -rw-r--r-- | src/captcha.c | 926 |
1 files changed, 926 insertions, 0 deletions
diff --git a/src/captcha.c b/src/captcha.c new file mode 100644 index 0000000..8e768ce --- /dev/null +++ b/src/captcha.c @@ -0,0 +1,926 @@ +/* +** Copyright (c) 2009 D. Richard Hipp +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the Simplified BSD License (also +** known as the "2-Clause License" or "FreeBSD License".) + +** 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. +** +** Author contact information: +** drh@hwaci.com +** http://www.hwaci.com/drh/ +** +******************************************************************************* +** +** This file contains code to a simple text-based CAPTCHA. Though easily +** defeated by a sophisticated attacker, this CAPTCHA does at least make +** scripting attacks more difficult. +*/ +#include "config.h" +#include <assert.h> +#include "captcha.h" + +#if INTERFACE +#define CAPTCHA 2 /* Which captcha rendering to use */ +#endif + +/* +** Convert a hex digit into a value between 0 and 15 +*/ +int hex_digit_value(char c){ + if( c>='0' && c<='9' ){ + return c - '0'; + }else if( c>='a' && c<='f' ){ + return c - 'a' + 10; + }else if( c>='A' && c<='F' ){ + return c - 'A' + 10; + }else{ + return 0; + } +} + +#if CAPTCHA==1 +/* +** A 4x6 pixel bitmap font for hexadecimal digits +*/ +static const unsigned int aFont1[] = { + 0x699996, + 0x262227, + 0x69124f, + 0xf16196, + 0x26af22, + 0xf8e196, + 0x68e996, + 0xf12244, + 0x696996, + 0x699716, + 0x699f99, + 0xe9e99e, + 0x698896, + 0xe9999e, + 0xf8e88f, + 0xf8e888, +}; + +/* +** Render an 8-character hexadecimal string as ascii art. +** Space to hold the result is obtained from malloc() and should be freed +** by the caller. +*/ +char *captcha_render(const char *zPw){ + char *z = fossil_malloc( 9*12*3*strlen(zPw) + 8 ); + int i, j, k, m; + + k = 0; + for(i=0; i<6; i++){ + for(j=0; zPw[j]; j++){ + unsigned char v = hex_digit_value(zPw[j]); + v = (aFont1[v] >> ((5-i)*4)) & 0xf; + for(m=8; m>=1; m = m>>1){ + if( v & m ){ + z[k++] = 0xe2; + z[k++] = 0x96; + z[k++] = 0x88; + z[k++] = 0xe2; + z[k++] = 0x96; + z[k++] = 0x88; + }else{ + z[k++] = ' '; + z[k++] = ' '; + } + } + z[k++] = ' '; + z[k++] = ' '; + } + z[k++] = '\n'; + } + z[k] = 0; + return z; +} +#endif /* CAPTCHA==1 */ + +#if CAPTCHA==2 +/* +** A 5x7 pixel bitmap font for hexadecimal digits +*/ +static const unsigned char aFont2[] = { + /* 0 */ 0x0e, 0x13, 0x15, 0x19, 0x11, 0x11, 0x0e, + /* 1 */ 0x02, 0x06, 0x0A, 0x02, 0x02, 0x02, 0x02, + /* 2 */ 0x0e, 0x11, 0x01, 0x02, 0x04, 0x08, 0x1f, + /* 3 */ 0x0e, 0x11, 0x01, 0x06, 0x01, 0x11, 0x0e, + /* 4 */ 0x02, 0x06, 0x0A, 0x12, 0x1f, 0x02, 0x02, + /* 5 */ 0x1f, 0x10, 0x1e, 0x01, 0x01, 0x11, 0x0e, + /* 6 */ 0x0e, 0x11, 0x10, 0x1e, 0x11, 0x11, 0x0e, + /* 7 */ 0x1f, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08, + /* 8 */ 0x0e, 0x11, 0x11, 0x0e, 0x11, 0x11, 0x0e, + /* 9 */ 0x0e, 0x11, 0x11, 0x0f, 0x01, 0x11, 0x0e, + /* A */ 0x0e, 0x11, 0x11, 0x11, 0x1f, 0x11, 0x11, + /* B */ 0x1e, 0x11, 0x11, 0x1e, 0x11, 0x11, 0x1e, + /* C */ 0x0e, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0e, + /* D */ 0x1c, 0x12, 0x11, 0x11, 0x11, 0x12, 0x1c, + /* E */ 0x1f, 0x10, 0x10, 0x1c, 0x10, 0x10, 0x1f, + /* F */ 0x1f, 0x10, 0x10, 0x1e, 0x10, 0x10, 0x10, +}; + +/* +** Render an 8-character hexadecimal string as ascii art. +** Space to hold the result is obtained from malloc() and should be freed +** by the caller. +*/ +char *captcha_render(const char *zPw){ + char *z = fossil_malloc( 224*strlen(zPw) + 9 ); + int i, j, k, m; + + k = 0; + for(i=0; i<7; i++){ + for(j=0; zPw[j]; j++){ + unsigned char v = hex_digit_value(zPw[j]); + v = aFont2[v*7+i]; + for(m=16; m>=1; m = m>>1){ + if( v & m ){ + z[k++] = 0xe2; + z[k++] = 0x96; + z[k++] = 0x88; + z[k++] = 0xe2; + z[k++] = 0x96; + z[k++] = 0x88; + }else{ + z[k++] = 0xe2; + z[k++] = 0x96; + z[k++] = 0x91; + z[k++] = 0xe2; + z[k++] = 0x96; + z[k++] = 0x91; + } + } + z[k++] = ' '; + z[k++] = ' '; + } + z[k++] = '\n'; + } + z[k] = 0; + return z; +} +#endif /* CAPTCHA==2 */ + + +#if CAPTCHA==3 +static const char *const azFont3[] = { + /* 0 */ + " __ ", + " / \\ ", + "| () |", + " \\__/ ", + + /* 1 */ + " _ ", + "/ |", + "| |", + "|_|", + + /* 2 */ + " ___ ", + "|_ )", + " / / ", + "/___|", + + /* 3 */ + " ____", + "|__ /", + " |_ \\", + "|___/", + + /* 4 */ + " _ _ ", + "| | | ", + "|_ _|", + " |_| ", + + /* 5 */ + " ___ ", + "| __|", + "|__ \\", + "|___/", + + /* 6 */ + " __ ", + " / / ", + "/ _ \\", + "\\___/", + + /* 7 */ + " ____ ", + "|__ |", + " / / ", + " /_/ ", + + /* 8 */ + " ___ ", + "( _ )", + "/ _ \\", + "\\___/", + + /* 9 */ + " ___ ", + "/ _ \\", + "\\_, /", + " /_/ ", + + /* A */ + " ", + " /\\ ", + " / \\ ", + "/_/\\_\\", + + /* B */ + " ___ ", + "| _ )", + "| _ \\", + "|___/", + + /* C */ + " ___ ", + " / __|", + "| (__ ", + " \\___|", + + /* D */ + " ___ ", + "| \\ ", + "| |) |", + "|___/ ", + + /* E */ + " ___ ", + "| __|", + "| _| ", + "|___|", + + /* F */ + " ___ ", + "| __|", + "| _| ", + "|_| ", +}; + +/* +** Render an 8-digit hexadecimal string as ascii arg. +** Space to hold the result is obtained from malloc() and should be freed +** by the caller. +*/ +char *captcha_render(const char *zPw){ + char *z = fossil_malloc( 7*4*strlen(zPw) + 5 ); + int i, j, k, m; + const char *zChar; + + k = 0; + for(i=0; i<4; i++){ + for(j=0; zPw[j]; j++){ + unsigned char v = hex_digit_value(zPw[j]); + zChar = azFont3[4*v + i]; + for(m=0; zChar[m]; m++){ + z[k++] = zChar[m]; + } + } + z[k++] = '\n'; + } + z[k] = 0; + return z; +} +#endif /* CAPTCHA==3 */ + +#if CAPTCHA==4 +static const char *const azFont4[] = { + /* 0 */ + " ___ ", + " / _ \\ ", + "| | | |", + "| | | |", + "| |_| |", + " \\___/ ", + + /* 1 */ + " __ ", + "/_ |", + " | |", + " | |", + " | |", + " |_|", + + /* 2 */ + " ___ ", + "|__ \\ ", + " ) |", + " / / ", + " / /_ ", + "|____|", + + /* 3 */ + " ____ ", + "|___ \\ ", + " __) |", + " |__ < ", + " ___) |", + "|____/ ", + + /* 4 */ + " _ _ ", + "| || | ", + "| || |_ ", + "|__ _|", + " | | ", + " |_| ", + + /* 5 */ + " _____ ", + "| ____|", + "| |__ ", + "|___ \\ ", + " ___) |", + "|____/ ", + + /* 6 */ + " __ ", + " / / ", + " / /_ ", + "| '_ \\ ", + "| (_) |", + " \\___/ ", + + /* 7 */ + " ______ ", + "|____ |", + " / / ", + " / / ", + " / / ", + " /_/ ", + + /* 8 */ + " ___ ", + " / _ \\ ", + "| (_) |", + " > _ < ", + "| (_) |", + " \\___/ ", + + /* 9 */ + " ___ ", + " / _ \\ ", + "| (_) |", + " \\__, |", + " / / ", + " /_/ ", + + /* A */ + " ", + " /\\ ", + " / \\ ", + " / /\\ \\ ", + " / ____ \\ ", + "/_/ \\_\\", + + /* B */ + " ____ ", + "| _ \\ ", + "| |_) |", + "| _ < ", + "| |_) |", + "|____/ ", + + /* C */ + " _____ ", + " / ____|", + "| | ", + "| | ", + "| |____ ", + " \\_____|", + + /* D */ + " _____ ", + "| __ \\ ", + "| | | |", + "| | | |", + "| |__| |", + "|_____/ ", + + /* E */ + " ______ ", + "| ____|", + "| |__ ", + "| __| ", + "| |____ ", + "|______|", + + /* F */ + " ______ ", + "| ____|", + "| |__ ", + "| __| ", + "| | ", + "|_| ", +}; + +/* +** Render an 8-digit hexadecimal string as ascii arg. +** Space to hold the result is obtained from malloc() and should be freed +** by the caller. +*/ +char *captcha_render(const char *zPw){ + char *z = fossil_malloc( 10*6*strlen(zPw) + 7 ); + int i, j, k, m; + const char *zChar; + unsigned char x; + int y; + + k = 0; + for(i=0; i<6; i++){ + x = 0; + for(j=0; zPw[j]; j++){ + unsigned char v = hex_digit_value(zPw[j]); + x = (x<<4) + v; + switch( x ){ + case 0x7a: + case 0xfa: + y = 3; + break; + case 0x47: + y = 2; + break; + case 0xf6: + case 0xa9: + case 0xa4: + case 0xa1: + case 0x9a: + case 0x76: + case 0x61: + case 0x67: + case 0x69: + case 0x41: + case 0x42: + case 0x43: + case 0x4a: + y = 1; + break; + default: + y = 0; + break; + } + zChar = azFont4[6*v + i]; + while( y && zChar[0]==' ' ){ y--; zChar++; } + while( y && z[k-1]==' ' ){ y--; k--; } + for(m=0; zChar[m]; m++){ + z[k++] = zChar[m]; + } + } + z[k++] = '\n'; + } + z[k] = 0; + return z; +} +#endif /* CAPTCHA==4 */ + +/* +** COMMAND: test-captcha +** +** Render an ASCII-art captcha for numbers given on the command line. +*/ +void test_captcha(void){ + int i; + sqlite3_uint64 v; + char *z; + + for(i=2; i<g.argc; i++){ + char zHex[30]; + v = (sqlite3_uint64)strtoll(g.argv[i],0,0); + sqlite3_snprintf(sizeof(zHex), zHex, "%llx", v); + z = captcha_render(zHex); + fossil_print("%s:\n%s", zHex, z); + free(z); + } +} + +/* +** Compute a seed value for a captcha. The seed is public and is sent +** as a hidden parameter with the page that contains the captcha. Knowledge +** of the seed is insufficient for determining the captcha without additional +** information held only on the server and never revealed. +*/ +unsigned int captcha_seed(void){ + unsigned int x; + sqlite3_randomness(sizeof(x), &x); + x &= 0x7fffffff; + return x; +} + +/* The SQL that will rotate the captcha-secret. */ +static const char captchaSecretRotationSql[] = +@ SAVEPOINT rotate; +@ DELETE FROM config +@ WHERE name GLOB 'captcha-secret-*' +@ AND mtime<unixepoch('now','-6 hours'); +@ UPDATE config +@ SET name=format('captcha-secret-%%d',substr(name,16)+1) +@ WHERE name GLOB 'captcha-secret-*'; +@ UPDATE config +@ SET name='captcha-secret-1', mtime=unixepoch() +@ WHERE name='captcha-secret'; +@ REPLACE INTO config(name,value,mtime) +@ VALUES('captcha-secret',%Q,unixepoch()); +@ RELEASE rotate; +; + + +/* +** Create a new random captcha-secret. Rotate the old one into +** the captcha-secret-N backups. Purge captcha-secret-N backups +** older than 6 hours. +** +** Do this on the current database and in all other databases of +** the same login group. +*/ +void captcha_secret_rotate(void){ + char *zNew = db_text(0, "SELECT lower(hex(randomblob(20)))"); + char *zSql = mprintf(captchaSecretRotationSql/*works-like:"%Q"*/, zNew); + char *zErrs = 0; + fossil_free(zNew); + db_unprotect(PROTECT_CONFIG); + db_begin_transaction(); + sqlite3_exec(g.db, zSql, 0, 0, &zErrs); + db_protect_pop(); + if( zErrs && zErrs[0] ){ + db_rollback_transaction(); + fossil_fatal("Unable to rotate captcha-secret\n%s\nERROR: %s\n", + zSql, zErrs); + } + db_end_transaction(0); + login_group_sql(zSql, "", "", &zErrs); + if( zErrs ){ + sqlite3_free(zErrs); /* Silently ignore errors on other repos */ + } + fossil_free(zSql); +} + +/* +** Return the value of the N-th more recent captcha-secret. The +** most recent captcha-secret is 0. Others are prior captcha-secrets +** that have expired, but are retained for a limited period of time +** so that pending anonymous login cookies and/or captcha dialogs +** don't malfunction when the captcha-secret changes. +** +** Clients should start by using the 0-th captcha-secret. Only if +** that one does not work should they advance to 1 and 2 and so forth, +** until this routine returns a NULL pointer. +** +** The value returned is a string obtained from fossil_malloc() and +** should be freed by the caller. +** +** The 0-th captcha secret is the value of Config.Name='captcha-secret'. +** For N>0, the value is in Config.Name='captcha-secret-$N'. +*/ +char *captcha_secret(int N){ + if( N==0 ){ + return db_text(0, "SELECT value FROM config WHERE name='captcha-secret'"); + }else{ + return db_text(0, + "SELECT value FROM config" + " WHERE name='captcha-secret-%d'" + " AND mtime>unixepoch('now','-6 hours')", N); + } +} + +/* +** Translate a captcha seed value into the captcha password string. +** The returned string is static and overwritten on each call to +** this function. +** +** Use the N-th captcha secret to compute the password. When N==0, +** a valid password is always returned. A new captcha-secret will +** be created if necessary. But for N>0, the return value might +** be NULL to indicate that there is no N-th captcha-secret. +*/ +const char *captcha_decode(unsigned int seed, int N){ + char *zSecret; + const char *z; + Blob b; + static char zRes[20]; + + zSecret = captcha_secret(N); + if( zSecret==0 ){ + if( N>0 ) return 0; + db_unprotect(PROTECT_CONFIG); + db_multi_exec( + "REPLACE INTO config(name,value)" + " VALUES('captcha-secret', lower(hex(randomblob(20))));" + ); + db_protect_pop(); + zSecret = captcha_secret(0); + assert( zSecret!=0 ); + } + blob_init(&b, 0, 0); + blob_appendf(&b, "%s-%x", zSecret, seed); + sha1sum_blob(&b, &b); + z = blob_buffer(&b); + memcpy(zRes, z, 8); + zRes[8] = 0; + fossil_free(zSecret); + return zRes; +} + +/* +** Return true if a CAPTCHA is required for editing wiki or tickets or for +** adding attachments. +** +** A CAPTCHA is required in those cases if the user is not logged in (if they +** are user "nobody") and if the "require-captcha" setting is true. The +** "require-captcha" setting is controlled on the Admin/Access page. It +** defaults to true. +*/ +int captcha_needed(void){ + return login_is_nobody() && db_get_boolean("require-captcha", 1); +} + +/* +** If a captcha is required but the correct captcha code is not supplied +** in the query parameters, then return false (0). +** +** If no captcha is required or if the correct captcha is supplied, return +** true (non-zero). +** +** The query parameters examined are "captchaseed" for the seed value and +** "captcha" for text that the user types in response to the captcha prompt. +*/ +int captcha_is_correct(int bAlwaysNeeded){ + const char *zSeed; + const char *zEntered; + const char *zDecode; + char z[30]; + int i; + int n = 0; + if( !bAlwaysNeeded && !captcha_needed() ){ + return 1; /* No captcha needed */ + } + zSeed = P("captchaseed"); + if( zSeed==0 ) return 0; + zEntered = P("captcha"); + if( zEntered==0 || strlen(zEntered)!=8 ) return 0; + do{ + zDecode = captcha_decode((unsigned int)atoi(zSeed), n++); + if( zDecode==0 ) return 0; + assert( strlen(zDecode)==8 ); + for(i=0; i<8; i++){ + char c = zEntered[i]; + if( c>='A' && c<='F' ) c += 'a' - 'A'; + if( c=='O' ) c = '0'; + z[i] = c; + } + }while( strncmp(zDecode,z,8)!=0 ); + return 1; +} + +/* +** Generate a captcha display together with the necessary hidden parameter +** for the seed and the entry box into which the user will type the text of +** the captcha. This is typically done at the very bottom of a form. +** +** This routine is a no-op if no captcha is required. +** +** Flag values: +** +** 0x01 Show the "Submit" button in the form. +** 0x02 Always generate the captcha, even if not required +*/ +void captcha_generate(int mFlags){ + unsigned int uSeed; + const char *zDecoded; + char *zCaptcha; + + if( !captcha_needed() && (mFlags & 0x02)==0 ) return; + uSeed = captcha_seed(); + zDecoded = captcha_decode(uSeed, 0); + zCaptcha = captcha_render(zDecoded); + @ <div class="captcha"><table class="captcha"><tr><td><pre class="captcha"> + @ %h(zCaptcha) + @ </pre> + @ Enter security code shown above: + @ <input type="hidden" name="captchaseed" value="%u(uSeed)"> + @ <input type="text" name="captcha" size="8" autofocus> + if( mFlags & 0x01 ){ + @ <input type="submit" value="Submit"> + } + @ <br/>\ + captcha_speakit_button(uSeed, 0); + @ </td></tr></table></div> +} + +/* +** Add a "Speak the captcha" button. +*/ +void captcha_speakit_button(unsigned int uSeed, const char *zMsg){ + if( zMsg==0 ) zMsg = "Speak the text"; + @ <input aria-label="%h(zMsg)" type="button" value="%h(zMsg)" \ + @ id="speakthetext"> + @ <script nonce="%h(style_nonce())">/* captcha_speakit_button() */ + @ document.getElementById("speakthetext").onclick = function(){ + @ var audio = window.fossilAudioCaptcha \ + @ || new Audio("%R/captcha-audio/%u(uSeed)"); + @ window.fossilAudioCaptcha = audio; + @ audio.currentTime = 0; + @ audio.play(); + @ } + @ </script> +} + +/* +** WEBPAGE: test-captcha +** +** If the name query parameter is provided, then render the hex value of +** the name using the captcha font. +** +** Otherwise render the captcha screen. The "show-button" parameter causes +** the submit button to be rendered. +*/ +void captcha_test(void){ + const char *zPw = P("name"); + if( zPw==0 || zPw[0]==0 ){ + (void)exclude_spiders(1); + @ <hr><p>The captcha is shown above. Add a name=HEX query parameter + @ to see how HEX would be rendered in the current captcha font. + @ <h2>Debug/Testing Values:</h2> + @ <ul> + @ <li> g.isRobot = %d(g.isRobot) + @ <li> g.zLogin = %h(g.zLogin) + @ <li> login_cookie_welformed() = %d(login_cookie_wellformed()) + @ <li> captcha_is_correct(1) = %d(captcha_is_correct(1)). + @ </ul> + style_finish_page(); + }else{ + style_set_current_feature("test"); + style_header("Captcha Test"); + @ <pre class="captcha"> + @ %s(captcha_render(zPw)) + @ </pre> + style_finish_page(); + } +} + +/* +** WEBPAGE: honeypot +** This page is a honeypot for spiders and bots. +*/ +void honeypot_page(void){ + (void)exclude_spiders(0); +} + +/* +** Check to see if the current request is coming from an agent that +** self-identifies as a spider. +** +** If the agent does not claim to be a spider or if the user has logged +** in (even as anonymous), then return 0 without doing anything. +** +** But if the user agent does self-identify as a spider and there is +** no login, offer a captcha challenge to allow the user agent to prove +** that he is human and return non-zero. +** +** If the bTest argument is non-zero, then show the captcha regardless of +** how the agent identifies. This is used for testing only. +*/ +int exclude_spiders(int bTest){ + if( !bTest ){ + if( g.zLogin!=0 ) return 0; /* Logged in. Consider them human */ + if( login_cookie_wellformed() ){ + /* Logged into another member of the login group */ + return 0; + } + } + + /* This appears to be a spider. Offer the captcha */ + style_set_current_feature("captcha"); + style_header("Captcha"); + style_submenu_enable(0); + @ <form method='POST' action='%R/ityaar'> + @ <h2>Prove that you are human: + if( bTest ){ + @ <input type="hidden" name="istest" value="1"> + } + captcha_generate(3); + @ </form> + if( !bTest ){ + if( P("fossil-goto")==0 ){ + cgi_set_cookie("fossil-goto", cgi_reconstruct_original_url(), 0, 600); + } + cgi_append_header("X-Robot: 1\r\n"); + style_finish_page(); + } + return 1; +} + +/* +** WEBPAGE: ityaar +** +** This is the action for the form that is the captcha. Not intended +** for external use. "ityaar" is an acronym "I Think You Are A Robot". +** +** If the captcha is correctly solved, then an anonymous login cookie +** is set. Regardless of whether or not the captcha was solved, this +** page always redirects to the fossil-goto cookie. +*/ +void captcha_callback(void){ + int bTest = atoi(PD("istest","0")); + if( captcha_is_correct(1) ){ + if( bTest==0 ){ + if( !login_cookie_wellformed() ){ + /* ^^^^--- Don't overwrite a valid login on another repo! */ + login_set_anon_cookie(0, 0); + } + cgi_append_header("X-Robot: 0\r\n"); + } + login_redirect_to_g(); + }else{ + g.isRobot = 1; + (void)exclude_spiders(bTest); + if( bTest ){ + @ <hr><p>Wrong code. Try again + style_finish_page(); + } + } +} + + +/* +** Generate a WAV file that reads aloud the hex digits given by +** zHex. +*/ +static void captcha_wav(const char *zHex, Blob *pOut){ + int i; + const int szWavHdr = 44; + blob_init(pOut, 0, 0); + blob_resize(pOut, szWavHdr); /* Space for the WAV header */ + pOut->nUsed = szWavHdr; + memset(pOut->aData, 0, szWavHdr); + if( zHex==0 || zHex[0]==0 ) zHex = "0"; + for(i=0; zHex[i]; i++){ + int v = hex_digit_value(zHex[i]); + int sz; + int nData; + const unsigned char *pData; + char zSoundName[50]; + sqlite3_snprintf(sizeof(zSoundName),zSoundName,"sounds/%c.wav", + "0123456789abcdef"[v]); + /* Extra silence in between letters */ + if( i>0 ){ + int nQuiet = 3000; + blob_resize(pOut, pOut->nUsed+nQuiet); + memset(pOut->aData+pOut->nUsed-nQuiet, 0x80, nQuiet); + } + pData = builtin_file(zSoundName, &sz); + nData = sz - szWavHdr; + blob_resize(pOut, pOut->nUsed+nData); + memcpy(pOut->aData+pOut->nUsed-nData, pData+szWavHdr, nData); + if( zHex[i+1]==0 ){ + int len = pOut->nUsed + 36; + memcpy(pOut->aData, pData, szWavHdr); + pOut->aData[4] = (char)(len&0xff); + pOut->aData[5] = (char)((len>>8)&0xff); + pOut->aData[6] = (char)((len>>16)&0xff); + pOut->aData[7] = (char)((len>>24)&0xff); + len = pOut->nUsed; + pOut->aData[40] = (char)(len&0xff); + pOut->aData[41] = (char)((len>>8)&0xff); + pOut->aData[42] = (char)((len>>16)&0xff); + pOut->aData[43] = (char)((len>>24)&0xff); + } + } +} + +/* +** WEBPAGE: /captcha-audio +** +** Return a WAV file that pronounces the digits of the captcha that +** is determined by the seed given in the name= query parameter. +*/ +void captcha_wav_page(void){ + const char *zSeed = PD("name","0"); + const char *zDecode = captcha_decode((unsigned int)atoi(zSeed), 0); + Blob audio; + captcha_wav(zDecode, &audio); + cgi_set_content_type("audio/wav"); + cgi_set_content(&audio); +} + +/* +** WEBPAGE: /test-captcha-audio +** +** Return a WAV file that pronounces the hex digits of the name= +** query parameter. +*/ +void captcha_test_wav_page(void){ + const char *zSeed = P("name"); + Blob audio; + captcha_wav(zSeed, &audio); + cgi_set_content_type("audio/wav"); + cgi_set_content(&audio); +} |
