Fossil SCM

Refactor how /attachadd reads POSTed files so that we can reuse it in the new forum editor.

stephan 2026-06-06 16:35 UTC forum-editor-2026
Commit 4a8bebc81c0237a5dca3d5d305f4dac9bed47d8653ab7c029c8ac63867c40559
1 file changed +67 -27
+67 -27
--- src/attach.c
+++ src/attach.c
@@ -780,18 +780,12 @@
780780
void attachadd_ajax_post(void){
781781
const char *zTarget;
782782
char *zExtraFree = 0;
783783
int eTgtType = 0;
784784
int bNeedsModeration = 0;
785
- int i;
786785
int goodCaptcha = 1;
787
- int szLimit; /* attachment-max-size setting */
788786
int bRollback = 0; /* Roll back if true. */
789
- char aKeyPrefix[20]; /* Buffer for key "file%d" */
790
- char aKeySize[30]; /* Buffer for key "file%d:bytes" */
791
- char aKeyName[30]; /* Buffer for key "file%d:filename" */
792
- char aKeyDesc[30]; /* Buffer for key "file%d_desc" */
793787
794788
if( ! ajax_route_bootstrap(0, 1) ){
795789
return;
796790
}else if( !(goodCaptcha = captcha_is_correct(0)) ){
797791
goto ajax_post_403;
@@ -869,14 +863,65 @@
869863
bNeedsModeration = wiki_need_moderation(0);
870864
break;
871865
}
872866
}
873867
868
+ if( attachments_from_POST_ajax(zTarget, bNeedsModeration)>=0 ){
869
+ CX("}");
870
+ if( atoi(PD("dryrun","0"))>0 ){
871
+ bRollback = 1;
872
+ }
873
+ }/*else error response was set up*/
874
+ fossil_free(zExtraFree);
875
+ db_end_transaction(bRollback);
876
+ return;
877
+ajax_post_403:
878
+ db_rollback_transaction();
879
+ ajax_route_error(403, "Permission denied.");
880
+ return;
881
+ajax_post_404:
882
+ db_rollback_transaction();
883
+ ajax_route_error(404, "Target not found.");
884
+ return;
885
+}
886
+
887
+/*
888
+** A helper for AJAX-style routines which accept file attachments via
889
+** POST. zTarget must be a full attachment target. bNeedsModeration
890
+** must be true if the attachment requires moderation.
891
+**
892
+** It is up to the caller to have validated all security measures
893
+** before calling this.
894
+**
895
+** This looks for POSTed files names "file1".."fileN", stopping when
896
+** it finds no entry. Returns the number of entries attached to the
897
+** target or a negative value on error (in which case the current db
898
+** transaction will be in a rollback state).
899
+**
900
+** The only errors are currently attachment size limit violations:
901
+** attachments must have a non-0 size and if the attachment-size-limit
902
+** setting is >0 then each file's size must be <= that.
903
+**
904
+** If this returns a negative value, it will have populated an error
905
+** response using ajax_route_error(). On success it produces no
906
+** output.
907
+*/
908
+int attachments_from_POST_ajax(const char *zTarget, int bNeedsModeration){
909
+ int i;
910
+ int rc = 0;
911
+ int n = 0;
912
+ int szLimit; /* attachment-max-size setting */
913
+ char aKeyPrefix[20]; /* Buffer for key "file%d" */
914
+ char aKeySize[30]; /* Buffer for key "file%d:bytes" */
915
+ char aKeyName[30]; /* Buffer for key "file%d:filename" */
916
+ char aKeyDesc[30]; /* Buffer for key "file%d_desc" */
917
+ db_begin_transaction();
874918
szLimit = db_get_int("attachment-size-limit", 0);
875
- for(i = 1; !bRollback; ++i){
919
+ for(i = 1; ; ++i, ++n){
876920
/* Look for P("fileN"), where N=1..n */
877921
const char *zContent;
922
+ const char *zFilename;
878923
int szContent;
879924
sqlite3_snprintf(sizeof(aKeyPrefix), aKeyPrefix, "file%d", i);
880925
zContent = P(aKeyPrefix);
881926
if( !zContent ){
882927
/* End of the list. */
@@ -884,43 +929,38 @@
884929
}
885930
sqlite3_snprintf(sizeof(aKeySize), aKeySize, "%s:bytes",
886931
aKeyPrefix);
887932
szContent = atoi(PD(aKeySize,"-1"));
888933
if( szContent<=0 ){
889
- bRollback = 1;
934
+ rc = -1;
890935
ajax_route_error(400,"Invalid file size: %d", szContent);
891936
break;
892937
}else if( szLimit>0 && szContent>szLimit ){
893
- bRollback = 1;
938
+ rc = -2;
894939
ajax_route_error(400, "File size limit is %d bytes.", szLimit);
895940
break;
896941
}else{
897942
sqlite3_snprintf(sizeof(aKeyName), aKeyName, "%s:filename",
898943
aKeyPrefix);
899944
sqlite3_snprintf(sizeof(aKeyDesc), aKeyDesc, "%s_desc",
900945
aKeyPrefix);
901
- attach_commit(P(aKeyName), zTarget, zContent, szContent,
946
+ if( 0==(zFilename=P(aKeyName)) ){
947
+ rc = -3;
948
+ ajax_route_error(400, "Missing filename.");
949
+ break;
950
+ }
951
+ attach_commit(zFilename, zTarget, zContent, szContent,
902952
bNeedsModeration, P(aKeyDesc));
903953
}
904954
}
905
- fossil_free(zExtraFree);
906
- if( !bRollback ){
907
- CX("}");
908
- if( atoi(PD("dryrun","0"))>0 ){
909
- bRollback = 1;
910
- }
911
- }
912
- db_end_transaction(bRollback);
913
- return;
914
-ajax_post_403:
915
- db_rollback_transaction();
916
- ajax_route_error(403, "Permission denied.");
917
- return;
918
-ajax_post_404:
919
- db_rollback_transaction();
920
- ajax_route_error(404, "Target not found.");
921
- return;
955
+ if( rc<0 ){
956
+ db_rollback_transaction();
957
+ return rc;
958
+ }else{
959
+ db_commit_transaction();
960
+ return n;
961
+ }
922962
}
923963
924964
/*
925965
** Proxy for /attachadd?target=X
926966
**
927967
--- src/attach.c
+++ src/attach.c
@@ -780,18 +780,12 @@
780 void attachadd_ajax_post(void){
781 const char *zTarget;
782 char *zExtraFree = 0;
783 int eTgtType = 0;
784 int bNeedsModeration = 0;
785 int i;
786 int goodCaptcha = 1;
787 int szLimit; /* attachment-max-size setting */
788 int bRollback = 0; /* Roll back if true. */
789 char aKeyPrefix[20]; /* Buffer for key "file%d" */
790 char aKeySize[30]; /* Buffer for key "file%d:bytes" */
791 char aKeyName[30]; /* Buffer for key "file%d:filename" */
792 char aKeyDesc[30]; /* Buffer for key "file%d_desc" */
793
794 if( ! ajax_route_bootstrap(0, 1) ){
795 return;
796 }else if( !(goodCaptcha = captcha_is_correct(0)) ){
797 goto ajax_post_403;
@@ -869,14 +863,65 @@
869 bNeedsModeration = wiki_need_moderation(0);
870 break;
871 }
872 }
873
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
874 szLimit = db_get_int("attachment-size-limit", 0);
875 for(i = 1; !bRollback; ++i){
876 /* Look for P("fileN"), where N=1..n */
877 const char *zContent;
 
878 int szContent;
879 sqlite3_snprintf(sizeof(aKeyPrefix), aKeyPrefix, "file%d", i);
880 zContent = P(aKeyPrefix);
881 if( !zContent ){
882 /* End of the list. */
@@ -884,43 +929,38 @@
884 }
885 sqlite3_snprintf(sizeof(aKeySize), aKeySize, "%s:bytes",
886 aKeyPrefix);
887 szContent = atoi(PD(aKeySize,"-1"));
888 if( szContent<=0 ){
889 bRollback = 1;
890 ajax_route_error(400,"Invalid file size: %d", szContent);
891 break;
892 }else if( szLimit>0 && szContent>szLimit ){
893 bRollback = 1;
894 ajax_route_error(400, "File size limit is %d bytes.", szLimit);
895 break;
896 }else{
897 sqlite3_snprintf(sizeof(aKeyName), aKeyName, "%s:filename",
898 aKeyPrefix);
899 sqlite3_snprintf(sizeof(aKeyDesc), aKeyDesc, "%s_desc",
900 aKeyPrefix);
901 attach_commit(P(aKeyName), zTarget, zContent, szContent,
 
 
 
 
 
902 bNeedsModeration, P(aKeyDesc));
903 }
904 }
905 fossil_free(zExtraFree);
906 if( !bRollback ){
907 CX("}");
908 if( atoi(PD("dryrun","0"))>0 ){
909 bRollback = 1;
910 }
911 }
912 db_end_transaction(bRollback);
913 return;
914 ajax_post_403:
915 db_rollback_transaction();
916 ajax_route_error(403, "Permission denied.");
917 return;
918 ajax_post_404:
919 db_rollback_transaction();
920 ajax_route_error(404, "Target not found.");
921 return;
922 }
923
924 /*
925 ** Proxy for /attachadd?target=X
926 **
927
--- src/attach.c
+++ src/attach.c
@@ -780,18 +780,12 @@
780 void attachadd_ajax_post(void){
781 const char *zTarget;
782 char *zExtraFree = 0;
783 int eTgtType = 0;
784 int bNeedsModeration = 0;
 
785 int goodCaptcha = 1;
 
786 int bRollback = 0; /* Roll back if true. */
 
 
 
 
787
788 if( ! ajax_route_bootstrap(0, 1) ){
789 return;
790 }else if( !(goodCaptcha = captcha_is_correct(0)) ){
791 goto ajax_post_403;
@@ -869,14 +863,65 @@
863 bNeedsModeration = wiki_need_moderation(0);
864 break;
865 }
866 }
867
868 if( attachments_from_POST_ajax(zTarget, bNeedsModeration)>=0 ){
869 CX("}");
870 if( atoi(PD("dryrun","0"))>0 ){
871 bRollback = 1;
872 }
873 }/*else error response was set up*/
874 fossil_free(zExtraFree);
875 db_end_transaction(bRollback);
876 return;
877 ajax_post_403:
878 db_rollback_transaction();
879 ajax_route_error(403, "Permission denied.");
880 return;
881 ajax_post_404:
882 db_rollback_transaction();
883 ajax_route_error(404, "Target not found.");
884 return;
885 }
886
887 /*
888 ** A helper for AJAX-style routines which accept file attachments via
889 ** POST. zTarget must be a full attachment target. bNeedsModeration
890 ** must be true if the attachment requires moderation.
891 **
892 ** It is up to the caller to have validated all security measures
893 ** before calling this.
894 **
895 ** This looks for POSTed files names "file1".."fileN", stopping when
896 ** it finds no entry. Returns the number of entries attached to the
897 ** target or a negative value on error (in which case the current db
898 ** transaction will be in a rollback state).
899 **
900 ** The only errors are currently attachment size limit violations:
901 ** attachments must have a non-0 size and if the attachment-size-limit
902 ** setting is >0 then each file's size must be <= that.
903 **
904 ** If this returns a negative value, it will have populated an error
905 ** response using ajax_route_error(). On success it produces no
906 ** output.
907 */
908 int attachments_from_POST_ajax(const char *zTarget, int bNeedsModeration){
909 int i;
910 int rc = 0;
911 int n = 0;
912 int szLimit; /* attachment-max-size setting */
913 char aKeyPrefix[20]; /* Buffer for key "file%d" */
914 char aKeySize[30]; /* Buffer for key "file%d:bytes" */
915 char aKeyName[30]; /* Buffer for key "file%d:filename" */
916 char aKeyDesc[30]; /* Buffer for key "file%d_desc" */
917 db_begin_transaction();
918 szLimit = db_get_int("attachment-size-limit", 0);
919 for(i = 1; ; ++i, ++n){
920 /* Look for P("fileN"), where N=1..n */
921 const char *zContent;
922 const char *zFilename;
923 int szContent;
924 sqlite3_snprintf(sizeof(aKeyPrefix), aKeyPrefix, "file%d", i);
925 zContent = P(aKeyPrefix);
926 if( !zContent ){
927 /* End of the list. */
@@ -884,43 +929,38 @@
929 }
930 sqlite3_snprintf(sizeof(aKeySize), aKeySize, "%s:bytes",
931 aKeyPrefix);
932 szContent = atoi(PD(aKeySize,"-1"));
933 if( szContent<=0 ){
934 rc = -1;
935 ajax_route_error(400,"Invalid file size: %d", szContent);
936 break;
937 }else if( szLimit>0 && szContent>szLimit ){
938 rc = -2;
939 ajax_route_error(400, "File size limit is %d bytes.", szLimit);
940 break;
941 }else{
942 sqlite3_snprintf(sizeof(aKeyName), aKeyName, "%s:filename",
943 aKeyPrefix);
944 sqlite3_snprintf(sizeof(aKeyDesc), aKeyDesc, "%s_desc",
945 aKeyPrefix);
946 if( 0==(zFilename=P(aKeyName)) ){
947 rc = -3;
948 ajax_route_error(400, "Missing filename.");
949 break;
950 }
951 attach_commit(zFilename, zTarget, zContent, szContent,
952 bNeedsModeration, P(aKeyDesc));
953 }
954 }
955 if( rc<0 ){
956 db_rollback_transaction();
957 return rc;
958 }else{
959 db_commit_transaction();
960 return n;
961 }
 
 
 
 
 
 
 
 
 
 
962 }
963
964 /*
965 ** Proxy for /attachadd?target=X
966 **
967

Keyboard Shortcuts

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