Back to index

avfs  1.0.1
http.c
Go to the documentation of this file.
00001 /*
00002     AVFS: A Virtual File System Library
00003     Copyright (C) 2000-2001  Miklos Szeredi <miklos@szeredi.hu>
00004 
00005     This program can be distributed under the terms of the GNU GPL.
00006     See the file COPYING.
00007 */
00008 
00009 #include "avfs.h"
00010 #include "version.h"
00011 #include "cache.h"
00012 #include "filebuf.h"
00013 #include "socket.h"
00014 #include "serialfile.h"
00015 #include "internal.h"
00016 
00017 #include <stdlib.h>
00018 #include <unistd.h>
00019 
00020 #define HTTP_READ_TIMEOUT 20000
00021 
00022 struct httpentry;
00023 
00024 struct httplocalfile {
00025     struct filebuf *sockfb;
00026     struct httpentry *ent;
00027 };
00028 
00029 struct httpentry {
00030     char *url;
00031     struct cacheobj *cobj;
00032     avoff_t size;
00033     struct httpentry *next;
00034 };
00035 
00036 struct httpfs {
00037     struct httpentry *ents;
00038     char *proxyname;
00039 };
00040 
00041 struct httpfile {
00042     struct httpfs *fs;
00043     struct httpentry *ent;
00044 };
00045 
00046 static int write_socket(int sock, const char *buf, avsize_t buflen)
00047 {
00048     int res;
00049 
00050     while(buflen > 0) {
00051         res = write(sock, buf, buflen);
00052         if(res == -1)
00053             return -errno;
00054         
00055         buf += res;
00056         buflen -= res;
00057     }
00058 
00059     return 0;
00060 }
00061 
00062 static void strip_crlf(char *line)
00063 {
00064     avsize_t len = strlen(line);
00065     
00066     if(len > 0 && line[len-1] == '\n') {
00067         if(len > 1 && line[len-2] == '\r')
00068             line[len-2] = '\0';
00069         else
00070             line[len-1] = '\0';
00071     }
00072 }
00073 
00074 static int http_get_line(struct httplocalfile *lf, char **linep)
00075 {
00076     int res;
00077     char *line;
00078 
00079     while(1) {        
00080         res = av_filebuf_readline(lf->sockfb, &line);
00081         if(res < 0)
00082             return res;
00083         if(res == 1)
00084             break;
00085 
00086         if(av_filebuf_eof(lf->sockfb)) {
00087             av_log(AVLOG_ERROR, "HTTP: connection closed in header");
00088             return -EIO;
00089         }
00090 
00091         res = av_filebuf_check(&lf->sockfb, 1, HTTP_READ_TIMEOUT);
00092         if(res < 0)
00093             return res;
00094 
00095         if(res == 0) {
00096             av_log(AVLOG_ERROR, "HTTP: timeout in header");
00097             return -EIO;
00098         }
00099     }
00100     
00101     strip_crlf(line);
00102 
00103     av_log(AVLOG_DEBUG, "HTTP: %s", line);
00104     *linep = line;
00105 
00106     return 0;
00107 }
00108 
00109 static char *http_split_header(char *line)
00110 {
00111     char *s;
00112 
00113     for(s = line; *s && !isspace((unsigned char) *s); s++);
00114     if(*s) {
00115         do {
00116             *s = '\0';
00117             s++;
00118         } while(isspace((unsigned char) *s));
00119     }
00120     
00121     return s;
00122 }
00123 
00124 static void http_process_header_line(struct httplocalfile *lf, char *line)
00125 {
00126     char *s;
00127 
00128     s = http_split_header(line);
00129 
00130     if(strcasecmp("content-length:", line) == 0) {
00131         char *end;
00132         avoff_t size;
00133         size = strtol(s, &end, 10);
00134         while(*end && isspace((unsigned char) *end))
00135             end++;
00136         
00137         if(!*end)
00138             lf->ent->size = size;
00139     }
00140 }
00141 
00142 static int http_check_header_line(struct httplocalfile *lf)
00143 {
00144     int res;
00145     char *line;
00146 
00147     res = http_get_line(lf, &line);
00148     if(res < 0)
00149         return res;
00150 
00151     if(line[0] == '\0')
00152         res = 0;
00153     else {
00154         http_process_header_line(lf, line);
00155         res = 1;
00156     }
00157     av_free(line);
00158 
00159     return res;
00160 }
00161 
00162 static int http_ignore_header(struct httplocalfile *lf)
00163 {
00164     int res;
00165     char *line;
00166     int end = 0;
00167 
00168     do {
00169         res = http_get_line(lf, &line);
00170         if(res < 0)
00171             return res;
00172 
00173         if(line[0] == '\0')
00174             end = 1;
00175 
00176         av_free(line);
00177     } while(!end);
00178 
00179     return 0;
00180 }
00181 
00182 static int http_process_status_line(struct httplocalfile *lf, char *line)
00183 {
00184     const char *s;
00185     int statuscode;
00186     int res;
00187 
00188     for(s = line; *s && *s != ' '; s++);
00189 
00190     if(s[0] != ' ' || 
00191        !isdigit((unsigned char) s[1]) || 
00192        !isdigit((unsigned char) s[2]) || 
00193        !isdigit((unsigned char) s[3])) {
00194         av_log(AVLOG_ERROR, "HTTP: bad status code: %s", s);
00195         return -EIO;
00196     }
00197     
00198     statuscode = (s[1] - '0') * 100 + (s[2] - '0') * 10 + (s[3] - '0');
00199     
00200     av_log(AVLOG_DEBUG, "HTTP: status code: %i", statuscode);
00201 
00202     if(statuscode / 100 == 1) {
00203         res = http_ignore_header(lf);
00204         if(res < 0)
00205             return res;
00206         
00207         return 0;
00208     }
00209     
00210     if(statuscode / 100 == 2)
00211         return 1;
00212 
00213     av_log(AVLOG_WARNING, "HTTP: error: %s", s);
00214     http_ignore_header(lf);
00215 
00216     if(statuscode / 100 == 3 || statuscode / 100 == 4)
00217         return -ENOENT;
00218 
00219     return -EIO;
00220 }
00221 
00222 static int http_check_status_line(struct httplocalfile *lf)
00223 {
00224     int res;
00225     char *line;
00226 
00227     do {
00228         res = http_get_line(lf, &line);
00229         if(res < 0)
00230             return res;
00231         
00232         res = http_process_status_line(lf, line);
00233         av_free(line);
00234     } while(res == 0);
00235 
00236     if(res < 0)
00237         return res;
00238 
00239     return 0;
00240 }
00241 
00242 static int http_wait_response(struct httplocalfile *lf)
00243 {
00244     int res;
00245 
00246     res = http_check_status_line(lf);
00247     if(res < 0)
00248         return res;
00249 
00250     do res = http_check_header_line(lf);
00251     while(res == 1);
00252 
00253     return res;
00254 }
00255 
00256 static const char *http_strip_resource_type(const char *url)
00257 {
00258     const char *s;
00259 
00260     for(s = url; *s && *s != ':'; s++);
00261     if(*s)
00262         s++;
00263     for(; *s == '/'; s++);
00264     
00265     return s;
00266 }
00267 
00268 static char *http_url_path(const char *url)
00269 {
00270     const char *s;
00271 
00272     s = http_strip_resource_type(url);
00273     s = strchr(s, '/');
00274     if(s == NULL)
00275         return av_strdup("/");
00276     else
00277         return av_strdup(s);
00278 }
00279 
00280 static char *http_url_host(const char *url)
00281 {
00282     const char *s;
00283     const char *t;
00284     
00285     s = http_strip_resource_type(url);
00286     t = strchr(s, '/');
00287     if(t == NULL)
00288         return av_strdup(s);
00289     else
00290         return av_strndup(s, t - s);
00291 }
00292 
00293 static int http_request_get(int sock, struct httpfile *fil)
00294 {
00295     int res;
00296     char *req;
00297     char *url;
00298     char *host;
00299     
00300     if(fil->fs->proxyname != NULL)
00301         url = av_strdup(fil->ent->url);
00302     else
00303         url = http_url_path(fil->ent->url);
00304 
00305     host = http_url_host(fil->ent->url);
00306 
00307     req = av_stradd(NULL, 
00308                       "GET ", url, " HTTP/1.1\r\n",
00309                       "Host: ", host, "\r\n"
00310                       "Connection: close\r\n"
00311                       "\r\n",
00312                       NULL);
00313 
00314     av_free(url);
00315     av_free(host);
00316 
00317     av_log(AVLOG_DEBUG, "HTTP: %s", req);
00318 
00319     res = write_socket(sock, req, strlen(req));
00320     av_free(req);
00321 
00322     return res;
00323 }
00324 
00325 
00326 static void http_stop(struct httplocalfile *lf)
00327 {
00328     av_unref_obj(lf->sockfb);
00329 }
00330 
00331 static int http_start(void *data, void **resp)
00332 {
00333     int res;
00334     int sock;
00335     int defaultport;
00336     char *host;
00337     struct httpfile *fil = (struct httpfile *) data;
00338     struct httplocalfile *lf;
00339 
00340     if(fil->fs->proxyname != NULL) {
00341         host = av_strdup(fil->fs->proxyname);
00342         defaultport = 8000;
00343     }
00344     else {
00345         host = http_url_host(fil->ent->url);
00346         defaultport = 80;
00347     }
00348 
00349     res = av_sock_connect(host, defaultport);
00350     av_free(host);
00351     if(res < 0)
00352         return res;
00353 
00354     sock = res;
00355     av_registerfd(sock);
00356 
00357     res = http_request_get(sock, fil);
00358     if(res < 0) {
00359         close(sock);
00360         return res;
00361     }
00362 
00363     fil->ent->size = -1;
00364 
00365     AV_NEW_OBJ(lf, http_stop);
00366     lf->sockfb = av_filebuf_new(sock, 0);
00367     lf->ent = fil->ent;
00368 
00369     res = http_wait_response(lf);
00370     if(res < 0) {
00371         av_unref_obj(lf);
00372         return res;
00373     }
00374 
00375     *resp = lf;
00376     
00377     return 0;
00378 }
00379 
00380 static avssize_t http_sread(void *data, char *buf, avsize_t nbyte)
00381 {
00382     avssize_t res;
00383     struct httplocalfile *lf = (struct httplocalfile *) data;
00384 
00385     do {
00386         res = av_filebuf_read(lf->sockfb, buf, nbyte);
00387         if(res != 0)
00388             return res;
00389         
00390         if(av_filebuf_eof(lf->sockfb))
00391             return 0;
00392         
00393         res = av_filebuf_check(&lf->sockfb, 1, HTTP_READ_TIMEOUT);
00394         if(res < 0)
00395             return res;
00396         
00397     } while(res == 1);
00398 
00399     av_log(AVLOG_ERROR, "HTTP: timeout in body");
00400     return -EIO;
00401 }
00402 
00403 static struct sfile *http_get_serialfile(struct httpfile *fil)
00404 {
00405     struct sfile *sf;
00406     struct httpfile *filcpy;
00407     struct httpentry *ent = fil->ent;
00408     static struct sfilefuncs func = {
00409         http_start,
00410         http_sread
00411     };
00412 
00413     sf = (struct sfile *) av_cacheobj_get(ent->cobj);
00414     if(sf != NULL)
00415         return sf;
00416 
00417     AV_NEW_OBJ(filcpy, NULL);
00418     *filcpy = *fil;
00419 
00420     sf = av_sfile_new(&func, filcpy, 0);
00421 
00422     av_unref_obj(ent->cobj);
00423     ent->cobj = av_cacheobj_new(sf, ent->url);
00424 
00425     return sf;
00426 }
00427 
00428 static void http_set_size(struct httpfile *fil, struct sfile *sf)
00429 {
00430     struct httpentry *ent = fil->ent;
00431     avoff_t du;
00432 
00433     du = av_sfile_diskusage(sf);
00434     if(du >= 0)
00435         av_cacheobj_setsize(ent->cobj, du);
00436 }
00437 
00438 static struct httpentry *http_get_entry(struct httpfs *fs, const char *url)
00439 {
00440     struct httpentry **ep;
00441     struct httpentry *ent;
00442 
00443     for(ep = &fs->ents; *ep != NULL; ep = &(*ep)->next) {
00444         ent = *ep;
00445         if(strcmp(ent->url, url) == 0)
00446             return ent;
00447     }
00448 
00449     AV_NEW(ent);
00450     ent->url = av_strdup(url);
00451     ent->cobj = NULL;
00452     ent->next = NULL;
00453     
00454     *ep = ent;
00455 
00456     return ent;
00457 }
00458 
00459 static int begins_with(const char *str, const char *beg)
00460 {
00461     if(strncmp(str, beg, strlen(beg)) == 0)
00462         return 1;
00463     else
00464         return 0;
00465 }
00466 
00467 static char *http_ventry_url(ventry *ve)
00468 {
00469     char *url = av_strdup((char *) ve->data);
00470     char *s;
00471 
00472     for(s = url; *s; s++) {
00473         if(*s == '|')
00474             *s = '/';
00475     }
00476     
00477     if(!begins_with(url, "http://") && !begins_with(url, "ftp://")) {
00478         char *newurl;
00479 
00480         newurl = av_stradd(NULL, "http://", url, NULL);
00481         av_free(url);
00482         url = newurl;
00483     }
00484 
00485     return url;
00486 }
00487 
00488 static int http_open(ventry *ve, int flags, avmode_t mode, void **resp)
00489 {
00490     int res;
00491     char *url;
00492     struct httpfs *fs = (struct httpfs *) ve->mnt->avfs->data;
00493     struct httpfile *fil;
00494     struct sfile *sf;
00495 
00496     url = http_ventry_url(ve);
00497     if(url == NULL)
00498         return -ENOENT;
00499 
00500     AV_NEW(fil);
00501     fil->ent = http_get_entry(fs, url);
00502     fil->fs = fs;
00503     av_free(url);
00504 
00505     sf = http_get_serialfile(fil);
00506     res = av_sfile_startget(sf);
00507     http_set_size(fil, sf);
00508     av_unref_obj(sf);
00509 
00510     if(res == 0) 
00511         *resp = (void *) fil;
00512     else 
00513         av_free(fil);
00514 
00515     return res;
00516 }
00517 
00518 static int http_close(vfile *vf)
00519 {
00520     struct httpfile *fil = (struct httpfile *) vf->data;
00521 
00522     av_free(fil);
00523 
00524     return 0;
00525 }
00526 
00527 
00528 static avssize_t http_read(vfile *vf, char *buf, avsize_t nbyte)
00529 {
00530     avssize_t res;
00531     struct httpfile *fil = (struct httpfile *) vf->data;
00532     struct sfile *sf;
00533 
00534     sf = http_get_serialfile(fil);
00535     res = av_sfile_pread(sf, buf, nbyte, vf->ptr);
00536     http_set_size(fil, sf);
00537     av_unref_obj(sf);
00538 
00539     if(res > 0)
00540         vf->ptr += res;
00541 
00542     return res;
00543 }
00544 
00545 static int http_getattr(vfile *vf, struct avstat *buf, int attrmask)
00546 {
00547     avoff_t size = -1;
00548     struct httpfile *fil = (struct httpfile *) vf->data;
00549 
00550     if(attrmask & AVA_SIZE) {
00551         int res;
00552         struct sfile *sf;
00553 
00554         sf = http_get_serialfile(fil);
00555         res = av_sfile_startget(sf);
00556         if(res < 0)
00557             return res;
00558 
00559         size = fil->ent->size;
00560         if(size == -1)
00561             size = av_sfile_size(sf);
00562 
00563         http_set_size(fil, sf);
00564         av_unref_obj(sf);
00565     }
00566 
00567     buf->dev = 1;
00568     buf->ino = 1;
00569     buf->mode = AV_IFREG | 0777;
00570     buf->nlink = 1;
00571     buf->uid = 0;
00572     buf->gid = 0;
00573     buf->size = size;
00574     buf->blksize = 512;
00575     buf->blocks = AV_BLOCKS(size);
00576     buf->atime.sec = 0;
00577     buf->atime.nsec = 0;
00578     buf->mtime = buf->atime;
00579     buf->ctime = buf->atime;
00580 
00581     return 0;
00582 }
00583 
00584 static int http_access(ventry *ve, int amode)
00585 {
00586     if((amode & AVW_OK) != 0)
00587         return -EACCES;
00588     
00589     return 0;
00590 }
00591 
00592 static void http_destroy(struct avfs *avfs)
00593 {
00594     struct httpentry *ent;
00595     struct httpentry *nextent;
00596     struct httpfs *fs = (struct httpfs *) avfs->data;
00597 
00598     ent = fs->ents;
00599     while(ent != NULL) {
00600         nextent = ent->next;
00601         av_free(ent->url);
00602         av_unref_obj(ent->cobj);
00603         av_free(ent);
00604         ent = nextent;
00605     }
00606 
00607     av_free(fs->proxyname);
00608     av_free(fs);
00609 }
00610 
00611 static int http_proxy_get(struct entry *ent, const char *param, char **retp)
00612 {
00613     struct statefile *sf = (struct statefile *) av_namespace_get(ent);
00614     struct avfs *avfs = (struct avfs *) sf->data;
00615     struct httpfs *fs = (struct httpfs *) avfs->data;
00616     char *s;
00617     
00618     AV_LOCK(avfs->lock);
00619     if(fs->proxyname != NULL)
00620         s = av_stradd(NULL, fs->proxyname, "\n", NULL);
00621     else
00622         s = av_strdup("");
00623     AV_UNLOCK(avfs->lock);
00624 
00625     *retp = s;
00626 
00627     return 0;
00628 }
00629 
00630 static int http_proxy_set(struct entry *ent, const char *param,
00631                           const char *val)
00632 {
00633     struct statefile *sf = (struct statefile *) av_namespace_get(ent);
00634     struct avfs *avfs = (struct avfs *) sf->data;
00635     struct httpfs *fs = (struct httpfs *) avfs->data;
00636     char *s;
00637     unsigned int len;
00638 
00639     if(begins_with(val, "http://"))
00640         val = http_strip_resource_type(val);
00641 
00642     s = av_strdup(val);
00643     len = strlen(s);
00644     if(len > 0 && s[len-1] == '\n')
00645         s[len-1] = '\0';
00646     
00647     if(s[0] == '\0') {
00648         av_free(s);
00649         s = NULL;
00650     }
00651     
00652     AV_LOCK(avfs->lock);
00653     av_free(fs->proxyname);
00654     fs->proxyname = s;
00655     AV_UNLOCK(avfs->lock);
00656 
00657     return 0;
00658 }
00659 
00660 static void http_default_proxy(struct httpfs *fs)
00661 {
00662     const char *proxyenv;
00663 
00664     proxyenv = getenv("http_proxy");
00665     if(proxyenv == NULL)
00666         return;
00667 
00668     if(begins_with(proxyenv, "http://"))
00669         proxyenv = http_strip_resource_type(proxyenv);
00670     
00671     fs->proxyname = av_strdup(proxyenv);
00672 
00673     av_log(AVLOG_DEBUG, "HTTP: proxy = %s", fs->proxyname);
00674 }
00675 
00676 extern int av_init_module_http(struct vmodule *module);
00677 
00678 int av_init_module_http(struct vmodule *module)
00679 {
00680     int res;
00681     struct avfs *avfs;
00682     struct httpfs *fs;
00683     struct statefile statf;
00684 
00685     res = av_new_avfs("http", NULL, AV_VER, AVF_ONLYROOT, module, &avfs);
00686     if(res < 0)
00687         return res;
00688 
00689     AV_NEW(fs);
00690     fs->ents = NULL;
00691     fs->proxyname = NULL;
00692     
00693     http_default_proxy(fs);
00694     statf.get = http_proxy_get;
00695     statf.set = http_proxy_set;
00696     statf.data = avfs;
00697     av_avfsstat_register("http_proxy", &statf);
00698 
00699     avfs->data = (void *) fs;
00700 
00701     avfs->destroy   = http_destroy;
00702     avfs->open      = http_open;
00703     avfs->close     = http_close;
00704     avfs->getattr   = http_getattr;
00705     avfs->read      = http_read;
00706     avfs->access    = http_access;
00707 
00708     av_add_avfs(avfs);
00709     
00710     return 0;
00711 }