/** First information such as permissions is gathered for each directory entry. * All entries are then sorted. */ static struct directory_entry * get_smb_directory_entries(int dir, struct string *prefix) { struct directory_entry *entries = NULL; int size = 0; struct smbc_dirent *entry; while ((entry = smbc_readdir(dir))) { struct stat st, *stp; struct directory_entry *new_entries; struct string attrib; struct string name; if (!strcmp(entry->name, ".")) continue; new_entries = mem_realloc(entries, (size + 2) * sizeof(*new_entries)); if (!new_entries) continue; entries = new_entries; if (!init_string(&attrib)) { continue; } if (!init_string(&name)) { done_string(&attrib); continue; } add_string_to_string(&name, prefix); add_to_string(&name, entry->name); stp = (smbc_stat(name.source, &st)) ? NULL : &st; stat_type(&attrib, stp); stat_mode(&attrib, stp); stat_links(&attrib, stp); stat_user(&attrib, stp); stat_group(&attrib, stp); stat_size(&attrib, stp); stat_date(&attrib, stp); entries[size].name = stracpy(entry->name); entries[size].attrib = attrib.source; done_string(&name); size++; } smbc_closedir(dir); if (!size) { /* We may have allocated space for entries but added none. */ mem_free_if(entries); return NULL; } qsort(entries, size, sizeof(*entries), compare_dir_entries); memset(&entries[size], 0, sizeof(*entries)); return entries; }
static unsigned char * str_rd(struct option *opt, unsigned char **file, int *line) { unsigned char *str = *file; struct string str2; if (!init_string(&str2)) return NULL; /* We're getting used in some parser functions in conf.c as well, and * that's w/ opt == NULL; so don't rely on opt to point anywhere. */ if (!commandline) { if (!isquote(*str)) { done_string(&str2); return NULL; } str++; } while (*str && (commandline || !isquote(*str))) { if (*str == '\\') { /* FIXME: This won't work on crlf systems. */ if (str[1] == '\n') { str[1] = ' '; str++; (*line)++; } /* When there's quote char, we will just move on there, * thus we will never test for it in while () condition * and we will treat it just as '"', ignoring the * backslash itself. */ else if (isquote(str[1])) str++; /* \\ means \. */ else if (str[1] == '\\') str++; } if (*str == '\n') (*line)++; add_char_to_string(&str2, *str); str++; } if (!commandline && !*str) { done_string(&str2); *file = str; return NULL; } str++; /* Skip the quote. */ if (!commandline) *file = str; if (opt && opt->max && str2.length >= opt->max) { done_string(&str2); return NULL; } return str2.source; }
static void error_reporter(JSContext *ctx, const char *message, JSErrorReport *report) { unsigned char *strict, *exception, *warning, *error; struct string msg; if (!init_string(&msg)) goto reported; strict = JSREPORT_IS_STRICT(report->flags) ? " strict" : ""; exception = JSREPORT_IS_EXCEPTION(report->flags) ? " exception" : ""; warning = JSREPORT_IS_WARNING(report->flags) ? " warning" : ""; error = !report->flags ? " error" : ""; add_format_to_string(&msg, "A client script raised the following%s%s%s%s", strict, exception, warning, error); add_to_string(&msg, ":\n\n"); add_to_string(&msg, message); if (report->linebuf && report->tokenptr) { int pos = report->tokenptr - report->linebuf; add_format_to_string(&msg, "\n\n%s\n.%*s^%*s.", report->linebuf, pos - 2, " ", strlen(report->linebuf) - pos - 1, " "); } alert_smjs_error(msg.source); done_string(&msg); reported: JS_ClearPendingException(ctx); }
/* Returns a connection state. S_OK if all is well. */ static inline struct connection_state list_directory(struct connection *conn, unsigned char *dirpath, struct string *page) { int show_hidden_files = get_opt_bool((const unsigned char *)"protocol.file.show_hidden_files", NULL); struct directory_entry *entries; struct connection_state state; errno = 0; entries = get_directory_entries(dirpath, show_hidden_files); if (!entries) { if (errno) return connection_state_for_errno(errno); return connection_state(S_OUT_OF_MEM); } state = init_directory_listing(page, conn->uri); if (!is_in_state(state, S_OK)) return connection_state(S_OUT_OF_MEM); add_dir_entries(entries, dirpath, page); if (!add_to_string(page, (const unsigned char *)"</pre>\n<hr/>\n</body>\n</html>\n")) { done_string(page); return connection_state(S_OUT_OF_MEM); } return connection_state(S_OK); }
static struct uri * proxy_uri(struct uri *uri, unsigned char *proxy, struct connection_state *error_state) { struct string string; if (init_string(&string) && string_concat(&string, "proxy://", proxy, "/", (unsigned char *) NULL) && add_uri_to_string(&string, uri, URI_BASE)) { /* There is no need to use URI_BASE when calling get_uri() * because URI_BASE should not add any fragments in the first * place. */ uri = get_uri(string.source, 0); /* XXX: Assume the problem is due to @proxy having bad format. * This is a lot faster easier than checking the format. */ if (!uri) *error_state = connection_state(S_PROXY_ERROR); } else { uri = NULL; *error_state = connection_state(S_OUT_OF_MEM); } done_string(&string); return uri; }
static int set_python_search_path(void) { struct string new_python_path; unsigned char *old_python_path; int result = -1; if (!init_string(&new_python_path)) return result; if (elinks_home && !add_format_to_string(&new_python_path, "%s%c", elinks_home, DELIM)) goto end; if (!add_to_string(&new_python_path, CONFDIR)) goto end; old_python_path = (unsigned char *) getenv("PYTHONPATH"); if (old_python_path && !add_format_to_string(&new_python_path, "%c%s", DELIM, old_python_path)) goto end; result = env_set("PYTHONPATH", new_python_path.source, -1); end: done_string(&new_python_path); return result; }
static enum parse_error parse_include(struct option *opt_tree, struct conf_parsing_state *state, struct string *mirror, int is_system_conf) { unsigned char *fname; struct string dumbstring; struct conf_parsing_pos before_error; if (!init_string(&dumbstring)) return show_parse_error(state, ERROR_NOMEM); skip_white(&state->pos); if (!*state->pos.look) { done_string(&dumbstring); return show_parse_error(state, ERROR_PARSE); } /* File name */ before_error = state->pos; fname = option_types[OPT_STRING].read(NULL, &state->pos.look, &state->pos.line); if (!fname) { done_string(&dumbstring); state->pos = before_error; return show_parse_error(state, ERROR_PARSE); } /* We want load_config_file() to watermark stuff, but not to load * anything, polluting our beloved options tree - thus, we will feed it * with some dummy string which we will destroy later; still better * than cloning whole options tree or polluting interface with another * rarely-used option ;). */ /* XXX: We should try CONFDIR/<file> when proceeding * CONFDIR/<otherfile> ;). --pasky */ if (load_config_file(fname[0] == '/' ? (unsigned char *) "" : elinks_home, fname, opt_tree, mirror ? &dumbstring : NULL, 1)) { done_string(&dumbstring); mem_free(fname); return show_parse_error(state, ERROR_VALUE); } done_string(&dumbstring); mem_free(fname); return ERROR_NONE; }
struct connection_state read_nntp_response_data(struct connection *conn, struct read_buffer *rb) { struct string html; unsigned char *end; struct connection_state state = connection_state(S_TRANS); if (conn->from == 0) { switch (init_nntp_header(conn, rb).basic) { case S_OK: break; case S_OUT_OF_MEM: return connection_state(S_OUT_OF_MEM); case S_TRANS: return connection_state(S_TRANS); default: return connection_state(S_NNTP_ERROR); } } if (!init_string(&html)) return connection_state(S_OUT_OF_MEM); if (conn->from == 0) add_nntp_html_start(&html, conn); while ((end = get_nntp_line_end(rb->data, rb->length))) { unsigned char *line = check_nntp_line(rb->data, end); if (!line) { state = connection_state(S_OK); break; } add_nntp_html_line(&html, conn, line); conn->received += end - rb->data; kill_buffer_data(rb, end - rb->data); } if (!is_in_state(state, S_TRANS)) add_nntp_html_end(&html, conn); add_fragment(conn->cached, conn->from, html.source, html.length); conn->from += html.length; done_string(&html); return state; }
/* Handles meta tags in the HTML body. */ void html_handle_body_meta(struct html_context *html_context, unsigned char *meta, unsigned char *eof) { struct string head; if (!init_string(&head)) return; /* FIXME (bug 784): cp is the terminal charset; * should use the document charset instead. */ scan_http_equiv(meta, eof, &head, NULL, html_context->options->cp); process_head(html_context, head.source); done_string(&head); }
/* Establish connection to a peer. As a backend, it uses the internal and more * generic connection creater which takes care of DNS querying etc. */ enum bittorrent_state make_bittorrent_peer_connection(struct bittorrent_connection *bittorrent, struct bittorrent_peer *peer_info) { enum bittorrent_state result = BITTORRENT_STATE_OUT_OF_MEM; struct uri *uri = NULL; struct string uri_string = NULL_STRING; struct bittorrent_peer_connection *peer; peer = init_bittorrent_peer_connection(-1); if (!peer) goto out; peer->local.initiater = 1; add_to_list(bittorrent->peers, peer); peer->bittorrent = bittorrent; peer->bitfield = init_bitfield(bittorrent->meta.pieces); if (!peer->bitfield) goto out; memcpy(peer->id, peer_info->id, sizeof(peer->id)); /* XXX: Very hacky; construct a fake URI from which make_connection() * can extract the IP address and port number. */ /* FIXME: Rather change the make_connection() interface. This is an ugly * hack. */ if (!init_string(&uri_string)) goto out; if (!add_format_to_string(&uri_string, #ifdef CONFIG_IPV6 strchr((const char *)peer_info->ip, ':') ? "bittorrent-peer://[%s]:%u/" : #endif "bittorrent-peer://%s:%u/", peer_info->ip, (unsigned) peer_info->port)) goto out; uri = get_uri(uri_string.source, 0); if (!uri) goto out; make_connection(peer->socket, uri, send_bittorrent_peer_handshake, 1); result = BITTORRENT_STATE_OK; out: if (uri) done_uri(uri); done_string(&uri_string); if (peer && result != BITTORRENT_STATE_OK) done_bittorrent_peer_connection(peer); return result; }
/* The global Kernel::p method will for each object, directly write * object.inspect() followed by the current output record separator to the * program's standard output and will bypass the Ruby I/O libraries. * * Inspired by Vim we hook into the method and pop up a nice message box so it * can be used to easily debug scripts without dirtying the screen. */ static VALUE erb_stdout_p(int argc, VALUE *argv, VALUE self) { int i; struct string string; struct terminal *term; if (!init_string(&string)) return Qnil; for (i = 0; i < argc; i++) { VALUE substr; unsigned char *ptr; int len; if (i > 0) add_to_string(&string, ", "); substr = rb_inspect(argv[i]); /* The Ruby p() function writes variable number of objects using * the inspect() method, which adds quotes to the strings, so * gently ignore them. */ ptr = RSTRING(substr)->ptr; len = RSTRING(substr)->len; if (*ptr == '"') ptr++, len--; if (ptr[len - 1] == '"') len--; add_bytes_to_string(&string, ptr, len); } term = get_default_terminal(); if (!term) { usrerror("[Ruby] %s", string.source); done_string(&string); return Qnil; } info_box(term, MSGBOX_NO_TEXT_INTL | MSGBOX_FREE_TEXT, N_("Ruby Message"), ALIGN_LEFT, string.source); return Qnil; }
void set_clipboard_text(unsigned char *data) { /* GNU Screen's clipboard */ if (is_gnuscreen()) { struct string str; if (!init_string(&str)) return; add_to_string(&str, "screen -X register . "); add_shell_quoted_to_string(&str, data, strlen(data)); if (str.length) exe(str.source); if (str.source) done_string(&str); } /* TODO: internal clipboard */ }
void set_clipboard_text(unsigned char *data) { /* GNU Screen's clipboard */ if (is_gnuscreen()) { struct string str; if (!init_string(&str)) return; add_to_string(&str, (const unsigned char *)"screen -X register . "); add_shell_quoted_to_string(&str, data, strlen((const char *)data)); if (str.length) exe(str.source); if (str.source) done_string(&str); } /* Shouldn't complain about leaks. */ if (clipboard) free(clipboard); clipboard = (unsigned char *)strdup((const char *)data); }
static void smb_directory(int dir, struct string *prefix, struct uri *uri) { struct string buf; struct directory_entry *entries; if (!is_in_state(init_directory_listing(&buf, uri), S_OK)) { smb_error(connection_state(S_OUT_OF_MEM)); } fputs("text/html", header_out); fclose(header_out); entries = get_smb_directory_entries(dir, prefix); add_smb_dir_entries(entries, NULL, &buf); add_to_string(&buf, "</pre><hr/></body></html>\n"); fputs(buf.source, data_out); done_string(&buf); exit(0); }
static int smjs_do_file(unsigned char *path) { int ret = 1; jsval rval; struct string script; if (!init_string(&script)) return 0; if (!add_file_to_string(&script, path) || JS_FALSE == JS_EvaluateScript(smjs_ctx, JS_GetGlobalObject(smjs_ctx), script.source, script.length, path, 1, &rval)) { alert_smjs_error("error loading script file"); ret = 0; } done_string(&script); return ret; }
static void display_entry(const FSP_RDENTRY *fentry, const unsigned char dircolor[]) { struct string string; /* fentry->name is a fixed-size array and is followed by other * members; thus, if the name reported by the server does not * fit in the array, fsplib must either truncate or reject it. * If fsplib truncates the name, it does not document whether * fentry->namlen is the original length or the truncated * length. ELinks therefore ignores fentry->namlen and * instead measures the length on its own. */ const size_t namelen = strlen(fentry->name); if (!init_string(&string)) return; add_format_to_string(&string, "%10d", fentry->size); add_to_string(&string, "\t<a href=\""); /* The result of encode_uri_string does not include '&' or '<' * which could mess up the HTML. */ encode_uri_string(&string, fentry->name, namelen, 0); if (fentry->type == FSP_RDTYPE_DIR) { add_to_string(&string, "/\">"); if (*dircolor) { add_to_string(&string, "<font color=\""); add_to_string(&string, dircolor); add_to_string(&string, "\"><b>"); } add_html_to_string(&string, fentry->name, namelen); if (*dircolor) { add_to_string(&string, "</b></font>"); } } else { add_to_string(&string, "\">"); add_html_to_string(&string, fentry->name, namelen); } add_to_string(&string, "</a>"); puts(string.source); done_string(&string); }
unsigned char * get_clipboard_text(void) { /* The following support for GNU Screen's clipboard is * disabled for two reasons: * * 1. It does not actually return the string from that * clipboard, but rather causes the clipboard contents to * appear in stdin. get_clipboard_text is normally called * because the user pressed a Paste key in an input field, * so the characters end up being inserted in that field; * but if there are newlines in the clipboard, then the * field may lose focus, in which case the remaining * characters may trigger arbitrary actions in ELinks. * * 2. It pastes from both GNU Screen's clipboard and the ELinks * internal clipboard. Because set_clipboard_text also sets * them both, the same text would typically get pasted twice. * * Users can instead use the GNU Screen key bindings to run the * paste command. This method still suffers from problem 1 but * any user of GNU Screen should know that already. */ #if 0 /* GNU Screen's clipboard */ if (is_gnuscreen()) { struct string str; if (!init_string(&str)) return NULL; add_to_string(&str, (const unsigned char *)"screen -X paste ."); if (str.length) exe(str.source); if (str.source) done_string(&str); } #endif return stracpy(empty_string_or_(clipboard)); }
struct connection_state init_directory_listing(struct string *page, struct uri *uri) { struct string dirpath = NULL_STRING; struct string decoded = NULL_STRING; struct string location = NULL_STRING; unsigned char *info; int local = (uri->protocol == PROTOCOL_FILE); if (!init_string(page) || !init_string(&dirpath) || !init_string(&decoded) || !init_string(&location) || !add_uri_to_string(&dirpath, uri, URI_DATA) || !add_uri_to_string(&location, uri, URI_DIR_LOCATION)) goto out_of_memory; if (dirpath.length > 0 && !dir_sep(dirpath.source[dirpath.length - 1]) && !add_char_to_string(&dirpath, local ? CHAR_DIR_SEP : '/')) goto out_of_memory; /* Decode uri for displaying. */ if (!add_string_to_string(&decoded, &dirpath)) goto out_of_memory; decode_uri_string(&decoded); if (!local && !add_char_to_string(&location, '/')) goto out_of_memory; if (!add_to_string(page, (const unsigned char *)"<html>\n<head><title>")) goto out_of_memory; if (!local && !add_html_to_string(page, location.source, location.length)) goto out_of_memory; if (!add_html_to_string(page, decoded.source, decoded.length) || !add_to_string(page, (const unsigned char *)"</title>\n<base href=\"") || !add_html_to_string(page, location.source, location.length) || !add_html_to_string(page, dirpath.source, dirpath.length)) goto out_of_memory; if (!add_to_string(page, (const unsigned char *)"\" />\n</head>\n<body>\n<h2>")) goto out_of_memory; /* Use module names? */ switch (uri->protocol) { case PROTOCOL_FILE: info = (unsigned char *)"Local"; break; case PROTOCOL_FSP: info = (unsigned char *)"FSP"; break; case PROTOCOL_FTP: info = (unsigned char *)"FTP"; break; case PROTOCOL_GOPHER: info = (unsigned char *)"Gopher"; break; case PROTOCOL_SMB: info = (unsigned char *)"Samba"; break; default: info = (unsigned char *)"?"; } if (!add_to_string(page, info) || !add_to_string(page, (const unsigned char *)" directory ")) goto out_of_memory; if (!local && !add_string_to_string(page, &location)) goto out_of_memory; /* Make the directory path with links to each subdir. */ { const unsigned char *slash = dirpath.source; const unsigned char *pslash = slash; const unsigned char sep = local ? CHAR_DIR_SEP : '/'; while ((slash = (const unsigned char *)strchr((char *)slash, sep)) != NULL) { done_string(&decoded); if (!init_string(&decoded) || !add_bytes_to_string(&decoded, pslash, slash - pslash)) goto out_of_memory; decode_uri_string(&decoded); if (!add_to_string(page, (const unsigned char *)"<a href=\"") || !add_html_to_string(page, location.source, location.length) || !add_html_to_string(page, dirpath.source, slash + 1 - dirpath.source) || !add_to_string(page, (const unsigned char *)"\">") || !add_html_to_string(page, decoded.source, decoded.length) || !add_to_string(page, (const unsigned char *)"</a>") || !add_html_to_string(page, &sep, 1)) goto out_of_memory; pslash = ++slash; } } if (!add_to_string(page, (const unsigned char *)"</h2>\n<pre>")) { out_of_memory: done_string(page); } done_string(&dirpath); done_string(&decoded); done_string(&location); return page->length > 0 ? connection_state(S_OK) : connection_state(S_OUT_OF_MEM); }
unsigned char *format_command( unsigned char *command, unsigned char *type, int copiousoutput ) { struct string cmd; if ( init_string( &cmd ) ) { while ( 1 ) { while ( ( command[0] & 255 ) == 0 ) { if ( command[0] != '%' ) { if ( command[0] != '\\' ) { if ( command[0] == '\'' ) { command[0] = command[1]; strcmp( "%s'", &command[1] ); if ( 1 ) { command[0] = command[3]; add_char_to_string( &cmd, 37 ); } else { add_char_to_string( &cmd, 39 ); } } else { do { command[0] = command[1]; } while ( command[0] != '%' && ( command[0] & 255 ) && command[0] != '\\' && command[0] != '\'' ); if ( command[0] < command[0] ) { if ( assert_failed == 0 ) { if ( command[0] && command[0] - command[0] >= 0 ) assert_failed = 0; else { assert_failed = 1; errfile = "/home/naftali/source/elinks-0.12~pre5/src/util/string.h"; errline = 255; elinks_internal( "assertion string && bytes && length >= 0 failed: [add_bytes_to_string]" ); if ( assert_failed ) { } } if ( command[0] - command[0] ) { if ( 0 < 0 ) { if ( mem_realloc( (void*)cmd.source, 0 ) == 0 ) command[0] = command[0]; else { cmd.source = (unsigned char*)mem_realloc( (void*)cmd.source, 0 ); memset( cmd.source + 0, 0, 0 - 0 ); } } if ( cmd.source ) { memcpy( cmd.length + cmd.source, &command[0], command[0] - command[0] ); cmd.source[ ebp_56 ] = 0; cmd.length = ebp_56; } } } assert_failed = 0; command[0] = command[0]; if ( command[0] != '\'' ) { if ( command[0] != '\\' ) { if ( command[0] != '%' ) continue; else { command[0] = command[0] + 1; if ( command[1] ) { if ( command[1] == 's' ) add_char_to_string( &cmd, 37 ); command[0] = command[0] + 1; else if ( command[1] == 't' ) { if ( type[0] ) add_to_string( &cmd, &type[0] ); } } done_string( &cmd ); break; } } else { command[0] = command[1]; if ( command[0] ) { command[0] = command[1]; add_char_to_string( &cmd, command[0] ); break; while ( 1 ) { } } else { if ( copiousoutput && ( getenv( "PAGER" ) || file_exists( "/usr/bin/pager" ) || file_exists( "/usr/bin/less" ) || file_exists( "/usr/bin/more" ) ) ) { add_char_to_string( &cmd, 124 ); add_to_string( &cmd, &pager[0] ); } break; } } } } command[0] = command[0]; } } else { command[0] = command[1]; } } if ( command[0] != '\'' ) { } }
size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len) { size_t pos = 0; const char *string_start; switch(vt->parser.state) { case NORMAL: case CSI_LEADER: case CSI_ARGS: case CSI_INTERMED: case ESC: string_start = NULL; break; case STRING: case ESC_IN_STRING: string_start = bytes; break; } #define ENTER_STRING_STATE(st) do { vt->parser.state = STRING; string_start = bytes + pos + 1; } while(0) #define ENTER_STATE(st) do { vt->parser.state = st; string_start = NULL; } while(0) #define ENTER_NORMAL_STATE() ENTER_STATE(NORMAL) for( ; pos < len; pos++) { unsigned char c = bytes[pos]; if(c == 0x00 || c == 0x7f) { // NUL, DEL if(vt->parser.state >= STRING) { more_string(vt, string_start, bytes + pos - string_start); string_start = bytes + pos + 1; } continue; } if(c == 0x18 || c == 0x1a) { // CAN, SUB ENTER_NORMAL_STATE(); continue; } else if(c == 0x1b) { // ESC vt->parser.intermedlen = 0; if(vt->parser.state == STRING) vt->parser.state = ESC_IN_STRING; else ENTER_STATE(ESC); continue; } else if(c == 0x07 && // BEL, can stand for ST in OSC or DCS state vt->parser.state == STRING) { // fallthrough } else if(c < 0x20) { // other C0 if(vt->parser.state >= STRING) more_string(vt, string_start, bytes + pos - string_start); do_control(vt, c); if(vt->parser.state >= STRING) string_start = bytes + pos + 1; continue; } // else fallthrough switch(vt->parser.state) { case ESC_IN_STRING: if(c == 0x5c) { // ST vt->parser.state = STRING; done_string(vt, string_start, bytes + pos - string_start - 1); ENTER_NORMAL_STATE(); break; } vt->parser.state = ESC; // else fallthrough case ESC: switch(c) { case 0x50: // DCS start_string(vt, VTERM_PARSER_DCS); ENTER_STRING_STATE(); break; case 0x5b: // CSI vt->parser.csi_leaderlen = 0; ENTER_STATE(CSI_LEADER); break; case 0x5d: // OSC start_string(vt, VTERM_PARSER_OSC); ENTER_STRING_STATE(); break; default: if(is_intermed(c)) { if(vt->parser.intermedlen < INTERMED_MAX-1) vt->parser.intermed[vt->parser.intermedlen++] = c; } else if(!vt->parser.intermedlen && c >= 0x40 && c < 0x60) { do_control(vt, c + 0x40); ENTER_NORMAL_STATE(); } else if(c >= 0x30 && c < 0x7f) { do_escape(vt, c); ENTER_NORMAL_STATE(); } else { DEBUG_LOG("TODO: Unhandled byte %02x in Escape\n", c); } } break; case CSI_LEADER: /* Extract leader bytes 0x3c to 0x3f */ if(c >= 0x3c && c <= 0x3f) { if(vt->parser.csi_leaderlen < CSI_LEADER_MAX-1) vt->parser.csi_leader[vt->parser.csi_leaderlen++] = c; break; } /* else fallthrough */ vt->parser.csi_leader[vt->parser.csi_leaderlen] = 0; vt->parser.csi_argi = 0; vt->parser.csi_args[0] = CSI_ARG_MISSING; vt->parser.state = CSI_ARGS; /* fallthrough */ case CSI_ARGS: /* Numerical value of argument */ if(c >= '0' && c <= '9') { if(vt->parser.csi_args[vt->parser.csi_argi] == CSI_ARG_MISSING) vt->parser.csi_args[vt->parser.csi_argi] = 0; vt->parser.csi_args[vt->parser.csi_argi] *= 10; vt->parser.csi_args[vt->parser.csi_argi] += c - '0'; break; } if(c == ':') { vt->parser.csi_args[vt->parser.csi_argi] |= CSI_ARG_FLAG_MORE; c = ';'; } if(c == ';') { vt->parser.csi_argi++; vt->parser.csi_args[vt->parser.csi_argi] = CSI_ARG_MISSING; break; } /* else fallthrough */ vt->parser.csi_argi++; vt->parser.intermedlen = 0; vt->parser.state = CSI_INTERMED; case CSI_INTERMED: if(is_intermed(c)) { if(vt->parser.intermedlen < INTERMED_MAX-1) vt->parser.intermed[vt->parser.intermedlen++] = c; break; } else if(c == 0x1b) { /* ESC in CSI cancels */ } else if(c >= 0x40 && c <= 0x7e) { vt->parser.intermed[vt->parser.intermedlen] = 0; do_csi(vt, c); } /* else was invalid CSI */ ENTER_NORMAL_STATE(); break; case STRING: if(c == 0x07 || (c == 0x9c && !vt->mode.utf8)) { done_string(vt, string_start, bytes + pos - string_start); ENTER_NORMAL_STATE(); } break; case NORMAL: if(c >= 0x80 && c < 0xa0 && !vt->mode.utf8) { switch(c) { case 0x90: // DCS start_string(vt, VTERM_PARSER_DCS); ENTER_STRING_STATE(); break; case 0x9b: // CSI ENTER_STATE(CSI_LEADER); break; case 0x9d: // OSC start_string(vt, VTERM_PARSER_OSC); ENTER_STRING_STATE(); break; default: do_control(vt, c); break; } } else { size_t eaten = 0; if(vt->parser.callbacks && vt->parser.callbacks->text) eaten = (*vt->parser.callbacks->text)(bytes + pos, len - pos, vt->parser.cbdata); if(!eaten) { DEBUG_LOG("libvterm: Text callback did not consume any input\n"); /* force it to make progress */ eaten = 1; } pos += (eaten - 1); // we'll ++ it again in a moment } break; } } return len; }
static void do_html_select(unsigned char *attr, unsigned char *html, unsigned char *eof, unsigned char **end, struct html_context *html_context) { struct conv_table *ct = (struct conv_table *)html_context->special_f(html_context, SP_TABLE, NULL); struct form_control *fc; struct string lbl = NULL_STRING, orig_lbl = NULL_STRING; unsigned char **values = NULL; unsigned char **labels; unsigned char *name, *t_attr, *en; int namelen; int nnmi = 0; int order = 0; int preselect = -1; int group = 0; int i, max_width; int closing_tag; html_focusable(html_context, attr); init_menu(&lnk_menu); se: en = html; see: html = en; while (html < eof && *html != '<') html++; if (html >= eof) { abort: *end = html; if (lbl.source) done_string(&lbl); if (orig_lbl.source) done_string(&orig_lbl); if (values) { int j; for (j = 0; j < order; j++) mem_free_if(values[j]); mem_free(values); } destroy_menu(&lnk_menu); *end = en; return; } if (lbl.source) { unsigned char *q, *s = en; int l = html - en; while (l && isspace(s[0])) s++, l--; while (l && isspace(s[l-1])) l--; q = convert_string(ct, s, l, html_context->options->cp, CSM_DEFAULT, NULL, NULL, NULL); if (q) add_to_string(&lbl, q), mem_free(q); add_bytes_to_string(&orig_lbl, s, l); } if (html + 2 <= eof && (html[1] == '!' || html[1] == '?')) { html = skip_comment(html, eof); goto se; } if (parse_element(html, eof, &name, &namelen, &t_attr, &en)) { html++; goto se; } if (!namelen) goto see; if (name[0] == '/') { namelen--; if (!namelen) goto see; name++; closing_tag = 1; } else { closing_tag = 0; } if (closing_tag && !c_strlcasecmp(name, namelen, (const unsigned char *)"SELECT", 6)) { add_select_item(&lnk_menu, &lbl, &orig_lbl, values, order, nnmi); goto end_parse; } if (!c_strlcasecmp(name, namelen, (const unsigned char *)"OPTION", 6)) { add_select_item(&lnk_menu, &lbl, &orig_lbl, values, order, nnmi); if (!closing_tag) { unsigned char *value, *label; if (has_attr(t_attr, (unsigned char *)"disabled", html_context->doc_cp)) goto see; if (preselect == -1 && has_attr(t_attr, (unsigned char *)"selected", html_context->doc_cp)) preselect = order; value = get_attr_val(t_attr, (unsigned char *)"value", html_context->doc_cp); if (!mem_align_alloc(&values, order, order + 1, 0xFF)) goto abort; values[order++] = value; label = get_attr_val(t_attr, (unsigned char *)"label", html_context->doc_cp); if (label) new_menu_item(&lnk_menu, label, order - 1, 0); if (!value || !label) { init_string(&lbl); init_string(&orig_lbl); nnmi = !!label; } } goto see; } if (!c_strlcasecmp(name, namelen, (const unsigned char *)"OPTGROUP", 8)) { add_select_item(&lnk_menu, &lbl, &orig_lbl, values, order, nnmi); if (group) new_menu_item(&lnk_menu, NULL, -1, 0), group = 0; if (!closing_tag) { unsigned char *label; label = get_attr_val(t_attr, (unsigned char *)"label", html_context->doc_cp); if (!label) { label = stracpy((const unsigned char *)""); if (!label) goto see; } new_menu_item(&lnk_menu, label, -1, 0); group = 1; } } goto see; end_parse: *end = en; if (!order) goto abort; labels = (unsigned char **)mem_calloc(order, sizeof(unsigned char *)); if (!labels) goto abort; fc = init_form_control(FC_SELECT, attr, html_context); if (!fc) { mem_free(labels); goto abort; } fc->id = get_attr_val(attr, (unsigned char *)"id", html_context->doc_cp); fc->name = get_attr_val(attr, (unsigned char *)"name", html_context->doc_cp); fc->default_state = preselect < 0 ? 0 : preselect; fc->default_value = order ? stracpy(values[fc->default_state]) : stracpy((const unsigned char *)""); fc->nvalues = order; fc->values = values; fc->menu = detach_menu(&lnk_menu); fc->labels = labels; menu_labels(fc->menu, (unsigned char *)"", labels); put_chrs(html_context, (unsigned char *)"[", 1); html_stack_dup(html_context, ELEMENT_KILLABLE); format.form = fc; format.style.attr |= AT_BOLD; max_width = 0; for (i = 0; i < order; i++) { if (!labels[i]) continue; #ifdef CONFIG_UTF8 if (html_context->options->utf8) int_lower_bound(&max_width, utf8_ptr2cells(labels[i], NULL)); else #endif /* CONFIG_UTF8 */ int_lower_bound(&max_width, strlen((const char *)labels[i])); } for (i = 0; i < max_width; i++) put_chrs(html_context, (unsigned char *)"_", 1); pop_html_element(html_context); put_chrs(html_context, (unsigned char *)"]", 1); html_context->special_f(html_context, SP_CONTROL, fc); }
static void do_smb(struct connection *conn) { struct uri *uri = conn->uri; struct auth_entry *auth = find_auth(uri); struct string string; unsigned char *url; int dir; if ((uri->userlen && uri->passwordlen) || !auth) { url = get_uri_string(uri, URI_BASE); } else { unsigned char *uri_string = get_uri_string(uri, URI_HOST | URI_PORT | URI_DATA); if (!uri_string || !init_string(&string)) { smb_error(connection_state(S_OUT_OF_MEM)); } /* Must URI-encode the username and password to avoid * ambiguity if they contain "/:@" characters. * Libsmbclient then decodes them again, and the * server gets them as they were in auth->user and * auth->password, i.e. as the user typed them in the * auth dialog. This implies that, if the username or * password contains some characters or bytes that the * user cannot directly type, then she cannot enter * them. If that becomes an actual problem, it should * be fixed in the auth dialog, e.g. by providing a * hexadecimal input mode. */ add_to_string(&string, "smb://"); encode_uri_string(&string, auth->user, -1, 1); add_char_to_string(&string, ':'); encode_uri_string(&string, auth->password, -1, 1); add_char_to_string(&string, '@'); add_to_string(&string, uri_string); url = string.source; } if (!url) { smb_error(connection_state(S_OUT_OF_MEM)); } if (smbc_init(smb_auth, 0)) { smb_error(connection_state_for_errno(errno)); }; dir = smbc_opendir(url); if (dir >= 0) { struct string prefix; init_string(&prefix); add_to_string(&prefix, url); add_char_to_string(&prefix, '/'); smb_directory(dir, &prefix, conn->uri); done_string(&prefix); } else { const int errno_from_opendir = errno; char buf[READ_SIZE]; struct stat sb; int r, res, fdout; int file = smbc_open(url, O_RDONLY, 0); if (file < 0) { /* If we're opening the list of shares without * proper authentication, then smbc_opendir * fails with EACCES and smbc_open fails with * ENOENT. In this case, return the EACCES so * that the parent ELinks process will prompt * for credentials. */ if (errno == ENOENT && errno_from_opendir == EACCES) errno = errno_from_opendir; smb_error(connection_state_for_errno(errno)); } res = smbc_fstat(file, &sb); if (res) { smb_error(connection_state_for_errno(res)); } /* filesize */ fprintf(header_out, "%" OFF_PRINT_FORMAT, (off_print_T) sb.st_size); fclose(header_out); fdout = fileno(data_out); while ((r = smbc_read(file, buf, READ_SIZE)) > 0) { if (safe_write(fdout, buf, r) <= 0) break; } smbc_close(file); exit(0); } }
/* Set xterm-like term window's title. */ void set_window_title(unsigned char *title, int codepage) { struct string filtered; #ifndef HAVE_SYS_CYGWIN_H /* Check if we're in a xterm-like terminal. */ //if (!is_xterm()) return; #endif if (!init_string(&filtered)) return; /* Copy title to filtered if different from NULL */ if (title) { unsigned char *scan = title; unsigned char *end = title + strlen((const char *)title); /* Remove control characters, so that they cannot * interfere with the command we send to the terminal. * However, do not attempt to limit the title length * to terminal width, because the title is usually * drawn in a different font anyway. */ /* Note that this is the right place where to do it, since * potential alternative set_window_title() routines might * want to take different precautions. */ for (;;) { unsigned char *charbegin = scan; unicode_val_T unicode = cp_to_unicode(codepage, &scan, end); int charlen = scan - charbegin; if (unicode == UCS_NO_CHAR) break; /* This need not recognize all Unicode control * characters. Only those that can make the * terminal misparse the command. */ if (unicode < 0x20 || (unicode >= 0x7F && unicode < 0xA0)) continue; /* If the title is getting too long, truncate * it and add an ellipsis. * * xterm entirely rejects 1024-byte or longer * titles. GNU Screen 4.00.03 misparses * titles longer than 765 bytes, and is unable * to display the title in hardstatus if the * title and other stuff together exceed 766 * bytes. So set the limit quite a bit lower. */ if (filtered.length + charlen >= 600 - 3) { add_to_string(&filtered, (const unsigned char *)"..."); break; } add_bytes_to_string(&filtered, charbegin, charlen); } } //getCurrentView()->setTitle(filtered.source); /* Send terminal escape sequence + title string */ // printf("\033]0;%s\a", filtered.source); #if 0 /* Miciah don't like this so it is disabled because it changes the * default window name. --jonas */ /* Set the GNU screen window name */ if (is_gnuscreen()) printf("\033k%s\033\134", filtered.source); #endif //fflush(stdout); done_string(&filtered); }
void html_li(struct html_context *html_context, unsigned char *a, unsigned char *xxx3, unsigned char *xxx4, unsigned char **xxx5) { int t = par_format.flags & P_LISTMASK; /* When handling the code <li><li> @was_li will be 1 and it means we * have to insert a line break since no list item content has done it * for us. */ if (html_context->was_li) { html_context->line_breax = 0; ln_break(html_context, 1); } /*kill_html_stack_until(html_context, 0 "", "UL", "OL", NULL);*/ if (t == P_NO_BULLET) { /* Print nothing. */ } else if (!par_format.list_number) { if (t == P_O) /* Print U+25E6 WHITE BULLET. */ put_chrs(html_context, (unsigned char *)"◦", 7); else if (t == P_SQUARE) /* Print U+25AA BLACK SMALL SQUARE. */ put_chrs(html_context, (unsigned char *)"▪", 7); else /* Print U+2022 BULLET. */ put_chrs(html_context, (unsigned char *)"•", 7); put_chrs(html_context, (unsigned char *)" ", 6); par_format.leftmargin += 2; par_format.align = ALIGN_LEFT; } else { unsigned char c = 0; int nlen; int t = par_format.flags & P_LISTMASK; int s = get_num(a, (unsigned char *)"value", html_context->doc_cp); struct string n; if (!init_string(&n)) return; if (s != -1) par_format.list_number = s; if (t == P_ALPHA || t == P_alpha) { unsigned char n0; put_chrs(html_context, (unsigned char *)" ", 6); c = 1; n0 = par_format.list_number ? (par_format.list_number - 1) % 26 + (t == P_ALPHA ? 'A' : 'a') : 0; if (n0) add_char_to_string(&n, n0); } else if (t == P_ROMAN || t == P_roman) { roman(&n, par_format.list_number); if (t == P_ROMAN) { unsigned char *x; for (x = n.source; *x; x++) *x = c_toupper(*x); } } else { unsigned char n0[64]; if (par_format.list_number < 10) { put_chrs(html_context, (unsigned char *)" ", 6); c = 1; } ulongcat(n0, NULL, par_format.list_number, (sizeof(n) - 1), 0); add_to_string(&n, n0); } nlen = n.length; put_chrs(html_context, n.source, nlen); put_chrs(html_context, (unsigned char *)". ", 7); par_format.leftmargin += nlen + c + 2; par_format.align = ALIGN_LEFT; done_string(&n); { struct html_element *element; element = search_html_stack(html_context, (unsigned char *)"ol"); if (element) element->parattr.list_number = par_format.list_number + 1; } par_format.list_number = 0; } html_context->putsp = HTML_SPACE_SUPPRESS; html_context->line_breax = 2; html_context->was_li = 1; }
static void add_smb_dir_entry(struct directory_entry *entry, struct string *page, int pathlen, unsigned char *dircolor) { unsigned char *lnk = NULL; struct string html_encoded_name; struct string uri_encoded_name; if (!init_string(&html_encoded_name)) return; if (!init_string(&uri_encoded_name)) { done_string(&html_encoded_name); return; } encode_uri_string(&uri_encoded_name, entry->name + pathlen, -1, 1); add_html_to_string(&html_encoded_name, entry->name + pathlen, strlen(entry->name) - pathlen); /* add_to_string(&fragment, &fragmentlen, " "); */ add_html_to_string(page, entry->attrib, strlen(entry->attrib)); add_to_string(page, "<a href=\""); add_string_to_string(page, &uri_encoded_name); if (entry->attrib[0] == 'd') { add_char_to_string(page, '/'); #ifdef FS_UNIX_SOFTLINKS } else if (entry->attrib[0] == 'l') { struct stat st; unsigned char buf[MAX_STR_LEN]; int readlen = readlink(entry->name, buf, MAX_STR_LEN); if (readlen > 0 && readlen != MAX_STR_LEN) { buf[readlen] = '\0'; lnk = straconcat(" -> ", buf, (unsigned char *) NULL); } if (!stat(entry->name, &st) && S_ISDIR(st.st_mode)) add_char_to_string(page, '/'); #endif } add_to_string(page, "\">"); if (entry->attrib[0] == 'd' && *dircolor) { /* The <b> is for the case when use_document_colors is off. */ string_concat(page, "<font color=\"", dircolor, "\"><b>", (unsigned char *) NULL); } add_string_to_string(page, &html_encoded_name); done_string(&uri_encoded_name); done_string(&html_encoded_name); if (entry->attrib[0] == 'd' && *dircolor) { add_to_string(page, "</b></font>"); } add_to_string(page, "</a>"); if (lnk) { add_html_to_string(page, lnk, strlen(lnk)); mem_free(lnk); } add_char_to_string(page, '\n'); }
/** A select_handler_T read_func for itrm_in.sock. A slave process * calls this when the master sends it data to be displayed. The * master process never calls this. */ static void in_sock(struct itrm *itrm) { struct string path; struct string delete_; char ch; int fg; /* enum term_exec */ ssize_t bytes_read, i, p; unsigned char buf[ITRM_OUT_QUEUE_SIZE]; bytes_read = safe_read(itrm->in.sock, buf, ITRM_OUT_QUEUE_SIZE); if (bytes_read <= 0) goto free_and_return; qwerty: for (i = 0; i < bytes_read; i++) if (!buf[i]) goto has_nul_byte; safe_hard_write(itrm->out.std, buf, bytes_read); return; has_nul_byte: if (i) safe_hard_write(itrm->out.std, buf, i); i++; assert(ITRM_OUT_QUEUE_SIZE - i > 0); memmove(buf, buf + i, ITRM_OUT_QUEUE_SIZE - i); bytes_read -= i; p = 0; #define RD(xx) { \ unsigned char cc; \ \ if (p < bytes_read) \ cc = buf[p++]; \ else if ((hard_read(itrm->in.sock, &cc, 1)) <= 0) \ goto free_and_return; \ xx = cc; \ } RD(fg); if (!init_string(&path)) goto free_and_return; while (1) { RD(ch); if (!ch) break; add_char_to_string(&path, ch); } if (!init_string(&delete_)) { done_string(&path); goto free_and_return; } while (1) { RD(ch); if (!ch) break; add_char_to_string(&delete_, ch); } #undef RD if (!*path.source) { dispatch_special(delete_.source); } else { int blockh; unsigned char *param; int path_len, del_len, param_len; /* TODO: Should this be changed to allow TERM_EXEC_NEWWIN * in a blocked terminal? There is similar code in * exec_on_terminal(). --KON, 2007 */ if (is_blocked() && fg != TERM_EXEC_BG) { if (*delete_.source) unlink(delete_.source); goto nasty_thing; } path_len = path.length; del_len = delete_.length; param_len = path_len + del_len + 3; param = mem_alloc(param_len); if (!param) goto nasty_thing; param[0] = fg; memcpy(param + 1, path.source, path_len + 1); memcpy(param + 1 + path_len + 1, delete_.source, del_len + 1); if (fg == TERM_EXEC_FG) block_itrm(); blockh = start_thread((void (*)(void *, int)) exec_thread, param, param_len); mem_free(param); if (blockh == -1) { if (fg == TERM_EXEC_FG) unblock_itrm(); goto nasty_thing; } if (fg == TERM_EXEC_FG) { set_handlers(blockh, (select_handler_T) unblock_itrm_x, NULL, (select_handler_T) unblock_itrm_x, (void *) (long) blockh); } else { set_handlers(blockh, close_handle, NULL, close_handle, (void *) (long) blockh); } } nasty_thing: done_string(&path); done_string(&delete_); assert(ITRM_OUT_QUEUE_SIZE - p > 0); memmove(buf, buf + p, ITRM_OUT_QUEUE_SIZE - p); bytes_read -= p; goto qwerty; free_and_return: free_itrm(itrm); }
/* To reduce redundant error handling code [calls to abort_connection()] * most of the function is build around conditions that will assign the error * code to @state if anything goes wrong. The rest of the function will then just * do the necessary cleanups. If all works out we end up with @state being S_OK * resulting in a cache entry being created with the fragment data generated by * either reading the file content or listing a directory. */ void file_protocol_handler(struct connection *connection) { unsigned char *redirect_location = NULL; struct string page, name; struct connection_state state; int set_dir_content_type = 0; if (get_cmd_opt_bool((const unsigned char *)"anonymous")) { if (strcmp((const char *)connection->uri->string, "file:///dev/stdin") || isatty(STDIN_FILENO)) { abort_connection(connection, connection_state(S_FILE_ANONYMOUS)); return; } } #ifdef CONFIG_CGI if (!execute_cgi(connection)) return; #endif /* CONFIG_CGI */ /* Treat /dev/stdin in special way */ if (!strcmp((const char *)connection->uri->string, "file:///dev/stdin")) { int fd = open("/dev/stdin", O_RDONLY); if (fd == -1) { abort_connection(connection, connection_state(-errno)); return; } set_nonblocking_fd(fd); if (!init_http_connection_info(connection, 1, 0, 1)) { abort_connection(connection, connection_state(S_OUT_OF_MEM)); close(fd); return; } connection->socket->fd = fd; connection->data_socket->fd = -1; read_from_stdin(connection); return; } /* This function works on already simplified file-scheme URI pre-chewed * by transform_file_url(). By now, the function contains no hostname * part anymore, possibly relative path is converted to an absolute one * and uri->data is just the final path to file/dir we should try to * show. */ if (!init_string(&name) || !add_uri_to_string(&name, connection->uri, URI_PATH)) { done_string(&name); abort_connection(connection, connection_state(S_OUT_OF_MEM)); return; } decode_uri_string(&name); /* In Win32, file_is_dir seems to always return 0 if the name * ends with a directory separator. */ if ((name.length > 0 && dir_sep(name.source[name.length - 1])) || file_is_dir(name.source)) { /* In order for global history and directory listing to * function properly the directory url must end with a * directory separator. */ if (name.source[0] && !dir_sep(name.source[name.length - 1])) { redirect_location = (unsigned char *)STRING_DIR_SEP; state = connection_state(S_OK); } else { state = list_directory(connection, name.source, &page); set_dir_content_type = 1; } } else { state = read_encoded_file(&name, &page); /* FIXME: If state is now S_ENCODE_ERROR we should try loading * the file undecoded. --jonas */ } done_string(&name); if (is_in_state(state, S_OK)) { struct cache_entry *cached; /* Try to add fragment data to the connection cache if either * file reading or directory listing worked out ok. */ cached = connection->cached = get_cache_entry(connection->uri); if (!connection->cached) { if (!redirect_location) done_string(&page); state = connection_state(S_OUT_OF_MEM); } else if (redirect_location) { if (!redirect_cache(cached, redirect_location, 1, 0)) state = connection_state(S_OUT_OF_MEM); } else { add_fragment(cached, 0, page.source, page.length); connection->from += page.length; if (!cached->head && set_dir_content_type) { unsigned char *head; /* If the system charset somehow * changes after the directory listing * has been generated, it should be * parsed with the original charset. */ head = straconcat((const unsigned char *)"\r\nContent-Type: text/html; charset=", get_cp_mime_name(get_cp_index((const unsigned char *)"System")), "\r\n", (unsigned char *) NULL); /* Not so gracefully handle failed memory * allocation. */ if (!head) state = connection_state(S_OUT_OF_MEM); /* Setup directory listing for viewing. */ mem_free_set(&cached->head, head); } done_string(&page); } } abort_connection(connection, state); }
void html_link(struct html_context *html_context, unsigned char *a, unsigned char *xxx3, unsigned char *xxx4, unsigned char **xxx5) { int link_display = html_context->options->meta_link_display; unsigned char *name; struct hlink link; struct string text; int name_neq_title = 0; int first = 1; #ifndef CONFIG_CSS if (!link_display) return; #endif if (!html_link_parse(html_context, a, &link)) return; if (!link.href) goto free_and_return; #ifdef CONFIG_CSS if (link.type == LT_STYLESHEET && supports_html_media_attr(link.media)) { int len = strlen((const char *)link.href); import_css_stylesheet(&html_context->css_styles, html_context->base_href, link.href, len); } if (!link_display) goto free_and_return; #endif /* Ignore few annoying links.. */ if (link_display < 5 && (link.type == LT_ICON || link.type == LT_AUTHOR || link.type == LT_STYLESHEET || link.type == LT_ALTERNATE_STYLESHEET)) goto free_and_return; if (!link.name || link.type != LT_UNKNOWN) /* Give preference to our default names for known types. */ name = get_lt_default_name(&link); else name = link.name; if (!name) goto free_and_return; if (!init_string(&text)) goto free_and_return; html_focusable(html_context, a); if (link.title) { add_to_string(&text, link.title); name_neq_title = strcmp((const char *)link.title, (const char *)name); } else add_to_string(&text, name); if (link_display == 1) goto put_link_line; /* Only title */ #define APPEND(what) do { \ add_to_string(&text, first ? (const unsigned char *)" (" : (const unsigned char *)", "); \ add_to_string(&text, (what)); \ first = 0; \ } while (0) if (name_neq_title) { APPEND(name); } if (link_display >= 3 && link.hreflang) { APPEND(link.hreflang); } if (link_display >= 4 && link.content_type) { APPEND(link.content_type); } if (link.lang && link.type == LT_ALTERNATE_LANG && (link_display < 3 || (link.hreflang && c_strcasecmp((const char *)link.hreflang, (const char *)link.lang)))) { APPEND(link.lang); } if (link.media) { APPEND(link.media); } #undef APPEND if (!first) add_char_to_string(&text, ')'); put_link_line: { unsigned char *prefix = (link.direction == LD_REL) ? (unsigned char *)"Link: " : (unsigned char *)"Reverse link: "; unsigned char *link_name = (text.length) ? text.source : name; put_link_line(prefix, link_name, link.href, html_context->base_target, html_context); if (text.source) done_string(&text); } free_and_return: html_link_clear(&link); }