Fossil SCM

Fix http_build_header() so /my%20repo stops sending 'POST /my repo', etc. Percent encode everything that RFC 3986 says MUST NOT be in a path, giving eg send POST /my%20repo. Ported from libfossil.

danshearer 2026-10-11 22:51 UTC trunk
Commit c4dec3a6ec5b8ed82b63a20202b002e21c3d61f427447ba07a8ed7cf4d9904c0
1 file changed +25 -6
+25 -6
--- src/http.c
+++ src/http.c
@@ -127,10 +127,29 @@
127127
blob_appendf(pLogin, "login %F %b %b", zLogin, &nonce, &sig);
128128
blob_reset(&pw);
129129
blob_reset(&sig);
130130
blob_reset(&nonce);
131131
}
132
+
133
+/*
134
+** Append zPath to pBlob as the target of an HTTP request line (RFC 9112
135
+** section 3.2). zPath is the decoded path from the URL, so it is
136
+** percent-encoded again here: each byte outside ASCII and each
137
+** character that RFC 3986 section 3.3 does not allow in a path is
138
+** written as %XX. '+' is encoded too, because the server decodes it
139
+** as a space.
140
+*/
141
+static void http_append_request_target(Blob *pBlob, const char *zPath){
142
+ for(; *zPath; zPath++){
143
+ unsigned char c = (unsigned char)*zPath;
144
+ if( c<=0x20 || c>=0x7f || strchr("\"#%+<>?[\\]^`{|}", c)!=0 ){
145
+ blob_appendf(pBlob, "%%%02X", c);
146
+ }else{
147
+ blob_append_char(pBlob, (char)c);
148
+ }
149
+ }
150
+}
132151
133152
/*
134153
** Construct an appropriate HTTP request header. Write the header
135154
** into pHdr. This routine initializes the pHdr blob. pPayload is
136155
** the complete payload (including the login card if pLogin is NULL or
@@ -141,22 +160,22 @@
141160
Blob *pHdr, /* construct the header here */
142161
Blob *pLogin, /* Login card header value or NULL */
143162
const char *zAltMimetype /* Alternative mimetype */
144163
){
145164
int nPayload = pPayload ? blob_size(pPayload) : 0;
146
- const char *zPath;
147165
148166
blob_zero(pHdr);
167
+ blob_appendf(pHdr, "%s ", nPayload>0 ? "POST" : "GET");
149168
if( g.url.subpath ){
150
- zPath = g.url.subpath;
169
+ /* The subpath is already percent-encoded. */
170
+ blob_append(pHdr, g.url.subpath, -1);
151171
}else if( g.url.path==0 || g.url.path[0]==0 ){
152
- zPath = "/";
172
+ blob_append_char(pHdr, '/');
153173
}else{
154
- zPath = g.url.path;
174
+ http_append_request_target(pHdr, g.url.path);
155175
}
156
- blob_appendf(pHdr, "%s %s HTTP/1.1\r\n",
157
- nPayload>0 ? "POST" : "GET", zPath);
176
+ blob_appendf(pHdr, " HTTP/1.1\r\n");
158177
if( g.url.proxyAuth ){
159178
blob_appendf(pHdr, "Proxy-Authorization: %s\r\n", g.url.proxyAuth);
160179
}
161180
if( g.zHttpAuth && g.zHttpAuth[0] ){
162181
const char *zCredentials = g.zHttpAuth;
163182
--- src/http.c
+++ src/http.c
@@ -127,10 +127,29 @@
127 blob_appendf(pLogin, "login %F %b %b", zLogin, &nonce, &sig);
128 blob_reset(&pw);
129 blob_reset(&sig);
130 blob_reset(&nonce);
131 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
133 /*
134 ** Construct an appropriate HTTP request header. Write the header
135 ** into pHdr. This routine initializes the pHdr blob. pPayload is
136 ** the complete payload (including the login card if pLogin is NULL or
@@ -141,22 +160,22 @@
141 Blob *pHdr, /* construct the header here */
142 Blob *pLogin, /* Login card header value or NULL */
143 const char *zAltMimetype /* Alternative mimetype */
144 ){
145 int nPayload = pPayload ? blob_size(pPayload) : 0;
146 const char *zPath;
147
148 blob_zero(pHdr);
 
149 if( g.url.subpath ){
150 zPath = g.url.subpath;
 
151 }else if( g.url.path==0 || g.url.path[0]==0 ){
152 zPath = "/";
153 }else{
154 zPath = g.url.path;
155 }
156 blob_appendf(pHdr, "%s %s HTTP/1.1\r\n",
157 nPayload>0 ? "POST" : "GET", zPath);
158 if( g.url.proxyAuth ){
159 blob_appendf(pHdr, "Proxy-Authorization: %s\r\n", g.url.proxyAuth);
160 }
161 if( g.zHttpAuth && g.zHttpAuth[0] ){
162 const char *zCredentials = g.zHttpAuth;
163
--- src/http.c
+++ src/http.c
@@ -127,10 +127,29 @@
127 blob_appendf(pLogin, "login %F %b %b", zLogin, &nonce, &sig);
128 blob_reset(&pw);
129 blob_reset(&sig);
130 blob_reset(&nonce);
131 }
132
133 /*
134 ** Append zPath to pBlob as the target of an HTTP request line (RFC 9112
135 ** section 3.2). zPath is the decoded path from the URL, so it is
136 ** percent-encoded again here: each byte outside ASCII and each
137 ** character that RFC 3986 section 3.3 does not allow in a path is
138 ** written as %XX. '+' is encoded too, because the server decodes it
139 ** as a space.
140 */
141 static void http_append_request_target(Blob *pBlob, const char *zPath){
142 for(; *zPath; zPath++){
143 unsigned char c = (unsigned char)*zPath;
144 if( c<=0x20 || c>=0x7f || strchr("\"#%+<>?[\\]^`{|}", c)!=0 ){
145 blob_appendf(pBlob, "%%%02X", c);
146 }else{
147 blob_append_char(pBlob, (char)c);
148 }
149 }
150 }
151
152 /*
153 ** Construct an appropriate HTTP request header. Write the header
154 ** into pHdr. This routine initializes the pHdr blob. pPayload is
155 ** the complete payload (including the login card if pLogin is NULL or
@@ -141,22 +160,22 @@
160 Blob *pHdr, /* construct the header here */
161 Blob *pLogin, /* Login card header value or NULL */
162 const char *zAltMimetype /* Alternative mimetype */
163 ){
164 int nPayload = pPayload ? blob_size(pPayload) : 0;
 
165
166 blob_zero(pHdr);
167 blob_appendf(pHdr, "%s ", nPayload>0 ? "POST" : "GET");
168 if( g.url.subpath ){
169 /* The subpath is already percent-encoded. */
170 blob_append(pHdr, g.url.subpath, -1);
171 }else if( g.url.path==0 || g.url.path[0]==0 ){
172 blob_append_char(pHdr, '/');
173 }else{
174 http_append_request_target(pHdr, g.url.path);
175 }
176 blob_appendf(pHdr, " HTTP/1.1\r\n");
 
177 if( g.url.proxyAuth ){
178 blob_appendf(pHdr, "Proxy-Authorization: %s\r\n", g.url.proxyAuth);
179 }
180 if( g.zHttpAuth && g.zHttpAuth[0] ){
181 const char *zCredentials = g.zHttpAuth;
182

Keyboard Shortcuts

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