Fossil SCM
Add a Content-Disposition: header to the HTTP reply for the "Download" button.
Commit
34cb4766f1cabaad44c1b91c500539dff9888e49fe64480194d86e1ed51f2129
Parent
9d18585d544b41f…
2 files changed
+19
+32
-14
+19
| --- src/cgi.c | ||
| +++ src/cgi.c | ||
| @@ -433,10 +433,29 @@ | ||
| 433 | 433 | va_list ap; |
| 434 | 434 | va_start(ap, zFormat); |
| 435 | 435 | cgi_redirect(vmprintf(zFormat, ap)); |
| 436 | 436 | va_end(ap); |
| 437 | 437 | } |
| 438 | + | |
| 439 | +/* | |
| 440 | +** Add a "Content-disposition: attachment; filename=%s" header to the reply. | |
| 441 | +*/ | |
| 442 | +void cgi_content_disposition_filename(const char *zFilename){ | |
| 443 | + /* 0123456789 123456789 123456789 123456789 123456*/ | |
| 444 | + char *z = mprintf("Content-Disposition: attachment; filename=\"%s\";\r\n", | |
| 445 | + zFilename); | |
| 446 | + int i; | |
| 447 | + int n = (int)strlen(z); | |
| 448 | + for(i=43; i<n-4; i++){ | |
| 449 | + char c = z[i]; | |
| 450 | + if( isalnum(c) ) continue; | |
| 451 | + if( c=='.' || c=='-' || c=='/' ) continue; | |
| 452 | + z[i] = '_'; | |
| 453 | + } | |
| 454 | + cgi_append_header(z); | |
| 455 | + fossil_free(z); | |
| 456 | +} | |
| 438 | 457 | |
| 439 | 458 | /* |
| 440 | 459 | ** Return the URL for the caller. This is obtained from either the |
| 441 | 460 | ** referer CGI parameter, if it exists, or the HTTP_REFERER HTTP parameter. |
| 442 | 461 | ** If neither exist, return zDefault. |
| 443 | 462 |
| --- src/cgi.c | |
| +++ src/cgi.c | |
| @@ -433,10 +433,29 @@ | |
| 433 | va_list ap; |
| 434 | va_start(ap, zFormat); |
| 435 | cgi_redirect(vmprintf(zFormat, ap)); |
| 436 | va_end(ap); |
| 437 | } |
| 438 | |
| 439 | /* |
| 440 | ** Return the URL for the caller. This is obtained from either the |
| 441 | ** referer CGI parameter, if it exists, or the HTTP_REFERER HTTP parameter. |
| 442 | ** If neither exist, return zDefault. |
| 443 |
| --- src/cgi.c | |
| +++ src/cgi.c | |
| @@ -433,10 +433,29 @@ | |
| 433 | va_list ap; |
| 434 | va_start(ap, zFormat); |
| 435 | cgi_redirect(vmprintf(zFormat, ap)); |
| 436 | va_end(ap); |
| 437 | } |
| 438 | |
| 439 | /* |
| 440 | ** Add a "Content-disposition: attachment; filename=%s" header to the reply. |
| 441 | */ |
| 442 | void cgi_content_disposition_filename(const char *zFilename){ |
| 443 | /* 0123456789 123456789 123456789 123456789 123456*/ |
| 444 | char *z = mprintf("Content-Disposition: attachment; filename=\"%s\";\r\n", |
| 445 | zFilename); |
| 446 | int i; |
| 447 | int n = (int)strlen(z); |
| 448 | for(i=43; i<n-4; i++){ |
| 449 | char c = z[i]; |
| 450 | if( isalnum(c) ) continue; |
| 451 | if( c=='.' || c=='-' || c=='/' ) continue; |
| 452 | z[i] = '_'; |
| 453 | } |
| 454 | cgi_append_header(z); |
| 455 | fossil_free(z); |
| 456 | } |
| 457 | |
| 458 | /* |
| 459 | ** Return the URL for the caller. This is obtained from either the |
| 460 | ** referer CGI parameter, if it exists, or the HTTP_REFERER HTTP parameter. |
| 461 | ** If neither exist, return zDefault. |
| 462 |
+32
-14
| --- src/info.c | ||
| +++ src/info.c | ||
| @@ -1754,13 +1754,18 @@ | ||
| 1754 | 1754 | style_footer(); |
| 1755 | 1755 | } |
| 1756 | 1756 | |
| 1757 | 1757 | /* |
| 1758 | 1758 | ** WEBPAGE: raw |
| 1759 | -** URL: /raw?name=ARTIFACTID&m=TYPE | |
| 1759 | +** URL: /raw/ARTIFACTID | |
| 1760 | 1760 | ** URL: /raw?ci=BRANCH&filename=NAME |
| 1761 | 1761 | ** |
| 1762 | +** Additional query parameters: | |
| 1763 | +** | |
| 1764 | +** m=MIMETYPE The mimetype is MIMETYPE | |
| 1765 | +** at=FILENAME Content-disposition; attachment; filename=FILENAME; | |
| 1766 | +** | |
| 1762 | 1767 | ** Return the uninterpreted content of an artifact. Used primarily |
| 1763 | 1768 | ** to view artifacts that are images. |
| 1764 | 1769 | */ |
| 1765 | 1770 | void rawartifact_page(void){ |
| 1766 | 1771 | int rid = 0; |
| @@ -1816,26 +1821,37 @@ | ||
| 1816 | 1821 | ** NULL, guess at the MIME-type based on the filename |
| 1817 | 1822 | ** associated with the artifact. |
| 1818 | 1823 | */ |
| 1819 | 1824 | void deliver_artifact(int rid, const char *zMime){ |
| 1820 | 1825 | Blob content; |
| 1826 | + const char *zAttachName = P("at"); | |
| 1821 | 1827 | if( zMime==0 ){ |
| 1822 | - char *zFName = db_text(0, "SELECT filename.name FROM mlink, filename" | |
| 1823 | - " WHERE mlink.fid=%d" | |
| 1824 | - " AND filename.fnid=mlink.fnid", rid); | |
| 1825 | - if( !zFName ){ | |
| 1828 | + char *zFN = (char*)zAttachName; | |
| 1829 | + if( zFN==0 ){ | |
| 1830 | + zFN = db_text(0, "SELECT filename.name FROM mlink, filename" | |
| 1831 | + " WHERE mlink.fid=%d" | |
| 1832 | + " AND filename.fnid=mlink.fnid", rid); | |
| 1833 | + } | |
| 1834 | + if( zFN==0 ){ | |
| 1826 | 1835 | /* Look also at the attachment table */ |
| 1827 | - zFName = db_text(0, "SELECT attachment.filename FROM attachment, blob" | |
| 1828 | - " WHERE blob.rid=%d" | |
| 1829 | - " AND attachment.src=blob.uuid", rid); | |
| 1836 | + zFN = db_text(0, "SELECT attachment.filename FROM attachment, blob" | |
| 1837 | + " WHERE blob.rid=%d" | |
| 1838 | + " AND attachment.src=blob.uuid", rid); | |
| 1839 | + } | |
| 1840 | + if( zFN ){ | |
| 1841 | + zMime = mimetype_from_name(zFN); | |
| 1830 | 1842 | } |
| 1831 | - if( zFName ) zMime = mimetype_from_name(zFName); | |
| 1832 | - if( zMime==0 ) zMime = "application/x-fossil-artifact"; | |
| 1843 | + if( zMime==0 ){ | |
| 1844 | + zMime = "application/x-fossil-artifact"; | |
| 1845 | + } | |
| 1833 | 1846 | } |
| 1834 | 1847 | content_get(rid, &content); |
| 1835 | 1848 | fossil_free(style_csp(1)); |
| 1836 | 1849 | cgi_set_content_type(zMime); |
| 1850 | + if( zAttachName ){ | |
| 1851 | + cgi_content_disposition_filename(zAttachName); | |
| 1852 | + } | |
| 1837 | 1853 | cgi_set_content(&content); |
| 1838 | 1854 | } |
| 1839 | 1855 | |
| 1840 | 1856 | /* |
| 1841 | 1857 | ** Render a hex dump of a file. |
| @@ -1929,11 +1945,12 @@ | ||
| 1929 | 1945 | @ :</h2> |
| 1930 | 1946 | } |
| 1931 | 1947 | blob_zero(&downloadName); |
| 1932 | 1948 | if( P("verbose")!=0 ) objdescFlags |= OBJDESC_DETAIL; |
| 1933 | 1949 | object_description(rid, objdescFlags, 0, &downloadName); |
| 1934 | - style_submenu_element("Download", "%R/raw/%s", zUuid); | |
| 1950 | + style_submenu_element("Download", "%R/raw/%s?at=%T", | |
| 1951 | + zUuid, blob_str(&downloadName)); | |
| 1935 | 1952 | @ <hr /> |
| 1936 | 1953 | content_get(rid, &content); |
| 1937 | 1954 | @ <blockquote><pre> |
| 1938 | 1955 | hexdump(&content); |
| 1939 | 1956 | @ </pre></blockquote> |
| @@ -2258,12 +2275,13 @@ | ||
| 2258 | 2275 | if( asText ) objdescFlags &= ~OBJDESC_BASE; |
| 2259 | 2276 | objType = object_description(rid, objdescFlags, |
| 2260 | 2277 | (isFile?zName:0), &downloadName); |
| 2261 | 2278 | } |
| 2262 | 2279 | if( !descOnly && P("download")!=0 ){ |
| 2263 | - cgi_redirectf("%R/raw/%s", | |
| 2264 | - db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid)); | |
| 2280 | + cgi_redirectf("%R/raw/%s?at=%T", | |
| 2281 | + db_text("x", "SELECT uuid FROM blob WHERE rid=%d", rid), | |
| 2282 | + blob_str(&downloadName)); | |
| 2265 | 2283 | /*NOTREACHED*/ |
| 2266 | 2284 | } |
| 2267 | 2285 | if( g.perm.Admin ){ |
| 2268 | 2286 | const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 2269 | 2287 | if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){ |
| @@ -2304,11 +2322,11 @@ | ||
| 2304 | 2322 | const char *zIp = db_column_text(&q,2); |
| 2305 | 2323 | @ <p>Received on %s(zDate) from %h(zUser) at %h(zIp).</p> |
| 2306 | 2324 | } |
| 2307 | 2325 | db_finalize(&q); |
| 2308 | 2326 | } |
| 2309 | - style_submenu_element("Download", "%R/raw/%s", zUuid); | |
| 2327 | + style_submenu_element("Download", "%R/raw/%s?at=%T", zUuid, zName); | |
| 2310 | 2328 | if( db_exists("SELECT 1 FROM mlink WHERE fid=%d", rid) ){ |
| 2311 | 2329 | style_submenu_element("Check-ins Using", "%R/timeline?n=200&uf=%s", zUuid); |
| 2312 | 2330 | } |
| 2313 | 2331 | zMime = mimetype_from_name(blob_str(&downloadName)); |
| 2314 | 2332 | if( zMime ){ |
| 2315 | 2333 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -1754,13 +1754,18 @@ | |
| 1754 | style_footer(); |
| 1755 | } |
| 1756 | |
| 1757 | /* |
| 1758 | ** WEBPAGE: raw |
| 1759 | ** URL: /raw?name=ARTIFACTID&m=TYPE |
| 1760 | ** URL: /raw?ci=BRANCH&filename=NAME |
| 1761 | ** |
| 1762 | ** Return the uninterpreted content of an artifact. Used primarily |
| 1763 | ** to view artifacts that are images. |
| 1764 | */ |
| 1765 | void rawartifact_page(void){ |
| 1766 | int rid = 0; |
| @@ -1816,26 +1821,37 @@ | |
| 1816 | ** NULL, guess at the MIME-type based on the filename |
| 1817 | ** associated with the artifact. |
| 1818 | */ |
| 1819 | void deliver_artifact(int rid, const char *zMime){ |
| 1820 | Blob content; |
| 1821 | if( zMime==0 ){ |
| 1822 | char *zFName = db_text(0, "SELECT filename.name FROM mlink, filename" |
| 1823 | " WHERE mlink.fid=%d" |
| 1824 | " AND filename.fnid=mlink.fnid", rid); |
| 1825 | if( !zFName ){ |
| 1826 | /* Look also at the attachment table */ |
| 1827 | zFName = db_text(0, "SELECT attachment.filename FROM attachment, blob" |
| 1828 | " WHERE blob.rid=%d" |
| 1829 | " AND attachment.src=blob.uuid", rid); |
| 1830 | } |
| 1831 | if( zFName ) zMime = mimetype_from_name(zFName); |
| 1832 | if( zMime==0 ) zMime = "application/x-fossil-artifact"; |
| 1833 | } |
| 1834 | content_get(rid, &content); |
| 1835 | fossil_free(style_csp(1)); |
| 1836 | cgi_set_content_type(zMime); |
| 1837 | cgi_set_content(&content); |
| 1838 | } |
| 1839 | |
| 1840 | /* |
| 1841 | ** Render a hex dump of a file. |
| @@ -1929,11 +1945,12 @@ | |
| 1929 | @ :</h2> |
| 1930 | } |
| 1931 | blob_zero(&downloadName); |
| 1932 | if( P("verbose")!=0 ) objdescFlags |= OBJDESC_DETAIL; |
| 1933 | object_description(rid, objdescFlags, 0, &downloadName); |
| 1934 | style_submenu_element("Download", "%R/raw/%s", zUuid); |
| 1935 | @ <hr /> |
| 1936 | content_get(rid, &content); |
| 1937 | @ <blockquote><pre> |
| 1938 | hexdump(&content); |
| 1939 | @ </pre></blockquote> |
| @@ -2258,12 +2275,13 @@ | |
| 2258 | if( asText ) objdescFlags &= ~OBJDESC_BASE; |
| 2259 | objType = object_description(rid, objdescFlags, |
| 2260 | (isFile?zName:0), &downloadName); |
| 2261 | } |
| 2262 | if( !descOnly && P("download")!=0 ){ |
| 2263 | cgi_redirectf("%R/raw/%s", |
| 2264 | db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid)); |
| 2265 | /*NOTREACHED*/ |
| 2266 | } |
| 2267 | if( g.perm.Admin ){ |
| 2268 | const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 2269 | if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){ |
| @@ -2304,11 +2322,11 @@ | |
| 2304 | const char *zIp = db_column_text(&q,2); |
| 2305 | @ <p>Received on %s(zDate) from %h(zUser) at %h(zIp).</p> |
| 2306 | } |
| 2307 | db_finalize(&q); |
| 2308 | } |
| 2309 | style_submenu_element("Download", "%R/raw/%s", zUuid); |
| 2310 | if( db_exists("SELECT 1 FROM mlink WHERE fid=%d", rid) ){ |
| 2311 | style_submenu_element("Check-ins Using", "%R/timeline?n=200&uf=%s", zUuid); |
| 2312 | } |
| 2313 | zMime = mimetype_from_name(blob_str(&downloadName)); |
| 2314 | if( zMime ){ |
| 2315 |
| --- src/info.c | |
| +++ src/info.c | |
| @@ -1754,13 +1754,18 @@ | |
| 1754 | style_footer(); |
| 1755 | } |
| 1756 | |
| 1757 | /* |
| 1758 | ** WEBPAGE: raw |
| 1759 | ** URL: /raw/ARTIFACTID |
| 1760 | ** URL: /raw?ci=BRANCH&filename=NAME |
| 1761 | ** |
| 1762 | ** Additional query parameters: |
| 1763 | ** |
| 1764 | ** m=MIMETYPE The mimetype is MIMETYPE |
| 1765 | ** at=FILENAME Content-disposition; attachment; filename=FILENAME; |
| 1766 | ** |
| 1767 | ** Return the uninterpreted content of an artifact. Used primarily |
| 1768 | ** to view artifacts that are images. |
| 1769 | */ |
| 1770 | void rawartifact_page(void){ |
| 1771 | int rid = 0; |
| @@ -1816,26 +1821,37 @@ | |
| 1821 | ** NULL, guess at the MIME-type based on the filename |
| 1822 | ** associated with the artifact. |
| 1823 | */ |
| 1824 | void deliver_artifact(int rid, const char *zMime){ |
| 1825 | Blob content; |
| 1826 | const char *zAttachName = P("at"); |
| 1827 | if( zMime==0 ){ |
| 1828 | char *zFN = (char*)zAttachName; |
| 1829 | if( zFN==0 ){ |
| 1830 | zFN = db_text(0, "SELECT filename.name FROM mlink, filename" |
| 1831 | " WHERE mlink.fid=%d" |
| 1832 | " AND filename.fnid=mlink.fnid", rid); |
| 1833 | } |
| 1834 | if( zFN==0 ){ |
| 1835 | /* Look also at the attachment table */ |
| 1836 | zFN = db_text(0, "SELECT attachment.filename FROM attachment, blob" |
| 1837 | " WHERE blob.rid=%d" |
| 1838 | " AND attachment.src=blob.uuid", rid); |
| 1839 | } |
| 1840 | if( zFN ){ |
| 1841 | zMime = mimetype_from_name(zFN); |
| 1842 | } |
| 1843 | if( zMime==0 ){ |
| 1844 | zMime = "application/x-fossil-artifact"; |
| 1845 | } |
| 1846 | } |
| 1847 | content_get(rid, &content); |
| 1848 | fossil_free(style_csp(1)); |
| 1849 | cgi_set_content_type(zMime); |
| 1850 | if( zAttachName ){ |
| 1851 | cgi_content_disposition_filename(zAttachName); |
| 1852 | } |
| 1853 | cgi_set_content(&content); |
| 1854 | } |
| 1855 | |
| 1856 | /* |
| 1857 | ** Render a hex dump of a file. |
| @@ -1929,11 +1945,12 @@ | |
| 1945 | @ :</h2> |
| 1946 | } |
| 1947 | blob_zero(&downloadName); |
| 1948 | if( P("verbose")!=0 ) objdescFlags |= OBJDESC_DETAIL; |
| 1949 | object_description(rid, objdescFlags, 0, &downloadName); |
| 1950 | style_submenu_element("Download", "%R/raw/%s?at=%T", |
| 1951 | zUuid, blob_str(&downloadName)); |
| 1952 | @ <hr /> |
| 1953 | content_get(rid, &content); |
| 1954 | @ <blockquote><pre> |
| 1955 | hexdump(&content); |
| 1956 | @ </pre></blockquote> |
| @@ -2258,12 +2275,13 @@ | |
| 2275 | if( asText ) objdescFlags &= ~OBJDESC_BASE; |
| 2276 | objType = object_description(rid, objdescFlags, |
| 2277 | (isFile?zName:0), &downloadName); |
| 2278 | } |
| 2279 | if( !descOnly && P("download")!=0 ){ |
| 2280 | cgi_redirectf("%R/raw/%s?at=%T", |
| 2281 | db_text("x", "SELECT uuid FROM blob WHERE rid=%d", rid), |
| 2282 | blob_str(&downloadName)); |
| 2283 | /*NOTREACHED*/ |
| 2284 | } |
| 2285 | if( g.perm.Admin ){ |
| 2286 | const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid); |
| 2287 | if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){ |
| @@ -2304,11 +2322,11 @@ | |
| 2322 | const char *zIp = db_column_text(&q,2); |
| 2323 | @ <p>Received on %s(zDate) from %h(zUser) at %h(zIp).</p> |
| 2324 | } |
| 2325 | db_finalize(&q); |
| 2326 | } |
| 2327 | style_submenu_element("Download", "%R/raw/%s?at=%T", zUuid, zName); |
| 2328 | if( db_exists("SELECT 1 FROM mlink WHERE fid=%d", rid) ){ |
| 2329 | style_submenu_element("Check-ins Using", "%R/timeline?n=200&uf=%s", zUuid); |
| 2330 | } |
| 2331 | zMime = mimetype_from_name(blob_str(&downloadName)); |
| 2332 | if( zMime ){ |
| 2333 |