Fossil SCM

Add the "fossil chat send" command.

drh 2021-01-05 01:23 trunk
Commit 1e81049063cc8833cb2f73e2b9ef4a1fc4cf0e22938a3c1638d35b7ff1393916
2 files changed +120 -24 +19
+120 -24
--- src/chat.c
+++ src/chat.c
@@ -264,11 +264,11 @@
264264
if( !g.perm.Chat ) {
265265
chat_emit_permissions_error(0);
266266
return;
267267
}
268268
chat_create_tables();
269
- nByte = atoi(PD("file:bytes",0));
269
+ nByte = atoi(PD("file:bytes","0"));
270270
zMsg = PD("msg","");
271271
db_begin_write();
272272
chat_purge();
273273
if( nByte==0 ){
274274
if( zMsg[0] ){
@@ -677,43 +677,139 @@
677677
}
678678
679679
/*
680680
** COMMAND: chat
681681
**
682
-** Usage: %fossil chat
682
+** Usage: %fossil chat [SUBCOMMAND] [--remote URL] [ARGS...]
683
+**
684
+** This command performs actions associated with the /chat instance
685
+** on the default remote Fossil repository (the Fossil repository whose
686
+** URL shows when you run the "fossil remote" command) or to the URL
687
+** specified by the --remote option. If there is no default remote
688
+** Fossil repository and the --remote option is omitted, then this
689
+** command fails with an error.
690
+**
691
+** When there is no SUBCOMMAND (when this command is simply "fossil chat")
692
+** the response is to bring up a web-browser window to the chatroom
693
+** on the default system web-browser. You can accomplish the same by
694
+** typing the appropriate URL into the web-browser yourself. This
695
+** command is merely a convenience for command-line oriented peope.
696
+**
697
+** The following subcommands are supported:
698
+**
699
+** > fossil chat send [ARGUMENTS]
700
+**
701
+** This command sends a new message to the chatroom. The message
702
+** to be sent is determined by arguments as follows:
703
+**
704
+** -m|--message TEXT Text of the chat message
705
+** -f|--file FILENAME File to attach to the message
683706
**
684
-** Bring up a web-browser window to the chatroom of the default
685
-** remote Fossil repository.
707
+** Additional subcommands may be added in the future.
686708
*/
687709
void chat_command(void){
688
- const char *zUrl;
689
- const char *zBrowser;
690
- char *zCmd;
710
+ const char *zUrl = find_option("remote",0,1);
711
+ int urlFlags = 0;
712
+ int isDefaultUrl = 0;
713
+ int i;
714
+
691715
db_find_and_open_repository(0,0);
692
- if( g.argc!=2 ){
693
- usage("");
694
- }
695
- zUrl = db_get("last-sync-url",0);
696
- if( zUrl==0 ){
697
- fossil_fatal("no \"remote\" repository defined");
698
- }
699
- url_parse(zUrl, 0);
716
+ if( zUrl ){
717
+ urlFlags = URL_PROMPT_PW;
718
+ }else{
719
+ zUrl = db_get("last-sync-url",0);
720
+ if( zUrl==0 ){
721
+ fossil_fatal("no \"remote\" repository defined");
722
+ }else{
723
+ isDefaultUrl = 1;
724
+ }
725
+ }
726
+ url_parse(zUrl, urlFlags);
727
+ if( g.url.isFile || g.url.isSsh ){
728
+ fossil_fatal("chat only works for http:// and https:// URLs");
729
+ }
730
+ i = (int)strlen(g.url.path);
731
+ while( i>0 && g.url.path[i-1]=='/' ) i--;
700732
if( g.url.port==g.url.dfltPort ){
701733
zUrl = mprintf(
702
- "%s://%T%T",
703
- g.url.protocol, g.url.name, g.url.path
734
+ "%s://%T%.*T",
735
+ g.url.protocol, g.url.name, i, g.url.path
704736
);
705737
}else{
706738
zUrl = mprintf(
707
- "%s://%T:%d%T",
708
- g.url.protocol, g.url.name, g.url.port, g.url.path
739
+ "%s://%T:%d%.*T",
740
+ g.url.protocol, g.url.name, g.url.port, i, g.url.path
709741
);
710742
}
711
- zBrowser = fossil_web_browser();
712
- if( zBrowser==0 ) return;
743
+ if( g.argc==2 ){
744
+ const char *zBrowser = fossil_web_browser();
745
+ char *zCmd;
746
+ if( zBrowser==0 ) return;
713747
#ifdef _WIN32
714
- zCmd = mprintf("%s %s/chat?cli &", zBrowser, zUrl);
748
+ zCmd = mprintf("%s %s/chat?cli &", zBrowser, zUrl);
715749
#else
716
- zCmd = mprintf("%s \"%s/chat?cli\" &", zBrowser, zUrl);
750
+ zCmd = mprintf("%s \"%s/chat?cli\" &", zBrowser, zUrl);
717751
#endif
718
- fossil_system(zCmd);
752
+ fossil_system(zCmd);
753
+ }else if( strcmp(g.argv[2],"send")==0 ){
754
+ const char *zFilename = find_option("file","r",1);
755
+ const char *zMsg = find_option("message","m",1);
756
+ const int mFlags = HTTP_GENERIC | HTTP_QUIET | HTTP_NOCOMPRESS;
757
+ int i;
758
+ const char *zPw;
759
+ Blob up, down, fcontent;
760
+ char zBoundary[80];
761
+ sqlite3_uint64 r[3];
762
+ if( zFilename==0 && zMsg==0 ){
763
+ fossil_fatal("must have --message or --file or both");
764
+ }
765
+ i = (int)strlen(g.url.path);
766
+ while( i>0 && g.url.path[i-1]=='/' ) i--;
767
+ g.url.path = mprintf("%.*s/chat-send", i, g.url.path);
768
+ blob_init(&up, 0, 0);
769
+ blob_init(&down, 0, 0);
770
+ sqlite3_randomness(sizeof(r),r);
771
+ sqlite3_snprintf(sizeof(zBoundary),zBoundary,
772
+ "--------%016llu%016llu%016llu", r[0], r[1], r[2]);
773
+ blob_appendf(&up, "%s", zBoundary);
774
+ if( g.url.user && g.url.user[0] ){
775
+ blob_appendf(&up,"\r\nContent-Disposition: form-data; name=\"resid\"\r\n"
776
+ "\r\n%z\r\n%s", obscure(g.url.user), zBoundary);
777
+ }
778
+ zPw = g.url.passwd;
779
+ if( zPw==0 && isDefaultUrl ) zPw = unobscure(db_get("last-sync-pw", 0));
780
+ if( zPw && zPw[0] ){
781
+ blob_appendf(&up,"\r\nContent-Disposition: form-data; name=\"token\"\r\n"
782
+ "\r\n%z\r\n%s", obscure(zPw), zBoundary);
783
+ }
784
+ if( zMsg && zMsg[0] ){
785
+ blob_appendf(&up,"\r\nContent-Disposition: form-data; name=\"msg\"\r\n"
786
+ "\r\n%s\r\n%s", zMsg, zBoundary);
787
+ }
788
+ if( zFilename && blob_read_from_file(&fcontent, zFilename, ExtFILE)>0 ){
789
+ char *zFN = mprintf("%s", file_tail(zFilename));
790
+ int i;
791
+ const char *zMime = mimetype_from_name(zFilename);
792
+ for(i=0; zFN[i]; i++){
793
+ char c = zFN[i];
794
+ if( fossil_isalnum(c) ) continue;
795
+ if( c=='.' ) continue;
796
+ if( c=='-' ) continue;
797
+ zFN[i] = '_';
798
+ }
799
+ blob_appendf(&up,"\r\nContent-Disposition: form-data; name=\"file\";"
800
+ " filename=\"%s\"\r\n", zFN);
801
+ blob_appendf(&up,"Content-Type: %s\r\n\r\n", zMime);
802
+ blob_append(&up, fcontent.aData, fcontent.nUsed);
803
+ blob_appendf(&up,"\r\n%s", zBoundary);
804
+ }
805
+ blob_append(&up,"--\r\n", 4);
806
+ http_exchange(&up, &down, mFlags, 4, "multipart/form-data");
807
+ blob_reset(&up);
808
+ blob_reset(&down);
809
+ }else if( strcmp(g.argv[2],"url")==0 ){
810
+ /* Undocumented command. Show the URL to access chat. */
811
+ fossil_print("%s/chat\n", zUrl);
812
+ }else{
813
+ fossil_fatal("no such subcommand \"%s\". Use --help for help", g.argv[2]);
814
+ }
719815
}
720816
--- src/chat.c
+++ src/chat.c
@@ -264,11 +264,11 @@
264 if( !g.perm.Chat ) {
265 chat_emit_permissions_error(0);
266 return;
267 }
268 chat_create_tables();
269 nByte = atoi(PD("file:bytes",0));
270 zMsg = PD("msg","");
271 db_begin_write();
272 chat_purge();
273 if( nByte==0 ){
274 if( zMsg[0] ){
@@ -677,43 +677,139 @@
677 }
678
679 /*
680 ** COMMAND: chat
681 **
682 ** Usage: %fossil chat
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
683 **
684 ** Bring up a web-browser window to the chatroom of the default
685 ** remote Fossil repository.
686 */
687 void chat_command(void){
688 const char *zUrl;
689 const char *zBrowser;
690 char *zCmd;
 
 
691 db_find_and_open_repository(0,0);
692 if( g.argc!=2 ){
693 usage("");
694 }
695 zUrl = db_get("last-sync-url",0);
696 if( zUrl==0 ){
697 fossil_fatal("no \"remote\" repository defined");
698 }
699 url_parse(zUrl, 0);
 
 
 
 
 
 
 
 
700 if( g.url.port==g.url.dfltPort ){
701 zUrl = mprintf(
702 "%s://%T%T",
703 g.url.protocol, g.url.name, g.url.path
704 );
705 }else{
706 zUrl = mprintf(
707 "%s://%T:%d%T",
708 g.url.protocol, g.url.name, g.url.port, g.url.path
709 );
710 }
711 zBrowser = fossil_web_browser();
712 if( zBrowser==0 ) return;
 
 
713 #ifdef _WIN32
714 zCmd = mprintf("%s %s/chat?cli &", zBrowser, zUrl);
715 #else
716 zCmd = mprintf("%s \"%s/chat?cli\" &", zBrowser, zUrl);
717 #endif
718 fossil_system(zCmd);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
719 }
720
--- src/chat.c
+++ src/chat.c
@@ -264,11 +264,11 @@
264 if( !g.perm.Chat ) {
265 chat_emit_permissions_error(0);
266 return;
267 }
268 chat_create_tables();
269 nByte = atoi(PD("file:bytes","0"));
270 zMsg = PD("msg","");
271 db_begin_write();
272 chat_purge();
273 if( nByte==0 ){
274 if( zMsg[0] ){
@@ -677,43 +677,139 @@
677 }
678
679 /*
680 ** COMMAND: chat
681 **
682 ** Usage: %fossil chat [SUBCOMMAND] [--remote URL] [ARGS...]
683 **
684 ** This command performs actions associated with the /chat instance
685 ** on the default remote Fossil repository (the Fossil repository whose
686 ** URL shows when you run the "fossil remote" command) or to the URL
687 ** specified by the --remote option. If there is no default remote
688 ** Fossil repository and the --remote option is omitted, then this
689 ** command fails with an error.
690 **
691 ** When there is no SUBCOMMAND (when this command is simply "fossil chat")
692 ** the response is to bring up a web-browser window to the chatroom
693 ** on the default system web-browser. You can accomplish the same by
694 ** typing the appropriate URL into the web-browser yourself. This
695 ** command is merely a convenience for command-line oriented peope.
696 **
697 ** The following subcommands are supported:
698 **
699 ** > fossil chat send [ARGUMENTS]
700 **
701 ** This command sends a new message to the chatroom. The message
702 ** to be sent is determined by arguments as follows:
703 **
704 ** -m|--message TEXT Text of the chat message
705 ** -f|--file FILENAME File to attach to the message
706 **
707 ** Additional subcommands may be added in the future.
 
708 */
709 void chat_command(void){
710 const char *zUrl = find_option("remote",0,1);
711 int urlFlags = 0;
712 int isDefaultUrl = 0;
713 int i;
714
715 db_find_and_open_repository(0,0);
716 if( zUrl ){
717 urlFlags = URL_PROMPT_PW;
718 }else{
719 zUrl = db_get("last-sync-url",0);
720 if( zUrl==0 ){
721 fossil_fatal("no \"remote\" repository defined");
722 }else{
723 isDefaultUrl = 1;
724 }
725 }
726 url_parse(zUrl, urlFlags);
727 if( g.url.isFile || g.url.isSsh ){
728 fossil_fatal("chat only works for http:// and https:// URLs");
729 }
730 i = (int)strlen(g.url.path);
731 while( i>0 && g.url.path[i-1]=='/' ) i--;
732 if( g.url.port==g.url.dfltPort ){
733 zUrl = mprintf(
734 "%s://%T%.*T",
735 g.url.protocol, g.url.name, i, g.url.path
736 );
737 }else{
738 zUrl = mprintf(
739 "%s://%T:%d%.*T",
740 g.url.protocol, g.url.name, g.url.port, i, g.url.path
741 );
742 }
743 if( g.argc==2 ){
744 const char *zBrowser = fossil_web_browser();
745 char *zCmd;
746 if( zBrowser==0 ) return;
747 #ifdef _WIN32
748 zCmd = mprintf("%s %s/chat?cli &", zBrowser, zUrl);
749 #else
750 zCmd = mprintf("%s \"%s/chat?cli\" &", zBrowser, zUrl);
751 #endif
752 fossil_system(zCmd);
753 }else if( strcmp(g.argv[2],"send")==0 ){
754 const char *zFilename = find_option("file","r",1);
755 const char *zMsg = find_option("message","m",1);
756 const int mFlags = HTTP_GENERIC | HTTP_QUIET | HTTP_NOCOMPRESS;
757 int i;
758 const char *zPw;
759 Blob up, down, fcontent;
760 char zBoundary[80];
761 sqlite3_uint64 r[3];
762 if( zFilename==0 && zMsg==0 ){
763 fossil_fatal("must have --message or --file or both");
764 }
765 i = (int)strlen(g.url.path);
766 while( i>0 && g.url.path[i-1]=='/' ) i--;
767 g.url.path = mprintf("%.*s/chat-send", i, g.url.path);
768 blob_init(&up, 0, 0);
769 blob_init(&down, 0, 0);
770 sqlite3_randomness(sizeof(r),r);
771 sqlite3_snprintf(sizeof(zBoundary),zBoundary,
772 "--------%016llu%016llu%016llu", r[0], r[1], r[2]);
773 blob_appendf(&up, "%s", zBoundary);
774 if( g.url.user && g.url.user[0] ){
775 blob_appendf(&up,"\r\nContent-Disposition: form-data; name=\"resid\"\r\n"
776 "\r\n%z\r\n%s", obscure(g.url.user), zBoundary);
777 }
778 zPw = g.url.passwd;
779 if( zPw==0 && isDefaultUrl ) zPw = unobscure(db_get("last-sync-pw", 0));
780 if( zPw && zPw[0] ){
781 blob_appendf(&up,"\r\nContent-Disposition: form-data; name=\"token\"\r\n"
782 "\r\n%z\r\n%s", obscure(zPw), zBoundary);
783 }
784 if( zMsg && zMsg[0] ){
785 blob_appendf(&up,"\r\nContent-Disposition: form-data; name=\"msg\"\r\n"
786 "\r\n%s\r\n%s", zMsg, zBoundary);
787 }
788 if( zFilename && blob_read_from_file(&fcontent, zFilename, ExtFILE)>0 ){
789 char *zFN = mprintf("%s", file_tail(zFilename));
790 int i;
791 const char *zMime = mimetype_from_name(zFilename);
792 for(i=0; zFN[i]; i++){
793 char c = zFN[i];
794 if( fossil_isalnum(c) ) continue;
795 if( c=='.' ) continue;
796 if( c=='-' ) continue;
797 zFN[i] = '_';
798 }
799 blob_appendf(&up,"\r\nContent-Disposition: form-data; name=\"file\";"
800 " filename=\"%s\"\r\n", zFN);
801 blob_appendf(&up,"Content-Type: %s\r\n\r\n", zMime);
802 blob_append(&up, fcontent.aData, fcontent.nUsed);
803 blob_appendf(&up,"\r\n%s", zBoundary);
804 }
805 blob_append(&up,"--\r\n", 4);
806 http_exchange(&up, &down, mFlags, 4, "multipart/form-data");
807 blob_reset(&up);
808 blob_reset(&down);
809 }else if( strcmp(g.argv[2],"url")==0 ){
810 /* Undocumented command. Show the URL to access chat. */
811 fossil_print("%s/chat\n", zUrl);
812 }else{
813 fossil_fatal("no such subcommand \"%s\". Use --help for help", g.argv[2]);
814 }
815 }
816
+19
--- src/login.c
+++ src/login.c
@@ -1078,10 +1078,29 @@
10781078
** see if those credentials are valid for a known user.
10791079
*/
10801080
if( uid==0 && db_get_boolean("http_authentication_ok",0) ){
10811081
uid = login_basic_authentication(zIpAddr);
10821082
}
1083
+
1084
+ /* Check for magic query parameters "resid" (for the username) and
1085
+ ** "token" for the password. Both values (if they exist) will be
1086
+ ** obfuscated.
1087
+ */
1088
+ if( uid==0 ){
1089
+ char *zUsr, *zPW;
1090
+ if( (zUsr = unobscure(P("resid")))!=0
1091
+ && (zPW = unobscure(P("token")))!=0
1092
+ ){
1093
+ char *zSha1Pw = sha1_shared_secret(zPW, zUsr, 0);
1094
+ uid = db_int(0, "SELECT uid FROM user"
1095
+ " WHERE login=%Q"
1096
+ " AND (constant_time_cmp(pw,%Q)=0"
1097
+ " OR constant_time_cmp(pw,%Q)=0)",
1098
+ zUsr, zSha1Pw, zPW);
1099
+ fossil_free(zSha1Pw);
1100
+ }
1101
+ }
10831102
10841103
/* If no user found yet, try to log in as "nobody" */
10851104
if( uid==0 ){
10861105
uid = db_int(0, "SELECT uid FROM user WHERE login='nobody'");
10871106
if( uid==0 ){
10881107
--- src/login.c
+++ src/login.c
@@ -1078,10 +1078,29 @@
1078 ** see if those credentials are valid for a known user.
1079 */
1080 if( uid==0 && db_get_boolean("http_authentication_ok",0) ){
1081 uid = login_basic_authentication(zIpAddr);
1082 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1083
1084 /* If no user found yet, try to log in as "nobody" */
1085 if( uid==0 ){
1086 uid = db_int(0, "SELECT uid FROM user WHERE login='nobody'");
1087 if( uid==0 ){
1088
--- src/login.c
+++ src/login.c
@@ -1078,10 +1078,29 @@
1078 ** see if those credentials are valid for a known user.
1079 */
1080 if( uid==0 && db_get_boolean("http_authentication_ok",0) ){
1081 uid = login_basic_authentication(zIpAddr);
1082 }
1083
1084 /* Check for magic query parameters "resid" (for the username) and
1085 ** "token" for the password. Both values (if they exist) will be
1086 ** obfuscated.
1087 */
1088 if( uid==0 ){
1089 char *zUsr, *zPW;
1090 if( (zUsr = unobscure(P("resid")))!=0
1091 && (zPW = unobscure(P("token")))!=0
1092 ){
1093 char *zSha1Pw = sha1_shared_secret(zPW, zUsr, 0);
1094 uid = db_int(0, "SELECT uid FROM user"
1095 " WHERE login=%Q"
1096 " AND (constant_time_cmp(pw,%Q)=0"
1097 " OR constant_time_cmp(pw,%Q)=0)",
1098 zUsr, zSha1Pw, zPW);
1099 fossil_free(zSha1Pw);
1100 }
1101 }
1102
1103 /* If no user found yet, try to log in as "nobody" */
1104 if( uid==0 ){
1105 uid = db_int(0, "SELECT uid FROM user WHERE login='nobody'");
1106 if( uid==0 ){
1107

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button