* released 1.1.11
* fixed multi-cookie handling in client request to allow clean deletion in insert+indirect mode. Now, only the server cookie is deleted and not all the header. Should now be compliant to RFC2109. * added a "nocache" option to "cookie" to specify that we explicitly want to add a "cache-control" header when we add a cookie. It is also possible to add an "Expires: <old-date>" to keep compatibility with old/broken caches. * some doc and examples cleanups
This commit is contained in:
parent
96d4037194
commit
240afa6d23
10
NOTES
10
NOTES
@ -12,10 +12,8 @@
|
||||
* full HTTP log with destination server ID, req and resp time.
|
||||
* source address of outgoing connections
|
||||
1.1.8 -> 1.1.9
|
||||
- handle parametrable HTTP health-checks replies
|
||||
- differentiate http headers and http uris
|
||||
- support environment variables in config file
|
||||
- support keep-alive
|
||||
1.1.9 -> 1.1.10
|
||||
- automatically remove client cookie in insert+indirect mode
|
||||
|
||||
* automatically remove client cookie in insert+indirect mode
|
||||
1.1.10 -> 1.1.11
|
||||
* support multi-cookie as described in RFC2109
|
||||
* added "nocache" option to prevent caches from storing cookies.
|
||||
|
9
TODO
9
TODO
@ -18,5 +18,12 @@
|
||||
- compter les matches
|
||||
- si match(n) & ([n].cpt > [n-1].cpt) & ([n].action == [n-1].action), swap(n,n-1)
|
||||
- régulièrement, diviser tous les compteurs (lors d'un dépassement par exemple)
|
||||
- filtrage sur l'adresse IP source
|
||||
- filtrage sur l'adresse IP source, et stocker le pointeur sur la dernière regex
|
||||
matchée dans la "session" pour accélérer les regex.
|
||||
- gestion keep-alive
|
||||
|
||||
- handle parametrable HTTP health-checks replies
|
||||
- differentiate http headers and http uris
|
||||
- support environment variables in config file
|
||||
- support keep-alive
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
|
||||
H A - P r o x y
|
||||
---------------
|
||||
version 1.1.10
|
||||
version 1.1.11
|
||||
willy tarreau
|
||||
2002/05/10
|
||||
2002/06/11
|
||||
|
||||
================
|
||||
| Introduction |
|
||||
@ -417,7 +417,29 @@ Ce param
|
||||
'balance'.
|
||||
|
||||
|
||||
2.8) Définition du nom du cookie
|
||||
2.8) Adresse de sortie
|
||||
----------------------
|
||||
Il est possible de forcer l'adresse utilisée pour établir les connexions
|
||||
vers les serveurs à l'aide du paramètre "source". Il est même possible de
|
||||
forcer le port, bien que cette fonctionnalité se limite à des usages très
|
||||
spécifiques. C'est particulièrement utile en cas d'adressage multiple, et
|
||||
plus généralement pour permettre aux serveurs de trouver le chemin de
|
||||
retour dans des contextes de routage difficiles. Si l'adresse est 0.0.0.0,
|
||||
elle sera choisie librement par le systeme. Si le port est 0, il
|
||||
sera choisi librement par le système.
|
||||
|
||||
Exemples :
|
||||
----------
|
||||
listen http_proxy 0.0.0.0:80
|
||||
# toutes les connexions prennent l'adresse 192.168.1.200
|
||||
source 192.168.1.200:0
|
||||
|
||||
listen rlogin_proxy 0.0.0.0:513
|
||||
# utiliser l'adresse 192.168.1.200 et le port réservé 900
|
||||
source 192.168.1.200:900
|
||||
|
||||
|
||||
2.9) Définition du nom du cookie
|
||||
--------------------------------
|
||||
En mode HTTP, il est possible de rechercher la valeur d'un cookie pour savoir
|
||||
vers quel serveur aiguiller la requête utilisateur. Le nom du cookie est donné
|
||||
@ -433,7 +455,8 @@ On peut modifier l'utilisation du cookie pour la rendre plus intelligente
|
||||
vis-à-vis des applications relayées. Il est possible, notamment de supprimer ou
|
||||
réécrire un cookie retourné par un serveur accédé en direct, et d'insérer un
|
||||
cookie dans une réponse HTTP adressée à un serveur sélectionné en répartition
|
||||
de charge.
|
||||
de charge, et même de signaler aux proxies amont de ne pas cacher le cookie
|
||||
inséré.
|
||||
|
||||
Exemples :
|
||||
----------
|
||||
@ -455,6 +478,12 @@ serveurs aient un cookie renseign
|
||||
|
||||
cookie SERVERID insert
|
||||
|
||||
Pour insérer un cookie, en s'assurant qu'un cache en amont ne le stockera pas,
|
||||
ajouter le mot clé 'nocache' après 'insert' :
|
||||
|
||||
cookie SERVERID insert nocache
|
||||
|
||||
|
||||
Remarques :
|
||||
-----------
|
||||
- Il est possible de combiner 'insert' avec 'indirect' ou 'rewrite' pour s'adapter
|
||||
@ -463,8 +492,13 @@ Remarques :
|
||||
- dans le cas où 'insert' et 'indirect' sont spécifiés, le cookie n'est jamais
|
||||
transmis au serveur vu qu'il n'en a pas connaissance et ne pourrait pas le
|
||||
comprendre.
|
||||
- il est particulièrement recommandé d'utiliser 'nocache' en mode insertion si
|
||||
des caches peuvent se trouver entre les clients et l'instance du proxy. Dans
|
||||
le cas contraire, un cache HTTP 1.0 pourrait cacher la réponse, incluant le
|
||||
cookie de persistence inséré, donc provoquer des changements de serveurs pour
|
||||
des clients partageant le même cache.
|
||||
|
||||
2.9) Assignation d'un serveur à une valeur de cookie
|
||||
2.10) Assignation d'un serveur à une valeur de cookie
|
||||
----------------------------------------------------
|
||||
En mode HTTP, il est possible d'associer des serveurs à des valeurs de
|
||||
cookie par le paramètre 'server'. La syntaxe est :
|
||||
@ -536,10 +570,11 @@ Exemples :
|
||||
server web2 192.168.1.2:80 cookie server02 check inter 500 rise 1 fall 2
|
||||
|
||||
# Insertion automatique de cookie dans la réponse du serveur, et suppression
|
||||
# automatique dans la requête.
|
||||
# automatique dans la requête, tout en indiquant aux caches de ne pas garder
|
||||
# ce cookie.
|
||||
listen web_appl 0.0.0.0:80
|
||||
mode http
|
||||
cookie SERVERID insert indirect
|
||||
cookie SERVERID insert nocache indirect
|
||||
balance roundrobin
|
||||
server web1 192.168.1.1:80 cookie server01 check
|
||||
server web2 192.168.1.2:80 cookie server02 check
|
||||
@ -667,7 +702,7 @@ La syntaxe est :
|
||||
reqallow <search> autoriser une requête qui valide <search>
|
||||
reqiallow <search> idem sans distinction majuscules/minuscules
|
||||
reqdeny <search> interdire une requête qui valide <search>
|
||||
reqdeny <search> idem sans distinction majuscules/minuscules
|
||||
reqideny <search> idem sans distinction majuscules/minuscules
|
||||
|
||||
rspadd <string> pour ajouter un en-tête dans la réponse
|
||||
rsprep <search> <replace> pour modifier la réponse
|
||||
@ -733,14 +768,15 @@ permet d'assurer une persistence dans les sessions HTTP d'une mani
|
||||
pratiquement transparente pour les applications. Le principe est simple :
|
||||
- attribuer une valeur d'un cookie à chaque serveur
|
||||
- effectuer une répartition interne
|
||||
- insérer un cookie dans les réponses issues d'une répartition uniquement
|
||||
- cacher ce cookie à l'application
|
||||
- insérer un cookie dans les réponses issues d'une répartition uniquement,
|
||||
et faire en sorte que des caches ne mémorisent pas ce cookie.
|
||||
- cacher ce cookie à l'application lors des requêtes ultérieures.
|
||||
|
||||
Exemple :
|
||||
-------
|
||||
listen application 0.0.0.0:80
|
||||
mode http
|
||||
cookie SERVERID insert indirect
|
||||
cookie SERVERID insert nocache indirect
|
||||
balance roundrobin
|
||||
server 192.168.1.1:80 cookie server01 check
|
||||
server 192.168.1.2:80 cookie server02 check
|
||||
@ -772,7 +808,6 @@ if [ -e /proc/sys/net/ipv4/netfilter/ip_ct_tcp_timeout_fin_wait ]; then
|
||||
fi
|
||||
|
||||
echo 1024 60999 > /proc/sys/net/ipv4/ip_local_port_range
|
||||
echo 32768 > /proc/sys/net/ipv4/ip_queue_maxlen
|
||||
echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout
|
||||
echo 4096 > /proc/sys/net/ipv4/tcp_max_syn_backlog
|
||||
echo 262144 > /proc/sys/net/ipv4/tcp_max_tw_buckets
|
||||
|
63
examples/haproxy.cfg
Normal file
63
examples/haproxy.cfg
Normal file
@ -0,0 +1,63 @@
|
||||
global
|
||||
log 127.0.0.1 local0
|
||||
maxconn 4096
|
||||
chroot /tmp
|
||||
uid 11
|
||||
gid 2
|
||||
daemon
|
||||
#debug
|
||||
#quiet
|
||||
|
||||
listen appli1-rewrite 0.0.0.0:10001
|
||||
log global
|
||||
mode http
|
||||
option httplog
|
||||
option dontlognull
|
||||
cookie SERVERID rewrite
|
||||
balance roundrobin
|
||||
server app1_1 192.168.34.23:8080 cookie app1inst1 check inter 2000 rise 2 fall 5
|
||||
server app1_2 192.168.34.32:8080 cookie app1inst2 check inter 2000 rise 2 fall 5
|
||||
server app1_3 192.168.34.27:8080 cookie app1inst3 check inter 2000 rise 2 fall 5
|
||||
server app1_4 192.168.34.42:8080 cookie app1inst4 check inter 2000 rise 2 fall 5
|
||||
retries 3
|
||||
redispatch
|
||||
maxconn 2000
|
||||
contimeout 5000
|
||||
clitimeout 50000
|
||||
srvtimeout 50000
|
||||
|
||||
listen appli2-insert 0.0.0.0:10002
|
||||
log global
|
||||
mode http
|
||||
option httplog
|
||||
option dontlognull
|
||||
balance roundrobin
|
||||
cookie SERVERID insert indirect nocache
|
||||
server inst1 192.168.114.56:80 cookie server01 check inter 2000 fall 3
|
||||
server inst2 192.168.114.56:81 cookie server02 check inter 2000 fall 3
|
||||
retries 3
|
||||
redispatch
|
||||
maxconn 2000
|
||||
contimeout 5000
|
||||
clitimeout 50000
|
||||
srvtimeout 50000
|
||||
|
||||
reqidel ^Connection: # desactivation du keep-alive
|
||||
reqadd Connection:\ close
|
||||
rspidel ^Connection:
|
||||
rspadd Connection:\ close
|
||||
rspidel ^Set-cookie:\ IP= # ne pas laisser sortir une adresse privee
|
||||
|
||||
listen appli3-relais 0.0.0.0:10003
|
||||
log global
|
||||
mode http
|
||||
option httplog
|
||||
option dontlognull
|
||||
dispatch 192.168.135.17:80
|
||||
retries 3
|
||||
redispatch
|
||||
maxconn 2000
|
||||
contimeout 5000
|
||||
clitimeout 50000
|
||||
srvtimeout 50000
|
||||
|
@ -17,7 +17,7 @@ if [ -e /proc/sys/net/ipv4/netfilter/ip_conntrack_tcp_timeout_fin_wait ]; then
|
||||
fi
|
||||
|
||||
echo 1024 60999 > /proc/sys/net/ipv4/ip_local_port_range
|
||||
echo 32768 > /proc/sys/net/ipv4/ip_queue_maxlen
|
||||
#echo 32768 > /proc/sys/net/ipv4/ip_queue_maxlen
|
||||
echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout
|
||||
echo 4096 > /proc/sys/net/ipv4/tcp_max_syn_backlog
|
||||
echo 262144 > /proc/sys/net/ipv4/tcp_max_tw_buckets
|
||||
|
147
haproxy.c
147
haproxy.c
@ -17,6 +17,14 @@
|
||||
*
|
||||
* ChangeLog :
|
||||
*
|
||||
* 2002/06/04 : 1.1.11
|
||||
* - fixed multi-cookie handling in client request to allow clean deletion
|
||||
* in insert+indirect mode. Now, only the server cookie is deleted and not
|
||||
* all the header. Should now be compliant to RFC2109.
|
||||
* - added a "nocache" option to "cookie" to specify that we explicitly want
|
||||
* to add a "cache-control" header when we add a cookie.
|
||||
* It is also possible to add an "Expires: <old-date>" to keep compatibility
|
||||
* with old/broken caches.
|
||||
* 2002/05/10 : 1.1.10
|
||||
* - if a cookie is used in insert+indirect mode, it's desirable that the
|
||||
* the servers don't see it. It was not possible to remove it correctly
|
||||
@ -145,8 +153,8 @@
|
||||
#include <linux/netfilter_ipv4.h>
|
||||
#endif
|
||||
|
||||
#define HAPROXY_VERSION "1.1.9"
|
||||
#define HAPROXY_DATE "2002/04/19"
|
||||
#define HAPROXY_VERSION "1.1.11"
|
||||
#define HAPROXY_DATE "2002/06/04"
|
||||
|
||||
/* this is for libc5 for example */
|
||||
#ifndef TCP_NODELAY
|
||||
@ -313,6 +321,7 @@ int strlcpy(char *dst, const char *src, int size) {
|
||||
#define PR_O_FWDFOR 128 /* insert x-forwarded-for with client address */
|
||||
#define PR_O_BIND_SRC 256 /* bind to a specific source address when connect()ing */
|
||||
#define PR_O_NULLNOLOG 512 /* a connect without request will not be logged */
|
||||
#define PR_O_COOK_NOC 1024 /* add a 'Cache-control' header with the cookie */
|
||||
|
||||
|
||||
/* various session flags */
|
||||
@ -2021,7 +2030,9 @@ int buffer_replace(struct buffer *b, char *pos, char *end, char *str) {
|
||||
return delta;
|
||||
}
|
||||
|
||||
/* same except that the string len is given */
|
||||
/* same except that the string len is given, which allows str to be NULL if
|
||||
* len is 0.
|
||||
*/
|
||||
int buffer_replace2(struct buffer *b, char *pos, char *end, char *str, int len) {
|
||||
int delta;
|
||||
|
||||
@ -2034,7 +2045,8 @@ int buffer_replace2(struct buffer *b, char *pos, char *end, char *str, int len)
|
||||
memmove(end + delta, end, b->data + b->l - end);
|
||||
|
||||
/* now, copy str over pos */
|
||||
memcpy(pos, str,len);
|
||||
if (len)
|
||||
memcpy(pos, str, len);
|
||||
|
||||
/* we only move data after the displaced zone */
|
||||
if (b->r > pos) b->r += delta;
|
||||
@ -2247,43 +2259,59 @@ int process_cli(struct session *t) {
|
||||
*ptr = term; /* restore the string terminator */
|
||||
}
|
||||
|
||||
/* now look for cookies */
|
||||
if (!delete_header && (req->r >= req->h + 8) && (t->proxy->cookie_name != NULL)
|
||||
/* Now look for cookies. Conforming to RFC2109, we have to support
|
||||
* attributes whose name begin with a '$', and associate them with
|
||||
* the right cookie, if we want to delete this cookie.
|
||||
* So there are 3 cases for each cookie read :
|
||||
* 1) it's a special attribute, beginning with a '$' : ignore it.
|
||||
* 2) it's a server id cookie that we *MAY* want to delete : save
|
||||
* some pointers on it (last semi-colon, beginning of cookie...)
|
||||
* 3) it's an application cookie : we *MAY* have to delete a previous
|
||||
* "special" cookie.
|
||||
* At the end of loop, if a "special" cookie remains, we may have to
|
||||
* remove it. If no application cookie persists in the header, we
|
||||
* *MUST* delete it
|
||||
*/
|
||||
if (!delete_header && (t->proxy->cookie_name != NULL)
|
||||
&& !(t->flags & SN_CLDENY) && (ptr >= req->h + 8)
|
||||
&& (strncmp(req->h, "Cookie: ", 8) == 0)) {
|
||||
char *p1, *p2, *p3, *p4;
|
||||
|
||||
char *del_colon, *del_cookie, *colon;
|
||||
int app_cookies;
|
||||
|
||||
p1 = req->h + 8; /* first char after 'Cookie: ' */
|
||||
colon = p1;
|
||||
/* del_cookie == NULL => nothing to be deleted */
|
||||
del_colon = del_cookie = NULL;
|
||||
app_cookies = 0;
|
||||
|
||||
while (p1 < ptr) {
|
||||
while (p1 < ptr && (isspace((int)*p1) || *p1 == ';'))
|
||||
/* skip spaces and colons, but keep an eye on these ones */
|
||||
while (p1 < ptr) {
|
||||
if (*p1 == ';' || *p1 == ',')
|
||||
colon = p1;
|
||||
else if (!isspace((int)*p1))
|
||||
break;
|
||||
p1++;
|
||||
}
|
||||
|
||||
if (p1 == ptr)
|
||||
break;
|
||||
else if (*p1 == ';') { /* next cookie */
|
||||
++p1;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* p1 is at the beginning of the cookie name */
|
||||
p2 = p1;
|
||||
|
||||
while (p2 < ptr && *p2 != '=' && *p2 != ';')
|
||||
while (p2 < ptr && *p2 != '=')
|
||||
p2++;
|
||||
|
||||
if (p2 == ptr)
|
||||
break;
|
||||
else if (*p2 == ';') { /* next cookie */
|
||||
p1=++p2;
|
||||
continue;
|
||||
}
|
||||
|
||||
p3 = p2 + 1; /* skips the '=' sign */
|
||||
if (p3 == ptr)
|
||||
break;
|
||||
|
||||
p4=p3;
|
||||
while (p4 < ptr && !isspace((int)*p4) && *p4 != ';')
|
||||
p4 = p3;
|
||||
while (p4 < ptr && !isspace((int)*p4) && *p4 != ';' && *p4 != ',')
|
||||
p4++;
|
||||
|
||||
/* here, we have the cookie name between p1 and p2,
|
||||
@ -2291,8 +2319,11 @@ int process_cli(struct session *t) {
|
||||
* we can process it.
|
||||
*/
|
||||
|
||||
if ((p2 - p1 == strlen(t->proxy->cookie_name)) &&
|
||||
(strncmp(p1, t->proxy->cookie_name, p2 - p1) == 0)) {
|
||||
if (*p1 == '$') {
|
||||
/* skip this one */
|
||||
}
|
||||
else if ((p2 - p1 == strlen(t->proxy->cookie_name)) &&
|
||||
(memcmp(p1, t->proxy->cookie_name, p2 - p1) == 0)) {
|
||||
/* Cool... it's the right one */
|
||||
struct server *srv = t->proxy->srv;
|
||||
|
||||
@ -2304,31 +2335,64 @@ int process_cli(struct session *t) {
|
||||
if (srv) { /* we found the server */
|
||||
t->flags |= SN_DIRECT;
|
||||
t->srv = srv;
|
||||
/* if this cookie was set in insert+indirect mode, then it's better that the
|
||||
* server never sees it.
|
||||
*/
|
||||
if ((t->proxy->options & (PR_O_COOK_INS | PR_O_COOK_IND)) == (PR_O_COOK_INS | PR_O_COOK_IND))
|
||||
delete_header = 1;
|
||||
}
|
||||
|
||||
break;
|
||||
/* if this cookie was set in insert+indirect mode, then it's better that the
|
||||
* server never sees it.
|
||||
*/
|
||||
if (del_cookie == NULL &&
|
||||
(t->proxy->options & (PR_O_COOK_INS | PR_O_COOK_IND)) == (PR_O_COOK_INS | PR_O_COOK_IND)) {
|
||||
del_cookie = p1;
|
||||
del_colon = colon;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// fprintf(stderr,"Ignoring unknown cookie : ");
|
||||
// write(2, p1, p2-p1);
|
||||
// fprintf(stderr," = ");
|
||||
// write(2, p3, p4-p3);
|
||||
// fprintf(stderr,"\n");
|
||||
/* now we know that we must keep this cookie since it's
|
||||
* not ours. But if we wanted to delete our cookie
|
||||
* earlier, we cannot remove the complete header, but we
|
||||
* can remove the previous block itself.
|
||||
*/
|
||||
app_cookies++;
|
||||
|
||||
if (del_cookie != NULL) {
|
||||
buffer_replace2(req, del_cookie, p1, NULL, 0);
|
||||
p4 -= (p1 - del_cookie);
|
||||
ptr -= (p1 - del_cookie);
|
||||
del_cookie = del_colon = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* we'll have to look for another cookie ... */
|
||||
p1 = p4;
|
||||
} /* while (p1 < ptr) */
|
||||
} /* end of cookie processing */
|
||||
|
||||
/* There's no more cookie on this line.
|
||||
* We may have marked the last one(s) for deletion.
|
||||
* We must do this now in two ways :
|
||||
* - if there is no app cookie, we simply delete the header ;
|
||||
* - if there are app cookies, we must delete the end of the
|
||||
* string properly, including the colon/semi-colon before
|
||||
* the cookie name.
|
||||
*/
|
||||
if (del_cookie != NULL) {
|
||||
if (app_cookies) {
|
||||
buffer_replace2(req, del_colon, ptr, NULL, 0);
|
||||
/* WARNING! <ptr> becomes invalid for now. If some code
|
||||
* below needs to rely on it before the end of the global
|
||||
* header loop, we need to correct it with this code :
|
||||
* ptr = del_colon;
|
||||
*/
|
||||
}
|
||||
else
|
||||
delete_header = 1;
|
||||
}
|
||||
} /* end of cookie processing on this header */
|
||||
|
||||
/* let's look if we have to delete this header */
|
||||
if (delete_header && !(t->flags & SN_CLDENY)) {
|
||||
buffer_replace2(req, req->h, req->lr, "", 0);
|
||||
buffer_replace2(req, req->h, req->lr, NULL, 0);
|
||||
}
|
||||
/* WARNING: ptr is not valid anymore, since the header may have been deleted or truncated ! */
|
||||
|
||||
req->h = req->lr;
|
||||
} /* while (req->lr < req->r) */
|
||||
|
||||
@ -2634,6 +2698,9 @@ int process_srv(struct session *t) {
|
||||
*/
|
||||
len = sprintf(newhdr, "Set-Cookie: %s=%s; path=/\r\n",
|
||||
t->proxy->cookie_name, t->srv->cookie);
|
||||
if (t->proxy->options & PR_O_COOK_NOC)
|
||||
len += sprintf(newhdr + len, "Cache-control: no-cache=\"set-cookie\"\r\n");
|
||||
|
||||
buffer_replace2(rep, rep->h, rep->h, newhdr, len);
|
||||
}
|
||||
|
||||
@ -2731,8 +2798,9 @@ int process_srv(struct session *t) {
|
||||
}
|
||||
|
||||
/* check for server cookies */
|
||||
if (!delete_header && (t->proxy->options & PR_O_COOK_ANY) && (rep->r >= rep->h + 12) &&
|
||||
(t->proxy->cookie_name != NULL) && (strncmp(rep->h, "Set-Cookie: ", 12) == 0)) {
|
||||
if (!delete_header && (t->proxy->options & PR_O_COOK_ANY)
|
||||
&& (t->proxy->cookie_name != NULL) && (ptr >= rep->h + 12)
|
||||
&& (strncmp(rep->h, "Set-Cookie: ", 12) == 0)) {
|
||||
char *p1, *p2, *p3, *p4;
|
||||
|
||||
p1 = rep->h + 12; /* first char after 'Set-Cookie: ' */
|
||||
@ -3709,6 +3777,9 @@ int cfg_parse_listen(char *file, int linenum, char **args) {
|
||||
else if (!strcmp(args[cur_arg], "insert")) {
|
||||
curproxy->options |= PR_O_COOK_INS;
|
||||
}
|
||||
else if (!strcmp(args[cur_arg], "nocache")) {
|
||||
curproxy->options |= PR_O_COOK_NOC;
|
||||
}
|
||||
else {
|
||||
Alert("parsing [%s:%d] : <cookie> supports 'rewrite', 'insert' and 'indirect' options.\n",
|
||||
file, linenum);
|
||||
|
Loading…
x
Reference in New Issue
Block a user