Fossil

Check-in [1e81049063]
Login

Check-in [1e81049063]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Add the "fossil chat send" command.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 1e81049063cc8833cb2f73e2b9ef4a1fc4cf0e22938a3c1638d35b7ff1393916
User & Date: drh 2021-01-05 01:23:02.535
Context
2021-01-05
01:26
The "fossil chat send" command should throw an error if there are any unrecognized command-line options. ... (check-in: 904a5a5612 user: drh tags: trunk)
01:23
Add the "fossil chat send" command. ... (check-in: 1e81049063 user: drh tags: trunk)
2021-01-04
20:10
Add two new small WAV files, perhaps useful as audiable alert sounds, but not yet used for anything. ... (check-in: 2146a13df1 user: drh tags: trunk)
Changes
Unified Diff Ignore Whitespace Patch
Changes to src/chat.c.
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
  const char *zMsg;
  login_check_credentials();
  if( !g.perm.Chat ) {
    chat_emit_permissions_error(0);
    return;
  }
  chat_create_tables();
  nByte = atoi(PD("file:bytes",0));
  zMsg = PD("msg","");
  db_begin_write();
  chat_purge();
  if( nByte==0 ){
    if( zMsg[0] ){
      db_multi_exec(
        "INSERT INTO chat(mtime,lmtime,xfrom,xmsg)"







|







262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
  const char *zMsg;
  login_check_credentials();
  if( !g.perm.Chat ) {
    chat_emit_permissions_error(0);
    return;
  }
  chat_create_tables();
  nByte = atoi(PD("file:bytes","0"));
  zMsg = PD("msg","");
  db_begin_write();
  chat_purge();
  if( nByte==0 ){
    if( zMsg[0] ){
      db_multi_exec(
        "INSERT INTO chat(mtime,lmtime,xfrom,xmsg)"
675
676
677
678
679
680
681
682
683








684






685








686
687
688


689
690
691
692
693
694
695
696
697


698

699





700
701
702
703
704
705
706
707
708
709
710

711

712
713
714
715
716
717
718











719



















































  cgi_set_content_type("audio/wav");
  cgi_set_content(&audio);  
}

/*
** COMMAND: chat
**
** Usage: %fossil chat
**








** Bring up a web-browser window to the chatroom of the default






** remote Fossil repository.








*/
void chat_command(void){
  const char *zUrl;


  const char *zBrowser;
  char *zCmd;
  db_find_and_open_repository(0,0);
  if( g.argc!=2 ){
    usage("");
  }
  zUrl = db_get("last-sync-url",0);
  if( zUrl==0 ){
    fossil_fatal("no \"remote\" repository defined");


  }

  url_parse(zUrl, 0);





  if( g.url.port==g.url.dfltPort ){
    zUrl = mprintf(
      "%s://%T%T",
      g.url.protocol, g.url.name, g.url.path
    );
  }else{
    zUrl = mprintf(
      "%s://%T:%d%T",
      g.url.protocol, g.url.name, g.url.port, g.url.path
    );
  }

  zBrowser = fossil_web_browser();

  if( zBrowser==0 ) return;
#ifdef _WIN32
  zCmd = mprintf("%s %s/chat?cli &", zBrowser, zUrl);
#else
  zCmd = mprintf("%s \"%s/chat?cli\" &", zBrowser, zUrl);
#endif
  fossil_system(zCmd);











}


























































|

>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>


|
>
>
|
|

|
|
|
|
|
|
>
>
|
>
|
>
>
>
>
>


|
|



|
|


>
|
>
|

|

|

|
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
  cgi_set_content_type("audio/wav");
  cgi_set_content(&audio);  
}

/*
** COMMAND: chat
**
** Usage: %fossil chat [SUBCOMMAND] [--remote URL] [ARGS...]
**
** This command performs actions associated with the /chat instance
** on the default remote Fossil repository (the Fossil repository whose
** URL shows when you run the "fossil remote" command) or to the URL
** specified by the --remote option.  If there is no default remote
** Fossil repository and the --remote option is omitted, then this
** command fails with an error.
**
** When there is no SUBCOMMAND (when this command is simply "fossil chat")
** the response is to bring up a web-browser window to the chatroom
** on the default system web-browser.  You can accomplish the same by
** typing the appropriate URL into the web-browser yourself.  This
** command is merely a convenience for command-line oriented peope.
**
** The following subcommands are supported:
**
** > fossil chat send [ARGUMENTS]
**
**      This command sends a new message to the chatroom.  The message
**      to be sent is determined by arguments as follows:
**
**        -m|--message TEXT      Text of the chat message
**        -f|--file FILENAME     File to attach to the message
**
** Additional subcommands may be added in the future.
*/
void chat_command(void){
  const char *zUrl = find_option("remote",0,1);
  int urlFlags = 0;
  int isDefaultUrl = 0;
  int i;

  db_find_and_open_repository(0,0);
  if( zUrl ){
    urlFlags = URL_PROMPT_PW;
  }else{
    zUrl = db_get("last-sync-url",0);
    if( zUrl==0 ){
      fossil_fatal("no \"remote\" repository defined");
    }else{
      isDefaultUrl = 1;
    }
  }
  url_parse(zUrl, urlFlags);
  if( g.url.isFile || g.url.isSsh ){
    fossil_fatal("chat only works for http:// and https:// URLs");
  }
  i = (int)strlen(g.url.path);
  while( i>0 && g.url.path[i-1]=='/' ) i--;
  if( g.url.port==g.url.dfltPort ){
    zUrl = mprintf(
      "%s://%T%.*T",
      g.url.protocol, g.url.name, i, g.url.path
    );
  }else{
    zUrl = mprintf(
      "%s://%T:%d%.*T",
      g.url.protocol, g.url.name, g.url.port, i, g.url.path
    );
  }
  if( g.argc==2 ){
    const char *zBrowser = fossil_web_browser();
    char *zCmd;
    if( zBrowser==0 ) return;
#ifdef _WIN32
    zCmd = mprintf("%s %s/chat?cli &", zBrowser, zUrl);
#else
    zCmd = mprintf("%s \"%s/chat?cli\" &", zBrowser, zUrl);
#endif
    fossil_system(zCmd);
  }else if( strcmp(g.argv[2],"send")==0 ){
    const char *zFilename = find_option("file","r",1);
    const char *zMsg = find_option("message","m",1);
    const int mFlags = HTTP_GENERIC | HTTP_QUIET | HTTP_NOCOMPRESS;
    int i;
    const char *zPw;
    Blob up, down, fcontent;
    char zBoundary[80];
    sqlite3_uint64 r[3];
    if( zFilename==0 && zMsg==0 ){
      fossil_fatal("must have --message or --file or both");
    }
    i = (int)strlen(g.url.path);
    while( i>0 && g.url.path[i-1]=='/' ) i--;
    g.url.path = mprintf("%.*s/chat-send", i, g.url.path);
    blob_init(&up, 0, 0);
    blob_init(&down, 0, 0);
    sqlite3_randomness(sizeof(r),r);
    sqlite3_snprintf(sizeof(zBoundary),zBoundary,
                     "--------%016llu%016llu%016llu", r[0], r[1], r[2]);
    blob_appendf(&up, "%s", zBoundary);
    if( g.url.user && g.url.user[0] ){
      blob_appendf(&up,"\r\nContent-Disposition: form-data; name=\"resid\"\r\n"
                       "\r\n%z\r\n%s", obscure(g.url.user), zBoundary);
    }
    zPw = g.url.passwd;
    if( zPw==0 && isDefaultUrl ) zPw = unobscure(db_get("last-sync-pw", 0));
    if( zPw && zPw[0] ){
      blob_appendf(&up,"\r\nContent-Disposition: form-data; name=\"token\"\r\n"
                       "\r\n%z\r\n%s", obscure(zPw), zBoundary);
    }
    if( zMsg && zMsg[0] ){
      blob_appendf(&up,"\r\nContent-Disposition: form-data; name=\"msg\"\r\n"
                       "\r\n%s\r\n%s", zMsg, zBoundary);
    }
    if( zFilename && blob_read_from_file(&fcontent, zFilename, ExtFILE)>0 ){
      char *zFN = mprintf("%s", file_tail(zFilename));
      int i;
      const char *zMime = mimetype_from_name(zFilename);
      for(i=0; zFN[i]; i++){
        char c = zFN[i];
        if( fossil_isalnum(c) ) continue;
        if( c=='.' ) continue;
        if( c=='-' ) continue;
        zFN[i] = '_';
      }
      blob_appendf(&up,"\r\nContent-Disposition: form-data; name=\"file\";"
                       " filename=\"%s\"\r\n", zFN);
      blob_appendf(&up,"Content-Type: %s\r\n\r\n", zMime);
      blob_append(&up, fcontent.aData, fcontent.nUsed);
      blob_appendf(&up,"\r\n%s", zBoundary);
    }
    blob_append(&up,"--\r\n", 4);
    http_exchange(&up, &down, mFlags, 4, "multipart/form-data");
    blob_reset(&up);
    blob_reset(&down);
  }else if( strcmp(g.argv[2],"url")==0 ){
    /* Undocumented command.  Show the URL to access chat. */
    fossil_print("%s/chat\n", zUrl);
  }else{
    fossil_fatal("no such subcommand \"%s\".  Use --help for help", g.argv[2]);
  }
}
Changes to src/login.c.
1076
1077
1078
1079
1080
1081
1082



















1083
1084
1085
1086
1087
1088
1089
  /* If the request didn't provide a login cookie or the login cookie didn't
  ** match a known valid user, check the HTTP "Authorization" header and
  ** see if those credentials are valid for a known user.
  */
  if( uid==0 && db_get_boolean("http_authentication_ok",0) ){
    uid = login_basic_authentication(zIpAddr);
  }




















  /* If no user found yet, try to log in as "nobody" */
  if( uid==0 ){
    uid = db_int(0, "SELECT uid FROM user WHERE login='nobody'");
    if( uid==0 ){
      /* If there is no user "nobody", then make one up - with no privileges */
      uid = -1;







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
  /* If the request didn't provide a login cookie or the login cookie didn't
  ** match a known valid user, check the HTTP "Authorization" header and
  ** see if those credentials are valid for a known user.
  */
  if( uid==0 && db_get_boolean("http_authentication_ok",0) ){
    uid = login_basic_authentication(zIpAddr);
  }

  /* Check for magic query parameters "resid" (for the username) and
  ** "token" for the password.  Both values (if they exist) will be
  ** obfuscated.
  */
  if( uid==0 ){
    char *zUsr, *zPW;
    if( (zUsr = unobscure(P("resid")))!=0
     && (zPW = unobscure(P("token")))!=0
    ){
      char *zSha1Pw = sha1_shared_secret(zPW, zUsr, 0);
      uid = db_int(0, "SELECT uid FROM user"
                      " WHERE login=%Q"
                      " AND (constant_time_cmp(pw,%Q)=0"
                      "      OR constant_time_cmp(pw,%Q)=0)",
                      zUsr, zSha1Pw, zPW);
      fossil_free(zSha1Pw);
    }
  }

  /* If no user found yet, try to log in as "nobody" */
  if( uid==0 ){
    uid = db_int(0, "SELECT uid FROM user WHERE login='nobody'");
    if( uid==0 ){
      /* If there is no user "nobody", then make one up - with no privileges */
      uid = -1;