@@ -23,19 +23,17 @@ import cors from "cors";
23
23
import http from "http" ;
24
24
import https from "https" ;
25
25
import { readFileSync } from "fs" ;
26
+ import { parse as parseContentType } from "content-type" ;
26
27
27
28
import { Rendezvous } from "./rendezvous" ;
28
- import { maxBytes , ttlSeconds , port } from "./config" ;
29
+ import { maxBytes , ttlSeconds , port , trustProxy } from "./config" ;
29
30
30
31
const app = express ( ) ;
31
- app . use ( cors ( {
32
- allowedHeaders : [ "Content-Type" , "If-Match" , "If-None-Match" ] ,
33
- exposedHeaders : [ "ETag" , "Location" , "X-Max-Bytes" ] ,
34
- } ) ) ;
35
32
app . use ( morgan ( "common" ) ) ;
36
33
app . set ( "env" , "production" ) ;
37
34
app . set ( "x-powered-by" , false ) ;
38
35
app . set ( "etag" , false ) ;
36
+ app . set ( "trust proxy" , trustProxy ) ;
39
37
40
38
// treat everything as raw
41
39
app . use ( bodyParser . raw ( {
@@ -51,59 +49,96 @@ const rvs = new NodeCache({
51
49
useClones : false ,
52
50
} ) ;
53
51
54
- app . post ( "/" , ( req , res ) => {
55
- let id : string | undefined ;
56
- while ( ! id || id in rvs ) {
57
- id = v4 ( ) ;
58
- }
59
- const rv = new Rendezvous ( id , ttlSeconds , maxBytes , req ) ;
60
- rvs . set ( id , rv , rv . ttlSeconds ) ;
52
+ app . options ( "/" , cors ( {
53
+ origin : "*" ,
54
+ methods : [ "GET" , "POST" , "PUT" , "DELETE" , "OPTIONS" ] ,
55
+ allowedHeaders : [ "X-Requested-With" , "Content-Type" , "Authorization" ] , // https://spec.matrix.org/v1.10/client-server-api/#web-browser-clients
56
+ exposedHeaders : [ "ETag" ] ,
57
+ } ) ) ;
61
58
62
- rv . setHeaders ( res ) ;
59
+ function notFound ( res : express . Response ) : express . Response {
60
+ return res . status ( 404 ) . json ( { "errcode" : "M_NOT_FOUND" , "error" : "Rendezvous not found" } ) ;
61
+ }
63
62
64
- res . setHeader ( "Location" , id ) ;
65
- res . setHeader ( "X-Max-Bytes" , maxBytes ) ;
63
+ function withContentType ( req : express . Request , res : express . Response ) {
64
+ return ( fn : ( contentType : string ) => express . Response ) : express . Response => {
65
+ const contentType = req . get ( "content-type" ) ;
66
+ if ( ! contentType ) {
67
+ return res . status ( 400 ) . json ( { "errcode" : "M_MISSING_PARAM" , "error" : "Missing Content-Type header" } ) ;
68
+ }
69
+ try {
70
+ parseContentType ( contentType ) ;
71
+ } catch ( e ) {
72
+ return res . status ( 400 ) . json ( { "errcode" : "M_INVALID_PARAM" , "error" : "Invalid Content-Type header" } ) ;
73
+ }
74
+ return fn ( contentType ) ;
75
+ } ;
76
+ }
77
+ app . post ( "/" , ( req , res ) => {
78
+ withContentType ( req , res ) ( ( contentType ) => {
66
79
67
- return res . sendStatus ( 201 ) ;
68
- } ) ;
80
+ let id : string | undefined ;
81
+ while ( ! id || id in rvs ) {
82
+ id = v4 ( ) ;
83
+ }
84
+ const rv = new Rendezvous ( id , ttlSeconds , maxBytes , req . body , contentType ) ;
85
+ rvs . set ( id , rv , rv . ttlSeconds ) ;
69
86
70
- app . put ( "/:id" , ( req , res ) => {
71
- const { id } = req . params ;
72
- const rv = rvs . get < Rendezvous > ( id ) ;
87
+ rv . setHeaders ( res ) ;
73
88
74
- if ( ! rv ) {
75
- return res . sendStatus ( 404 ) ;
76
- }
89
+ const url = `${ req . protocol } ://${ req . hostname } /${ id } ` ;
77
90
78
- if ( rv . expired ( ) ) {
79
- rvs . del ( id ) ;
80
- return res . sendStatus ( 404 ) ;
81
- }
91
+ return res . status ( 201 ) . json ( { url } ) ;
92
+ } ) ;
93
+ } ) ;
82
94
83
- const ifMatch = req . headers [ "if-match" ] ;
95
+ app . options ( "/:id" , cors ( {
96
+ origin : "*" ,
97
+ methods : [ "GET" , "PUT" , "DELETE" , "OPTIONS" ] ,
98
+ allowedHeaders : [ "If-Match" , "If-None-Match" ] ,
99
+ exposedHeaders : [ "ETag" ] ,
100
+ } ) ) ;
84
101
85
- if ( ifMatch && ifMatch !== rv . etag ) {
102
+ app . put ( "/:id" , ( req , res ) => {
103
+ withContentType ( req , res ) ( ( contentType ) => {
104
+ const { id } = req . params ;
105
+ const rv = rvs . get < Rendezvous > ( id ) ;
106
+
107
+ if ( ! rv ) {
108
+ return notFound ( res ) ;
109
+ }
110
+
111
+ if ( rv . expired ( ) ) {
112
+ rvs . del ( id ) ;
113
+ return notFound ( res ) ;
114
+ }
115
+
116
+ const ifMatch = req . headers [ "if-match" ] ;
117
+ if ( ! ifMatch ) {
118
+ return res . status ( 400 ) . json ( { "errcode" : "M_MISSING_PARAM" , "error" : "Missing If-Match header" } ) ;
119
+ } else if ( ifMatch !== rv . etag ) {
120
+ rv . setHeaders ( res ) ;
121
+ return res . send ( 412 ) . json ( { "errcode" : "M_CONCURRENT_WRITE" , "error" : "Rendezvous has been modified" } ) ;
122
+ }
123
+
124
+ rv . update ( req . body , contentType ) ;
86
125
rv . setHeaders ( res ) ;
87
- return res . sendStatus ( 412 ) ;
88
- }
89
126
90
- rv . update ( req ) ;
91
- rv . setHeaders ( res ) ;
92
-
93
- return res . sendStatus ( 202 ) ;
127
+ return res . sendStatus ( 202 ) ;
128
+ } ) ;
94
129
} ) ;
95
130
96
131
app . get ( "/:id" , ( req , res ) => {
97
132
const { id } = req . params ;
98
133
const rv = rvs . get < Rendezvous > ( id ) ;
99
134
100
135
if ( ! rv ) {
101
- return res . sendStatus ( 404 ) ;
136
+ return notFound ( res ) ;
102
137
}
103
138
104
139
if ( rv . expired ( ) ) {
105
140
rvs . del ( id ) ;
106
- return res . sendStatus ( 404 ) ;
141
+ return notFound ( res ) ;
107
142
}
108
143
109
144
rv . setHeaders ( res ) ;
@@ -118,7 +153,7 @@ app.get("/:id", (req, res) => {
118
153
app . delete ( "/:id" , ( req , res ) => {
119
154
const { id } = req . params ;
120
155
if ( ! rvs . has ( id ) ) {
121
- return res . sendStatus ( 404 ) ;
156
+ return notFound ( res ) ;
122
157
}
123
158
rvs . del ( id ) ;
124
159
return res . sendStatus ( 204 ) ;
@@ -130,11 +165,11 @@ if (process.env.DEV_SSL === "yes") {
130
165
cert : readFileSync ( "./devssl/cert.pem" ) ,
131
166
} , app ) ;
132
167
httpsServer . listen ( port , ( ) => {
133
- console . log ( `Starting rendezvous server at https://0.0.0.0:${ port } with self-signed certificate` ) ;
168
+ console . log ( `Starting rendezvous server at https://0.0.0.0:${ port } with self-signed certificate${ trustProxy ? " behind trusted proxy" : "" } ` ) ;
134
169
} ) ;
135
170
} else {
136
171
const httpServer = http . createServer ( app ) ;
137
172
httpServer . listen ( port , ( ) => {
138
- console . log ( `Starting rendezvous server at http://0.0.0.0:${ port } ` ) ;
173
+ console . log ( `Starting rendezvous server at http://0.0.0.0:${ port } ${ trustProxy ? " behind trusted proxy" : "" } ` ) ;
139
174
} ) ;
140
175
}
0 commit comments