about summary refs log tree commit diff
path: root/nixpkgs/pkgs/servers/http/nginx/nix-etag-1.15.4.patch
blob: 8c8c8ce74b21a857b351dc937f5916e6e2705d57 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
This patch makes it possible to serve static content from Nix store paths, by
using the hash of the store path for the ETag header.

diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c
index 97a91aee2..2d07d71e6 100644
--- a/src/http/ngx_http_core_module.c
+++ b/src/http/ngx_http_core_module.c
@@ -1676,6 +1676,8 @@ ngx_http_set_etag(ngx_http_request_t *r)
 {
     ngx_table_elt_t           *etag;
     ngx_http_core_loc_conf_t  *clcf;
+    u_char                    *real, *ptr1, *ptr2;
+    ngx_err_t                 err;
 
     clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
 
@@ -1692,16 +1694,82 @@ ngx_http_set_etag(ngx_http_request_t *r)
     etag->next = NULL;
     ngx_str_set(&etag->key, "ETag");
 
-    etag->value.data = ngx_pnalloc(r->pool, NGX_OFF_T_LEN + NGX_TIME_T_LEN + 3);
-    if (etag->value.data == NULL) {
-        etag->hash = 0;
-        return NGX_ERROR;
+    // Upstream nginx uses file mod timestamp and content-length for Etag, but
+    // files in the Nix store have their timestamps reset, so that doesn't work.
+    // Instead, when serving from the Nix store, we use the hash from the store
+    // path and content-length.
+    //
+    // Every file in under the given store path will share the same store path
+    // hash. It is fine to serve different resources with the same Etag, but
+    // different representations of the same resource (eg the same file, but
+    // gzip-compressed) should have different Etags. Thus, we also append
+    // content-length, which should be different when the response is compressed
+
+    err = ngx_errno;
+    real = ngx_realpath(clcf->root.data, NULL);
+    ngx_set_errno(err);
+
+    #define NIX_STORE_DIR "@nixStoreDir@"
+    #define NIX_STORE_LEN @nixStoreDirLen@
+
+    if (r->headers_out.last_modified_time == 1
+        && real != NULL
+        && !ngx_strncmp(real, NIX_STORE_DIR, NIX_STORE_LEN)
+        && real[NIX_STORE_LEN] == '/'
+        && real[NIX_STORE_LEN + 1] != '\0')
+    {
+        // extract the hash from a path formatted like
+        // /nix/store/hashhere1234-pname-1.0.0
+        // +1 to skip the leading /
+        ptr1 = real + NIX_STORE_LEN + 1;
+
+        ptr2 = (u_char *) ngx_strchr(ptr1, '-');
+
+        if (ptr2 == NULL) {
+            ngx_free(real);
+            etag->hash = 0;
+            return NGX_ERROR;
+        }
+
+        *ptr2 = '\0';
+
+        // hash + content-length + quotes and hyphen. Note that the
+        // content-length part of the string can vary in length.
+        etag->value.data = ngx_pnalloc(r->pool, ngx_strlen(ptr1) + NGX_OFF_T_LEN + 3);
+
+        if (etag->value.data == NULL) {
+            ngx_free(real);
+            etag->hash = 0;
+            return NGX_ERROR;
+        }
+
+
+        // set value.data content to "{hash}-{content-length}" (including quote
+        // marks), and set value.len to the length of the resulting string
+        etag->value.len = ngx_sprintf(etag->value.data, "\"\%s-%xO\"",
+                                      ptr1,
+                                      r->headers_out.content_length_n)
+                          - etag->value.data;
+
+        ngx_http_clear_last_modified(r);
+    } else {
+        // outside of Nix store, use the upstream Nginx logic for etags
+
+        etag->value.data = ngx_pnalloc(r->pool, NGX_OFF_T_LEN + NGX_TIME_T_LEN + 3);
+
+        if (etag->value.data == NULL) {
+            ngx_free(real);
+            etag->hash = 0;
+            return NGX_ERROR;
+        }
+
+        etag->value.len = ngx_sprintf(etag->value.data, "\"%xT-%xO\"",
+                                      r->headers_out.last_modified_time,
+                                      r->headers_out.content_length_n)
+                          - etag->value.data;
     }
 
-    etag->value.len = ngx_sprintf(etag->value.data, "\"%xT-%xO\"",
-                                  r->headers_out.last_modified_time,
-                                  r->headers_out.content_length_n)
-                      - etag->value.data;
+    ngx_free(real);
 
     r->headers_out.etag = etag;