Annotation of parser3/src/main/pa_common.C, revision 1.152
1.15 paf 1: /** @file
1.16 paf 2: Parser: commonly functions.
3:
1.143 paf 4: Copyright(c) 2001, 2003 ArtLebedev Group (http://www.artlebedev.com)
1.101 paf 5: Author: Alexandr Petrosian <paf@design.ru> (http://paf.design.ru)
1.111 paf 6: */
1.16 paf 7:
1.152 ! paf 8: static const char* IDENT_COMMON_C="$Date: 2003/05/30 10:45:37 $";
1.1 paf 9:
10: #include "pa_common.h"
1.4 paf 11: #include "pa_exception.h"
1.14 paf 12: #include "pa_globals.h"
1.126 paf 13: #include "pa_hash.h"
1.152 ! paf 14: #include "pa_table.h"
1.126 paf 15: #include "pa_vstring.h"
1.135 paf 16: #include "pa_vdate.h"
1.152 ! paf 17: #include "pa_vhash.h"
! 18: #include "pa_vtable.h"
1.1 paf 19:
1.98 paf 20: #ifdef WIN32
21: # include <windows.h>
1.126 paf 22: #else
23: # define closesocket close
1.98 paf 24: #endif
25:
1.93 paf 26: // some maybe-undefined constants
27:
1.82 paf 28: #ifndef _O_TEXT
29: # define _O_TEXT 0
30: #endif
31: #ifndef _O_BINARY
32: # define _O_BINARY 0
1.47 paf 33: #endif
1.80 paf 34:
1.138 paf 35: #ifdef HAVE_FTRUNCATE
36: # define PA_O_TRUNC 0
37: #else
38: # ifdef _O_TRUNC
39: # define PA_O_TRUNC _O_TRUNC
40: # else
41: # error you must have either ftruncate function or _O_TRUNC bit declared
42: # endif
43: #endif
44:
1.93 paf 45: // locking constants
46:
1.99 paf 47: #ifdef HAVE_FLOCK
48:
49: static int lock_shared_blocking(int fd) { return flock(fd, LOCK_SH); }
50: static int lock_exclusive_blocking(int fd) { return flock(fd, LOCK_EX); }
51: static int lock_exclusive_nonblocking(int fd) { return flock(fd, LOCK_EX || LOCK_NB); }
52: static int unlock(int fd) { return flock(fd, LOCK_UN); }
53:
1.98 paf 54: #else
1.99 paf 55: #ifdef HAVE__LOCKING
1.98 paf 56:
1.126 paf 57: #define FLOCK(operation) lseek(fd, 0, SEEK_SET); return _locking(fd, operation, 1)
1.99 paf 58: static int lock_shared_blocking(int fd) { FLOCK(_LK_LOCK); }
59: static int lock_exclusive_blocking(int fd) { FLOCK(_LK_LOCK); }
60: static int lock_exclusive_nonblocking(int fd) { FLOCK(_LK_NBLCK); }
61: static int unlock(int fd) { FLOCK(_LK_UNLCK); }
1.93 paf 62:
1.99 paf 63: #else
64: #ifdef HAVE_FCNTL
1.93 paf 65:
1.126 paf 66: #define FLOCK(cmd, arg) struct flock ls={arg, SEEK_SET}; return fcntl(fd, cmd, &ls)
1.99 paf 67: static int lock_shared_blocking(int fd) { FLOCK(F_SETLKW, F_RDLCK); }
68: static int lock_exclusive_blocking(int fd) { FLOCK(F_SETLKW, F_WRLCK); }
69: static int lock_exclusive_nonblocking(int fd) { FLOCK(F_SETLK, F_RDLCK); }
70: static int unlock(int fd) { FLOCK(F_SETLK, F_UNLCK); }
1.93 paf 71:
72: #else
73: #ifdef HAVE_LOCKF
1.99 paf 74:
1.126 paf 75: #define FLOCK(fd, operation) lseek(fd, 0, SEEK_SET); return lockf(fd, operation, 1)
1.99 paf 76: static int lock_shared_blocking(int fd) { FLOCK(F_LOCK); } // on intel solaris man doesn't have doc on shared blocking
77: static int lock_exclusive_blocking(int fd) { FLOCK(F_LOCK); }
78: static int lock_exclusive_nonblocking(int fd) { FLOCK(F_TLOCK); }
79: static int unlock(int fd) { FLOCK(F_TLOCK); }
80:
1.93 paf 81: #else
1.99 paf 82:
83: #error unable to find file locking func
84:
85: #endif
1.93 paf 86: #endif
87: #endif
88: #endif
89:
1.127 paf 90: #define DEFAULT_USER_AGENT "parser3"
91:
92:
1.126 paf 93: void fix_line_breaks(char* buf, size_t& size) {
1.139 paf 94: if(size==0)
95: return;
96:
1.87 paf 97: //_asm int 3;
1.126 paf 98: const char* const eob=buf+size;
99: char* dest=buf;
1.72 parser 100: // fix DOS: \r\n -> \n
101: // fix Macintosh: \r -> \n
1.126 paf 102: char* bol=buf;
1.137 paf 103: while(char* eol=(char*)memchr(bol, '\r', eob -bol)) {
1.72 parser 104: size_t len=eol-bol;
105: if(dest!=bol)
1.126 paf 106: memcpy(dest, bol, len);
1.72 parser 107: dest+=len;
1.126 paf 108: *dest++='\n';
1.72 parser 109:
1.126 paf 110: if(&eol[1]<eob && eol[1]=='\n') { // \r, \n = DOS
1.72 parser 111: bol=eol+2;
1.126 paf 112: size--;
113: } else // \r, not \n = Macintosh
1.72 parser 114: bol=eol+1;
115: }
116: // last piece without \r, including terminating 0
117: if(dest!=bol)
1.126 paf 118: memcpy(dest, bol, eob-bol);
1.72 parser 119: }
1.18 paf 120:
1.126 paf 121: char* file_read_text(Pool& pool, const String& file_spec,
122: bool fail_on_read_problem,
123: Hash *params, Hash** out_fields) {
1.72 parser 124: void *result; size_t size;
1.126 paf 125: return file_read(pool, file_spec, result, size, true, params, out_fields, fail_on_read_problem)?(char *)result:0;
126: }
127:
128: //http request stuff
129: /* ************************ http stuff *********************** */
130:
131: static bool set_addr(struct sockaddr_in *addr, const char* host, const short port){
132: memset(addr, 0, sizeof(*addr));
133: addr->sin_family=AF_INET;
134: addr->sin_port=htons(port);
135: if(host) {
136: if(struct hostent *hostIP=gethostbyname(host))
137: memcpy(&addr->sin_addr, hostIP->h_addr, hostIP->h_length);
138: else
139: return false;
140: } else
141: addr->sin_addr.s_addr=INADDR_ANY;
142: return true;
143: }
144:
1.142 paf 145: static int http_read_response(String& response, int sock, bool fail_on_status_ne_200){
146: const String* status_code=0;
1.130 paf 147: ssize_t EOLat=0;
1.126 paf 148: while(true) {
149: char *buf=(char *)response.pool().malloc(MAX_STRING);
150: ssize_t size=recv(sock, buf, MAX_STRING, 0);
151: if(size<=0)
152: break;
1.130 paf 153: response.APPEND_TAINTED(buf, size, "remote HTTP server response", 0);
1.142 paf 154: if(!status_code && (EOLat=response.pos("\r\n", 2))>=0) { // checking status in first response
155: const String& status_line=response.mid(0, (size_t)EOLat);
156: Array astatus(response.pool());
157: size_t pos_after_ref=0; status_line.split(astatus, &pos_after_ref, " ", 1);
158: status_code=astatus.get_string(1);
159:
160: if(fail_on_status_ne_200 && *status_code!="200")
161: throw Exception("http.status",
162: status_code,
163: "invalid HTTP response status");
164: }
165: }
166: if(status_code)
167: return status_code->as_int();
168: else
169: throw Exception("http.response",
170: 0,
171: "bad response from host - no status found (size=%lu)", response.size());
1.126 paf 172: }
173:
174: /* ********************** request *************************** */
175:
176: #if defined(SIGALRM) && defined(HAVE_SIGSETJMP) && defined(HAVE_SIGLONGJMP)
1.145 paf 177: # define PA_USE_ALARM
1.126 paf 178: #endif
179:
1.145 paf 180: #ifdef PA_USE_ALARM
1.126 paf 181: static sigjmp_buf timeout_env;
182: static void timeout_handler(int sig){
183: siglongjmp(timeout_env, 1);
184: }
185: #endif
186:
1.142 paf 187: static int http_request(String& response,
1.152 ! paf 188: const String *origin_string,
! 189: const char* host, int port,
! 190: const char* request,
! 191: int timeout,
! 192: bool fail_on_status_ne_200) {
1.126 paf 193: if(!host)
194: throw Exception("http.host",
195: origin_string,
196: "zero hostname"); //never
197:
1.145 paf 198: #ifdef PA_USE_ALARM
1.146 paf 199: signal(SIGALRM, timeout_handler);
1.126 paf 200: #endif
201: int sock=-1;
1.145 paf 202: #ifdef PA_USE_ALARM
203: if(sigsetjmp(timeout_env, 1)) {
204: // stupid gcc [2.95.4] generated bad code
205: // which failed to handle sigsetjmp+throw: crashed inside of pre-throw code.
206: // rewritten simplier [though duplicating closesocket code]
207: if(sock>=0)
208: closesocket(sock);
209: throw Exception("http.timeout",
210: origin_string,
211: "timeout occured while retrieving document");
1.146 paf 212: return 0; // never
1.145 paf 213: } else {
214: alarm(timeout);
215: #endif
1.146 paf 216: try {
217: int result;
1.126 paf 218: struct sockaddr_in dest;
1.146 paf 219:
220: if(!set_addr(&dest, host, port))
1.126 paf 221: throw Exception("http.host",
222: origin_string,
1.127 paf 223: "can not resolve hostname \"%s\"", host);
1.126 paf 224:
225: if((sock=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP/*0*/))<0)
226: throw Exception("http.connect",
227: origin_string,
1.127 paf 228: "can not make socket: %s (%d)", strerror(errno), errno);
1.126 paf 229: if(connect(sock, (struct sockaddr *)&dest, sizeof(dest)))
230: throw Exception("http.connect",
231: origin_string,
1.127 paf 232: "can not connect to host \"%s\": %s (%d)", host, strerror(errno), errno);
1.126 paf 233: size_t request_size=strlen(request);
234: if(send(sock, request, request_size, 0)!=(ssize_t)request_size)
235: throw Exception("http.connect",
236: origin_string,
1.127 paf 237: "error sending request: %s (%d)", strerror(errno), errno);
1.126 paf 238:
1.142 paf 239: result=http_read_response(response, sock, fail_on_status_ne_200);
240: closesocket(sock);
1.145 paf 241: #ifdef PA_USE_ALARM
1.142 paf 242: alarm(0);
1.126 paf 243: #endif
1.147 paf 244: return result;
1.146 paf 245: } catch(...) {
1.145 paf 246: #ifdef PA_USE_ALARM
1.146 paf 247: alarm(0);
1.126 paf 248: #endif
1.146 paf 249: if(sock>=0)
250: closesocket(sock);
251: /*re*/throw;
252: }
1.148 paf 253: #ifdef PA_USE_ALARM
1.126 paf 254: }
1.148 paf 255: #endif
1.126 paf 256: }
257:
1.127 paf 258: #ifndef DOXYGEN
259: struct Http_pass_header_info {
260: String* request;
261: bool user_agent_specified;
262: };
263: #endif
264: static void http_pass_header(const Hash::Key& key, Hash::Val *value, void *info)
1.126 paf 265: {
1.127 paf 266: Http_pass_header_info& i=*static_cast<Http_pass_header_info *>(info);
267: Pool& pool=i.request->pool();
268:
1.135 paf 269: *(i.request)<<key<<": "
1.136 paf 270: << attributed_meaning_to_string(*static_cast<Value *>(value), String::UL_HTTP_HEADER, false)
1.135 paf 271: <<"\n";
272:
1.127 paf 273: if(key.change_case(pool, String::CC_UPPER)=="USER-AGENT")
274: i.user_agent_specified=true;
1.126 paf 275: }
1.152 ! paf 276: /// @todo build .cookies field. use ^file.tables.SET-COOKIES.menu{ for now
1.126 paf 277: static void file_read_http(Pool& pool, const String& file_spec,
278: void*& data, size_t& data_size,
1.127 paf 279: Hash *options=0, Hash** out_fields=0) {
1.126 paf 280: char host[MAX_STRING];
1.129 paf 281: const char* uri;
1.126 paf 282: int port;
283: const char* method="GET";
284: int timeout=2;
1.142 paf 285: bool fail_on_status_ne_200=true;
1.127 paf 286: Value *vheaders=0;
1.126 paf 287:
1.144 paf 288: String& connect_string=*new(pool) String(pool); // must not be local [exception may be reported outside]
1.133 paf 289: // not in ^sql{... UL_SQL ...} spirit, but closer to ^file::load one
290: connect_string.append(file_spec, String::UL_URI); // tainted pieces -> URI pieces
291:
292: char* connect_string_cstr=connect_string.cstr(String::UL_UNSPECIFIED);
293: if(strncmp(connect_string_cstr, "http://", 7)!=0)
1.126 paf 294: throw Exception(0,
1.133 paf 295: &connect_string,
1.126 paf 296: "does not start with http://"); //never
1.133 paf 297: connect_string_cstr+=7;
1.126 paf 298:
1.133 paf 299: strncpy(host, connect_string_cstr, sizeof(host)-1); host[sizeof(host)-1]=0;
1.126 paf 300: char* host_uri=lsplit(host, '/');
1.133 paf 301: uri=host_uri?connect_string_cstr+(host_uri-1-host):"/";
1.126 paf 302: char* port_cstr=lsplit(host, ':');
303: char* error_pos=0;
304: port=port_cstr?strtol(port_cstr, &error_pos, 0):80;
305:
1.127 paf 306: if(options) {
307: int valid_options=0;
308: if(Value *vmethod=static_cast<Value *>(options->get(*http_method_name))) {
309: valid_options++;
310: method=vmethod->as_string().cstr();
311: }
312: if(Value *vtimeout=static_cast<Value *>(options->get(*http_timeout_name))) {
313: valid_options++;
314: timeout=vtimeout->as_int();
315: }
316: if(vheaders=static_cast<Value *>(options->get(*http_headers_name))) {
317: valid_options++;
318: }
1.142 paf 319: if(Value *vany_status=static_cast<Value *>(options->get(*http_any_status_name))) {
320: valid_options++;
321: fail_on_status_ne_200=!vany_status->as_bool();
322: }
323:
1.127 paf 324: if(valid_options!=options->size())
325: throw Exception("parser.runtime",
326: 0,
327: "invalid option passed");
1.133 paf 328: }
1.126 paf 329:
330: //making request
1.127 paf 331: String request(pool);
1.126 paf 332: request<< method <<" "<< uri <<" HTTP/1.0\nHost: "<< host<<"\n";
1.127 paf 333: bool user_agent_specified=false;
334: if(vheaders && !vheaders->is_string()) { // allow empty
1.133 paf 335: if(Hash *headers=vheaders->get_hash(&connect_string)) {
1.127 paf 336: Http_pass_header_info info={&request};
337: headers->for_each(http_pass_header, &info);
338: user_agent_specified=info.user_agent_specified;
339: } else
340: throw Exception("parser.runtime",
1.133 paf 341: &connect_string,
1.127 paf 342: "headers param must be hash");
343: };
344: if(!user_agent_specified) // defaulting
345: request << "user-agent: " DEFAULT_USER_AGENT "\n";
1.126 paf 346: request<<"\n";
347:
348: //sending request
349: String response(pool);
1.142 paf 350: int status_code=http_request(response,
351: &connect_string, host, port, request.cstr(String::UL_UNSPECIFIED),
352: timeout, fail_on_status_ne_200);
1.126 paf 353:
354: //processing results
355: int pos=response.pos("\r\n\r\n", 4);
356: if(pos<1){
357: throw Exception("http.response",
1.133 paf 358: &connect_string,
1.126 paf 359: "bad response from host - no headers found");
360: }
361: String header_block=response.mid(0, pos);
362: String body=response.mid(pos+4, response.size());
363:
364: Array aheaders(pool);
365: Hash& headers=*new(pool) Hash(pool);
1.152 ! paf 366: VHash* vtables=new(pool) VHash(pool);
! 367: headers.put(*http_tables_name, vtables);
! 368: Hash& tables=vtables->hash(0);
! 369:
1.126 paf 370: size_t pos_after_ref=0;
371: header_block.split(aheaders, &pos_after_ref, "\r\n", 2);
372:
373: //processing headers
374: for(int i=1;i<aheaders.size();i++) {
375: if(const String *line=aheaders.get_string(i)) {
376: pos=line->pos(": ", 2);
377: if(pos<1)
378: throw Exception("http.response",
1.133 paf 379: &connect_string,
1.126 paf 380: "bad response from host - bad header \"%s\"", line->cstr());
1.152 ! paf 381:
! 382: const String& sname=line->mid(0, pos).change_case(pool, String::CC_UPPER);
! 383: const String& string=line->mid(pos+2, line->size());
! 384:
! 385: // tables
! 386: {
! 387: Value *valready=(Value *)tables.get(sname);
! 388: bool existed=valready!=0;
! 389: Table *table;
! 390: if(existed) {
! 391: // second+ appearence
! 392: table=valready->get_table();
! 393: } else {
! 394: // first appearence
! 395: Array& columns=*new(pool) Array(pool, 1);
! 396: columns+=new(pool) String(pool, "value");
! 397: table=new(pool) Table(pool, 0, &columns);
! 398: }
! 399: // this string becomes next row
! 400: Array& row=*new(pool) Array(pool, 1);
! 401: row+=&string;
! 402: *table+=&row;
! 403: // not existed before? add it
! 404: if(!existed)
! 405: tables.put(sname, new(pool) VTable(pool, table));
! 406: }
! 407: headers.put(sname, new(pool) VString(string));
1.126 paf 408: } else
409: throw Exception("http.response",
1.133 paf 410: &connect_string,
1.126 paf 411: "bad response from host - bad headers \"%s\"", header_block.cstr());
412: }
413:
414: // output response
415: data=body.cstr(); data_size=body.size();
1.141 paf 416: if(out_fields) {
1.142 paf 417: headers.put(*file_status_name, new(pool) VInt(pool, status_code));
1.126 paf 418: *out_fields=&headers;
1.141 paf 419: }
1.34 paf 420: }
1.123 paf 421:
422: #ifndef DOXYGEN
423: struct File_read_action_info {
424: void **data; size_t *data_size;
1.126 paf 425: };
1.123 paf 426: #endif
1.126 paf 427: static void file_read_action(Pool& pool,
428: struct stat& finfo,
1.123 paf 429: int f,
1.126 paf 430: const String& file_spec, const char* fname, bool as_text,
1.123 paf 431: void *context) {
1.126 paf 432: File_read_action_info& info=*static_cast<File_read_action_info *>(context);
1.123 paf 433: if(size_t to_read_size=(size_t)finfo.st_size) {
1.126 paf 434: *info.data=pool.malloc(to_read_size+(as_text?1:0), 3);
435: *info.data_size=(size_t)read(f, *info.data, to_read_size);
1.123 paf 436:
437: if(ssize_t(*info.data_size)<0 || *info.data_size>to_read_size)
1.126 paf 438: throw Exception(0,
1.123 paf 439: &file_spec,
440: "read failed: actually read %lu bytes count not in [0..%lu] valid range",
1.126 paf 441: *info.data_size, to_read_size);
1.123 paf 442: } else { // empty file
443: if(as_text) {
1.126 paf 444: *info.data=pool.malloc(1);
1.123 paf 445: *(char*)(*info.data)=0;
446: } else
447: *info.data=0;
448: *info.data_size=0;
449: return;
450: }
1.126 paf 451: }
452: bool file_read(Pool& pool, const String& file_spec,
453: void*& data, size_t& data_size,
454: bool as_text, Hash *params, Hash** out_fields,
455: bool fail_on_read_problem) {
456: bool result;
457: if(file_spec.starts_with("http://", 7)) {
458: // fail on read problem
459: file_read_http(pool, file_spec, data, data_size, params, out_fields);
460: result=true;
461: } else {
462: File_read_action_info info={&data, &data_size};
463: result=file_read_action_under_lock(pool, file_spec,
464: "read", file_read_action, &info,
465: as_text, fail_on_read_problem);
466: }
1.123 paf 467:
1.126 paf 468: if(result && as_text) {
1.131 paf 469: // UTF-8 signature: EF BB BF
470: if(data_size>=3) {
471: char *in=(char *)data;
472: if((in[0] == '\xEF') && (in[1] == '\xBB') &&
473: (in[2] == '\xBF')) {
474: data=in+3; data_size-=3;// skip prefix
475: }
476: }
477:
1.126 paf 478: fix_line_breaks((char *)(data), data_size);
1.123 paf 479: // note: after fixing
1.126 paf 480: ((char*&)(data))[data_size]=0;
1.123 paf 481: }
1.126 paf 482:
483: return result;
1.123 paf 484: }
485:
1.149 paf 486: #ifdef PA_SAFE_MODE
1.150 paf 487: void check_safe_mode(struct stat finfo, const String& file_spec, const char* fname) {
1.149 paf 488: if(finfo.st_uid/*foreign?*/!=geteuid()
489: && finfo.st_gid/*foreign?*/!=getegid())
490: throw Exception("parser.runtime",
491: &file_spec,
492: "parser is in safe mode: "
493: "reading files of foreign group and user disabled "
494: "[recompile parser with --disable-safe-mode configure option], "
495: "actual filename '%s', "
496: "fuid(%d)!=euid(%d) or fgid(%d)!=egid(%d)",
497: fname,
498: finfo.st_uid, geteuid(),
499: finfo.st_gid, getegid());
500: }
501: #endif
502:
1.123 paf 503: bool file_read_action_under_lock(Pool& pool, const String& file_spec,
1.126 paf 504: const char* action_name, File_read_action action, void *context,
505: bool as_text,
1.123 paf 506: bool fail_on_read_problem) {
1.126 paf 507: const char* fname=file_spec.cstr(String::UL_FILE_SPEC);
1.33 paf 508: int f;
509:
510: // first open, next stat:
1.45 paf 511: // directory update of NTFS hard links performed on open.
1.33 paf 512: // ex:
513: // a.html:^test[] and b.html hardlink to a.html
514: // user inserts ! before ^test in a.html
1.126 paf 515: // directory entry of b.html in NTFS not updated at once,
1.35 paf 516: // they delay update till open, so we would receive "!^test[" string
517: // if would do stat, next open.
1.123 paf 518: // later: it seems, even this does not help sometimes
1.98 paf 519: if((f=open(fname, O_RDONLY|(as_text?_O_TEXT:_O_BINARY)))>=0) {
1.123 paf 520: try {
521: if(lock_shared_blocking(f)!=0)
1.126 paf 522: throw Exception("file.lock",
1.123 paf 523: &file_spec,
524: "shared lock failed: %s (%d), actual filename '%s'",
1.126 paf 525: strerror(errno), errno, fname);
1.123 paf 526:
1.124 paf 527: struct stat finfo;
528: if(stat(fname, &finfo)!=0)
529: throw Exception("file.missing", // hardly possible: we just opened it OK
530: &file_spec,
531: "stat failed: %s (%d), actual filename '%s'",
1.126 paf 532: strerror(errno), errno, fname);
1.124 paf 533:
1.140 paf 534: #ifdef PA_SAFE_MODE
1.149 paf 535: check_safe_mode(finfo, file_spec, fname);
1.105 paf 536: #endif
1.32 paf 537:
1.126 paf 538: action(pool, finfo, f, file_spec, fname, as_text, context);
1.123 paf 539: } catch(...) {
1.126 paf 540: unlock(f);close(f);
1.123 paf 541: if(fail_on_read_problem)
542: /*re*/throw;
543: return false;
544: }
1.87 paf 545:
1.126 paf 546: unlock(f);close(f);
1.72 parser 547: return true;
1.118 paf 548: } else {
549: if(fail_on_read_problem)
1.126 paf 550: throw Exception(errno==EACCES?"file.access":errno==ENOENT?"file.missing":0,
1.118 paf 551: &file_spec,
1.123 paf 552: "%s failed: %s (%d), actual filename '%s'",
1.126 paf 553: action_name, strerror(errno), errno, fname);
1.118 paf 554: return false;
555: }
1.8 paf 556: }
557:
1.63 parser 558: static void create_dir_for_file(const String& file_spec) {
559: size_t pos_after=1;
560: int pos_before;
561: while((pos_before=file_spec.pos("/", 1, pos_after))>=0) {
1.126 paf 562: mkdir(file_spec.mid(0, pos_before).cstr(String::UL_FILE_SPEC), 0775);
1.63 parser 563: pos_after=pos_before+1;
564: }
565: }
566:
1.98 paf 567: bool file_write_action_under_lock(
1.28 paf 568: const String& file_spec,
1.126 paf 569: const char* action_name, File_write_action action, void *context,
570: bool as_text,
571: bool do_append,
572: bool do_block,
1.110 paf 573: bool fail_on_lock_problem) {
1.126 paf 574: const char* fname=file_spec.cstr(String::UL_FILE_SPEC);
1.28 paf 575: int f;
1.80 paf 576: if(access(fname, W_OK)!=0) // no
1.126 paf 577: create_dir_for_file(file_spec);
1.50 paf 578:
1.80 paf 579: if((f=open(fname,
580: O_CREAT|O_RDWR
581: |(as_text?_O_TEXT:_O_BINARY)
1.138 paf 582: |(do_append?O_APPEND:PA_O_TRUNC), 0664))>=0) {
1.99 paf 583: if((do_block?lock_exclusive_blocking(f):lock_exclusive_nonblocking(f))!=0) {
1.126 paf 584: Exception e("file.lock",
1.110 paf 585: &file_spec,
586: "shared lock failed: %s (%d), actual filename '%s'",
1.126 paf 587: strerror(errno), errno, fname);
588: close(f);
1.110 paf 589: if(fail_on_lock_problem)
590: throw e;
1.98 paf 591: return false;
592: }
1.96 paf 593:
594: try {
1.126 paf 595: action(f, context);
1.96 paf 596: } catch(...) {
1.138 paf 597: #ifdef HAVE_FTRUNCATE
1.104 paf 598: if(!do_append)
1.125 paf 599: ftruncate(f, lseek(f, 0, SEEK_CUR)); // one can not use O_TRUNC, read lower
1.138 paf 600: #endif
1.126 paf 601: unlock(f);close(f);
1.96 paf 602: /*re*/throw;
603: }
1.80 paf 604:
1.138 paf 605: #ifdef HAVE_FTRUNCATE
1.104 paf 606: if(!do_append)
1.125 paf 607: ftruncate(f, lseek(f, 0, SEEK_CUR)); // O_TRUNC truncates even exclusevely write-locked file [thanks to Igor Milyakov <virtan@rotabanner.com> for discovering]
1.138 paf 608: #endif
1.126 paf 609: unlock(f);close(f);
1.98 paf 610: return true;
1.80 paf 611: } else
1.126 paf 612: throw Exception(errno==EACCES?"file.access":0,
1.80 paf 613: &file_spec,
1.96 paf 614: "%s failed: %s (%d), actual filename '%s'",
1.126 paf 615: action_name, strerror(errno), errno, fname);
1.96 paf 616: // here should be nothing, see rethrow above
617: }
618:
619: #ifndef DOXYGEN
620: struct File_write_action_info {
621: const void *data; size_t size;
1.126 paf 622: };
1.96 paf 623: #endif
624: static void file_write_action(int f, void *context) {
1.126 paf 625: File_write_action_info& info=*static_cast<File_write_action_info *>(context);
1.113 paf 626: if(info.size) {
1.126 paf 627: int written=write(f, info.data, info.size);
1.116 paf 628: if(written<0)
1.126 paf 629: throw Exception(0,
630: 0,
631: "write failed: %s (%d)", strerror(errno), errno);
1.113 paf 632: }
1.96 paf 633: }
634: void file_write(
635: const String& file_spec,
636: const void *data, size_t size,
1.126 paf 637: bool as_text,
1.96 paf 638: bool do_append) {
1.126 paf 639: File_write_action_info info={data, size};
1.98 paf 640: file_write_action_under_lock(
1.96 paf 641: file_spec,
1.126 paf 642: "write", file_write_action, &info,
643: as_text,
644: do_append);
1.30 paf 645: }
646:
1.63 parser 647: // throws nothing! [this is required in file_move & file_delete]
1.50 paf 648: static void rmdir(const String& file_spec, size_t pos_after) {
649: int pos_before;
650: if((pos_before=file_spec.pos("/", 1, pos_after))>=0)
1.126 paf 651: rmdir(file_spec, pos_before+1);
1.50 paf 652:
1.126 paf 653: rmdir(file_spec.mid(0, pos_after-1/* / */).cstr(String::UL_FILE_SPEC));
1.50 paf 654: }
1.95 paf 655: bool file_delete(const String& file_spec, bool fail_on_read_problem) {
1.126 paf 656: const char* fname=file_spec.cstr(String::UL_FILE_SPEC);
1.54 parser 657: if(unlink(fname)!=0)
1.93 paf 658: if(fail_on_read_problem)
1.126 paf 659: throw Exception(errno==EACCES?"file.access":errno==ENOENT?"file.missing":0,
1.93 paf 660: &file_spec,
661: "unlink failed: %s (%d), actual filename '%s'",
1.126 paf 662: strerror(errno), errno, fname);
1.93 paf 663: else
664: return false;
1.50 paf 665:
1.126 paf 666: rmdir(file_spec, 1);
1.93 paf 667: return true;
1.60 parser 668: }
1.95 paf 669: void file_move(const String& old_spec, const String& new_spec) {
1.126 paf 670: const char* old_spec_cstr=old_spec.cstr(String::UL_FILE_SPEC);
671: const char* new_spec_cstr=new_spec.cstr(String::UL_FILE_SPEC);
1.63 parser 672:
1.126 paf 673: create_dir_for_file(new_spec);
1.63 parser 674:
1.60 parser 675: if(rename(old_spec_cstr, new_spec_cstr)!=0)
1.126 paf 676: throw Exception(errno==EACCES?"file.access":errno==ENOENT?"file.missing":0,
1.60 parser 677: &old_spec,
678: "rename failed: %s (%d), actual filename '%s' to '%s'",
1.126 paf 679: strerror(errno), errno, old_spec_cstr, new_spec_cstr);
1.63 parser 680:
1.126 paf 681: rmdir(old_spec, 1);
1.31 paf 682: }
683:
1.51 paf 684:
1.126 paf 685: bool entry_exists(const char* fname, struct stat *afinfo) {
1.118 paf 686: struct stat lfinfo;
687: bool result=stat(fname, &lfinfo)==0;
688: if(afinfo)
689: *afinfo=lfinfo;
690: return result;
1.119 paf 691: }
692:
693: bool entry_exists(const String& file_spec) {
1.126 paf 694: const char* fname=file_spec.cstr(String::UL_FILE_SPEC);
695: return entry_exists(fname, 0);
1.118 paf 696: }
697:
1.51 paf 698: static bool entry_readable(const String& file_spec, bool need_dir) {
1.126 paf 699: char* fname=file_spec.cstr(String::UL_FILE_SPEC);
1.120 paf 700: if(need_dir) {
1.126 paf 701: size_t size=strlen(fname);
1.120 paf 702: while(size) {
1.126 paf 703: char c=fname[size-1];
1.120 paf 704: if(c=='/' || c=='\\')
705: fname[--size]=0;
706: else
707: break;
708: }
709: }
1.51 paf 710: struct stat finfo;
1.118 paf 711: if(access(fname, R_OK)==0 && entry_exists(fname, &finfo)) {
1.109 paf 712: bool is_dir=(finfo.st_mode&S_IFDIR) != 0;
1.51 paf 713: return is_dir==need_dir;
714: }
715: return false;
716: }
1.31 paf 717: bool file_readable(const String& file_spec) {
1.126 paf 718: return entry_readable(file_spec, false);
1.51 paf 719: }
720: bool dir_readable(const String& file_spec) {
1.126 paf 721: return entry_readable(file_spec, true);
1.65 parser 722: }
723: String *file_readable(const String& path, const String& name) {
1.126 paf 724: String *result=new(path.pool()) String(path);
725: *result << "/";
1.65 parser 726: *result << name;
727: return file_readable(*result)?result:0;
1.43 paf 728: }
729: bool file_executable(const String& file_spec) {
1.64 parser 730: return access(file_spec.cstr(String::UL_FILE_SPEC), X_OK)==0;
1.44 paf 731: }
732:
1.64 parser 733: bool file_stat(const String& file_spec,
1.58 parser 734: size_t& rsize,
1.126 paf 735: time_t& ratime,
736: time_t& rmtime,
737: time_t& rctime,
1.64 parser 738: bool fail_on_read_problem) {
1.126 paf 739: Pool& pool=file_spec.pool();
740: const char* fname=file_spec.cstr(String::UL_FILE_SPEC);
1.44 paf 741: struct stat finfo;
742: if(stat(fname, &finfo)!=0)
1.64 parser 743: if(fail_on_read_problem)
1.126 paf 744: throw Exception("file.missing",
1.67 parser 745: &file_spec,
746: "getting file size failed: %s (%d), real filename '%s'",
1.126 paf 747: strerror(errno), errno, fname);
1.64 parser 748: else
749: return false;
1.58 parser 750: rsize=finfo.st_size;
751: ratime=finfo.st_atime;
752: rmtime=finfo.st_mtime;
753: rctime=finfo.st_ctime;
1.64 parser 754: return true;
1.18 paf 755: }
756:
1.126 paf 757: char* getrow(char* *row_ref, char delim) {
758: char* result=*row_ref;
1.8 paf 759: if(result) {
1.126 paf 760: *row_ref=strchr(result, delim);
1.8 paf 761: if(*row_ref)
762: *((*row_ref)++)=0;
763: else if(!*result)
764: return 0;
765: }
766: return result;
767: }
768:
1.126 paf 769: char* lsplit(char* string, char delim) {
1.23 paf 770: if(string) {
1.126 paf 771: char* v=strchr(string, delim);
1.8 paf 772: if(v) {
773: *v=0;
774: return v+1;
775: }
776: }
777: return 0;
778: }
779:
1.126 paf 780: char* lsplit(char* *string_ref, char delim) {
781: char* result=*string_ref;
782: char* next=lsplit(*string_ref, delim);
1.8 paf 783: *string_ref=next;
784: return result;
1.9 paf 785: }
786:
1.126 paf 787: char* rsplit(char* string, char delim) {
1.18 paf 788: if(string) {
1.126 paf 789: char* v=strrchr(string, delim);
1.18 paf 790: if(v) {
1.9 paf 791: *v=0;
792: return v+1;
793: }
794: }
795: return NULL;
1.10 paf 796: }
797:
1.37 paf 798: /// @todo less stupid type detection
1.126 paf 799: char* format(Pool& pool, double value, char* fmt) {
800: char local_buf[MAX_NUMBER];
1.108 paf 801: size_t size;
802:
1.10 paf 803: if(fmt)
804: if(strpbrk(fmt, "diouxX"))
805: if(strpbrk(fmt, "ouxX"))
1.126 paf 806: size=snprintf(local_buf, sizeof(local_buf), fmt, (uint)value);
1.10 paf 807: else
1.126 paf 808: size=snprintf(local_buf, sizeof(local_buf), fmt, (int)value);
1.10 paf 809: else
1.126 paf 810: size=snprintf(local_buf, sizeof(local_buf), fmt, value);
1.10 paf 811: else
1.126 paf 812: size=snprintf(local_buf, sizeof(local_buf), "%d", (int)value);
1.10 paf 813:
1.126 paf 814: char* pool_buf=(char *)pool.malloc(size+1, 4);
815: memcpy(pool_buf, local_buf, size+1);
1.108 paf 816: return pool_buf;
1.12 paf 817: }
818:
1.36 paf 819: size_t stdout_write(const void *buf, size_t size) {
1.12 paf 820: #ifdef WIN32
821: do{
1.126 paf 822: int chunk_written=fwrite(buf, 1, min(8*0x400, size), stdout);
1.12 paf 823: if(chunk_written<=0)
824: break;
825: size-=chunk_written;
1.36 paf 826: buf=((const char*)buf)+chunk_written;
1.126 paf 827: } while(size>0);
1.12 paf 828:
829: return size;
830: #else
1.126 paf 831: return fwrite(buf, 1, size, stdout);
1.12 paf 832: #endif
1.2 paf 833: }
1.14 paf 834:
1.126 paf 835: char* unescape_chars(Pool& pool, const char* cp, int len) {
836: char* s=(char *)pool.malloc(len + 1, 5);
1.14 paf 837: enum EscapeState {
1.33 paf 838: EscapeRest,
839: EscapeFirst,
1.14 paf 840: EscapeSecond
841: } escapeState=EscapeRest;
842: int escapedValue=0;
843: int srcPos=0;
844: int dstPos=0;
845: while(srcPos < len) {
1.126 paf 846: int ch=cp[srcPos];
1.14 paf 847: switch(escapeState) {
848: case EscapeRest:
849: if(ch=='%') {
850: escapeState=EscapeFirst;
851: } else if(ch=='+') {
1.126 paf 852: s[dstPos++]=' ';
1.14 paf 853: } else {
854: s[dstPos++]=ch;
855: }
856: break;
857: case EscapeFirst:
858: escapedValue=hex_value[ch] << 4;
859: escapeState=EscapeSecond;
860: break;
861: case EscapeSecond:
1.126 paf 862: escapedValue +=hex_value[ch];
1.14 paf 863: s[dstPos++]=escapedValue;
864: escapeState=EscapeRest;
865: break;
866: }
1.126 paf 867: srcPos++;
1.14 paf 868: }
869: s[dstPos]=0;
870: return s;
1.24 paf 871: }
872:
873: #ifdef WIN32
1.126 paf 874: void back_slashes_to_slashes(char* s) {
1.24 paf 875: if(s)
876: for(; *s; s++)
877: if(*s=='\\')
1.126 paf 878: *s='/';
1.24 paf 879: }
1.42 paf 880: /*
1.126 paf 881: void slashes_to_back_slashes(char* s) {
1.42 paf 882: if(s)
883: for(; *s; s++)
884: if(*s=='/')
1.126 paf 885: *s='\\';
1.42 paf 886: }
887: */
1.24 paf 888: #endif
1.41 paf 889:
1.126 paf 890: bool StrEqNc(const char* s1, const char* s2, bool strict) {
1.41 paf 891: while(true) {
892: if(!(*s1)) {
893: if(!(*s2))
894: return true;
895: else
896: return !strict;
897: } else if(!(*s2))
898: return !strict;
899: if(isalpha(*s1)) {
900: if(tolower(*s1) !=tolower(*s2))
901: return false;
902: } else if((*s1) !=(*s2))
903: return false;
1.126 paf 904: s1++;
905: s2++;
1.41 paf 906: }
1.57 parser 907: }
908:
1.84 paf 909: static bool isLeap(int year) {
1.57 parser 910: return !(
911: (year % 4) || ((year % 400) && !(year % 100))
1.126 paf 912: );
1.57 parser 913: }
914:
915: int getMonthDays(int year, int month) {
916: int monthDays[]={
1.126 paf 917: 31,
918: isLeap(year) ? 29 : 28,
919: 31,
920: 30,
921: 31,
922: 30,
923: 31,
924: 31,
925: 30,
926: 31,
927: 30,
1.57 parser 928: 31
1.126 paf 929: };
930: return monthDays[month];
1.41 paf 931: }
1.69 parser 932:
1.126 paf 933: void remove_crlf(char* start, char* end) {
934: for(char* p=start; p<end; p++)
1.69 parser 935: switch(*p) {
1.126 paf 936: case '\n': *p='|'; break;
937: case '\r': *p=' '; break;
1.69 parser 938: }
1.91 paf 939: }
940:
941:
942: /// must be last in this file
943: #undef vsnprintf
1.126 paf 944: int __vsnprintf(char* b, size_t s, const char* f, va_list l) {
1.91 paf 945: if(!s)
946: return 0;
947:
948: int r;
949: // note: on win32& maybe somewhere else
950: // vsnprintf do not writes terminating 0 in 'buffer full' case, reducing
951: --s;
952: #if _MSC_VER
953: /*
954: win32:
955: mk:@MSITStore:C:\Program%20Files\Microsoft%20Visual%20Studio\MSDN\2001APR\1033\vccore.chm::/html/_crt__vsnprintf.2c_._vsnwprintf.htm
956:
957: if the number of bytes to write exceeds buffer, then count bytes are written and –1 is returned
958: */
1.126 paf 959: r=_vsnprintf(b, s, f, l);
1.91 paf 960: if(r<0)
961: r=s;
962: #else
1.126 paf 963: r=vsnprintf(b, s, f, l);
1.91 paf 964: /*
965: solaris:
966: man vsnprintf
967:
968: The snprintf() function returns the number of characters
969: formatted, that is, the number of characters that would have
970: been written to the buffer if it were large enough. If the
971: value of n is 0 on a call to snprintf(), an unspecified
972: value less than 1 is returned.
973: */
974:
975: if(r<0)
976: r=0;
977: else if(r>s)
978: r=s;
979: #endif
980: b[r]=0;
981: return r;
982: }
983:
1.126 paf 984: int __snprintf(char* b, size_t s, const char* f, ...) {
1.91 paf 985: va_list l;
1.126 paf 986: va_start(l, f);
987: int r=__vsnprintf(b, s, f, l);
988: va_end(l);
1.91 paf 989: return r;
1.98 paf 990: }
991:
992: int pa_sleep(unsigned long secs, unsigned long usecs) {
1.126 paf 993: for (; usecs >= 1000000; ++secs, usecs -= 1000000);
1.98 paf 994:
995: #ifdef WIN32
1.126 paf 996: Sleep(secs * 1000 + usecs / 1000);
1.98 paf 997: return 0;
998: #else
999: struct timeval t;
1000: t.tv_sec = secs;
1001: t.tv_usec = usecs;
1.126 paf 1002: return (select(0, NULL, NULL, NULL, &t) == -1 ? errno : 0);
1.98 paf 1003: #endif
1.135 paf 1004: }
1005:
1006:
1007: // attributed meaning
1008:
1.151 paf 1009: /// http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3
1.135 paf 1010: static size_t date_attribute(const VDate& vdate, char *buf, size_t buf_size) {
1011: const char month_names[12][4]={
1012: "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
1013: const char days[7][4]={
1014: "Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
1015:
1016: time_t when=vdate.get_time();
1017: struct tm *tms=gmtime(&when);
1018: if(!tms)
1019: throw Exception(0,
1020: 0,
1021: "bad time in attribute value (seconds from epoch=%ld)", when);
1.151 paf 1022: return snprintf(buf, MAX_STRING, "%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT",
1.135 paf 1023: days[tms->tm_wday],
1024: tms->tm_mday,month_names[tms->tm_mon],tms->tm_year+1900,
1025: tms->tm_hour,tms->tm_min,tms->tm_sec);
1026: }
1027: static void append_attribute_meaning(String& result,
1.136 paf 1028: Value& value, String::Untaint_lang lang, bool forced) {
1.135 paf 1029: if(const String *string=value.get_string())
1.136 paf 1030: result.append(string->join_chains(result.pool(), 0), lang, forced);
1.135 paf 1031: else
1032: if(Value *vdate=value.as(VDATE_TYPE, false)) {
1033: char *buf=(char *)result.malloc(MAX_STRING);
1034: size_t size=date_attribute(*static_cast<VDate *>(vdate),
1035: buf, MAX_STRING);
1036:
1037: result.APPEND_CLEAN(buf, size, "converted from date", 0);
1038: } else
1039: throw Exception("parser.runtime",
1040: &result,
1041: "trying to append here neither string nor date (%s)",
1042: value.type());
1043: }
1044: #ifndef DOXYGEN
1045: struct Attributed_meaning_info {
1046: String *header; // header line being constructed
1047: String::Untaint_lang lang; // language in which to append to that line
1.136 paf 1048: bool forced; // do they force that lang?
1.135 paf 1049: };
1050: #endif
1051: static void append_attribute_subattribute(const Hash::Key& akey, Hash::Val *avalue,
1052: void *info) {
1053: if(akey==VALUE_NAME)
1054: return;
1055:
1056: Attributed_meaning_info& ami=*static_cast<Attributed_meaning_info *>(info);
1057:
1058: // ...; charset=windows1251
1059: *ami.header << "; ";
1.136 paf 1060: ami.header->append(akey, ami.lang, ami.forced);
1.135 paf 1061: *ami.header << "=";
1.136 paf 1062: append_attribute_meaning(*ami.header, *static_cast<Value *>(avalue), ami.lang, ami.forced);
1.135 paf 1063: }
1064: const String& attributed_meaning_to_string(Value& meaning,
1.136 paf 1065: String::Untaint_lang lang, bool forced) {
1.135 paf 1066: String &result=*new(meaning.pool()) String(meaning.pool());
1067: if(Hash *hash=meaning.get_hash(0)) {
1068: // $value(value) $subattribute(subattribute value)
1069: if(Value *value=static_cast<Value *>(hash->get(*value_name)))
1.136 paf 1070: append_attribute_meaning(result, *value, lang, forced);
1.135 paf 1071:
1072: Attributed_meaning_info attributed_meaning_info={&result, lang};
1073: hash->for_each(append_attribute_subattribute, &attributed_meaning_info);
1074: } else // result value
1.136 paf 1075: append_attribute_meaning(result, meaning, lang, forced);
1.135 paf 1076:
1077: return result;
1.74 parser 1078: }
E-mail: