Error'd: Testing English in Production |
Philip G. writes, "I found this gem when I was on the 'Windows USB/DVD Download Tool' page (yes, I know Rufus is better) and I decided to increment the number in the URL."
"Using a snowman emoji as a delimiter...yeah, I guess you could do that," writes George.
Seb wrote, "These signup incentives are just a little too variable for my tastes..."
"Wow. Vodafone UK really isn't selling the battery life of the Samsung Galaxy J3...or maybe they're just being honest?" Steve M. writes.
"Nice to see the Acer website here in South Africa being up front about their attempts at upselling," wrote Gabriel S.
"Thank you $wargaming_company_title$ for your friendly notice, I'll spend my $wot_gold_amount$ in $wot_gold_suggestion$.", Tassu writes.
https://thedailywtf.com/articles/testing-english-in-production
|
Метки: Error'd |
CodeSOD: Foggy about Security |
Maverick StClare’s company recently adopted a new, SaaS solution for resource planning. Like most such solutions, it was pushed from above without regard to how people actually worked, and thus required the users to enter highly structured data into free-form, validation-free, text fields. That was dumb, so someone asked Maverick: “Hey, could you maybe write a program to enter the data for us?”
Well, you’ll be shocked to learn that there was no API, but the web pages themselves all looked pretty simple and the design implied they hadn’t changed since IE4, so Maverick decided to take a crack at writing a scraper. Step one: log in. Easy, right? Maverick fired up a trace on the HTTPS traffic and sniffed the requests. He was happy to see that his password wasn’t sent in plain text. He was less happy to see that it wasn’t sent using any of the standard HTTP authentication mechanisms, and it certainly wasn’t hashed using any algorithm he recognized. He dug into the code, and found this:
function Foggy(svInput)
{
// Any changes must be duplicated in the server-side version of this function.
var svOutput = "";
var ivRnd;
var i;
var ivLength = svInput.length;
if (ivLength == 0 || ivLength > 158)
{
svInput = svInput.replace(/"/g,"&qt;");
return svInput;
}
for (i = 0; i < ivLength; i++)
{
ivRnd = Math.floor(Math.random() * 3);
if (svInput.charCodeAt(i) == 32 || svInput.charCodeAt(i) == 34 || svInput.charCodeAt(i) == 62)
{
ivRnd = 1;
}
if (svInput.charCodeAt(i) == 33 || svInput.charCodeAt(i) == 58 || svInput.charCodeAt(i) == 59 || svInput.charCodeAt(i) + ivRnd > 255)
{
ivRnd = 0;
}
svOutput += String.fromCharCode(ivRnd+97);
svOutput += String.fromCharCode(svInput.charCodeAt(i)+ivRnd);
}
for (i = 0; i < Math.floor(Math.random() * 8) + 8; i++)
{
ivRnd = Math.floor(Math.random() * 26);
svOutput += String.fromCharCode(ivRnd+97);
}
svOutput += String.fromCharCode(svInput.length + 96);
return svOutput;
}
I… have so many questions. Why do they only replace quotes if the string is empty or greater than 158 characters? Why are there random numbers involved in their “hashing” algorithm? I’m foggy about this whole thing, indeed. And ah, protip: security through obscurity works better when nobody can see how you obfuscated things. All I can say is: “aWcjaacvc0b!cVahcgc0b!cHaubdcmb/gmzyrcoqhp”.
|
Метки: CodeSOD |
Representative Line: Got Your Number |
You have a string. It contains numbers. You want to turn those numbers into all “0”s, presumably to anonymize them. You’re also an utter incompetent. What do you do?
You already know what they do. Jane’s co-worker encountered this solution, and she tells us that the language was “Visual BASIC, Profanity”.
Private Function ReplaceNumbersWithZeros(ByVal strText As String) As String
ReplaceNumbersWithZeros = Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(Replace(strText, "1", "0"), "2", "0"), "3", "0"), "4", "0"), "5", "0"), "6", "0"), "7", "0"), "8", "0"), "9", "0")
End Function
Jane adds:
My co-worker found this function while researching some legacy code. Shortly after this discovery, it took us 15 minutes to talk him down off the ledge…and we’re on the ground floor.
|
Метки: Representative Line |
CodeSOD: External SQL |
"Externalize your strings" is generally good advice. Maybe you pull them up into constants, maybe you move them into a resource file, but putting a barrier between your code and the strings you output makes everything more flexible.
But what about strings that aren't output? Things like, oh… database queries? We want to be cautious about embedding SQL directly into our application code, but our SQL code often is our business logic, so it makes sense to inline it. Most data access layers end up trying to abstract the details of SQL behind method calls, whether it's just a simple repository or an advanced ORM approach.
Sean found a… unique approach to resolving this tension in some Java code he inherited. He saw lots of references to keys in a hash-map, keys like user or pw or insert_account_table or select_all_transaction_table. But where did these keys get defined?
Like all good strings, they were externalized into a file called sql.txt. A simple regex-based parser loaded the data and created the dictionary. Now, any module which wanted to query the database had a map of any query they could possibly want to run. Just chuck 'em into a PreparedStatement object and you're ready to go.
Here, in its entirety, is the sql.txt file.
user = root
pw = password
db_name = lrc_mydb
create_account_table = create table if not exists account_table(username varchar(45) not null, password text not null, last_name text, first_name text, mid_name text, suffix_name text, primary key (username))
create_course_table = create table if not exists course_table (course_abbr char(45) not null unique, course_name text, primary key(course_abbr))
create_student_table = create table if not exists student_table (username varchar(45) not null, registration_date date, year_lvl char(45), photolink longblob, freetime time, course_abbr char(45) not null, status char(45) not null, balance double not null, foreign key fk_username(username) references account_table(username) on update cascade on delete cascade, foreign key fk_course_abbr(course_abbr) references course_table(course_abbr) on update cascade on delete cascade, primary key(username))
create_admin_table = create table if not exists admin_table (username varchar(45) not null, delete_priv boolean, settle_priv boolean, db_access boolean, foreign key fk_username(username) references account_table(username) on update cascade on delete cascade, primary key(username))
create_reservation_table = create table if not exists reservation_table (username varchar(45) not null, foreign key fk_username(username) references account_table(username) on update cascade on delete cascade, primary key(username))
create_service_table = create table if not exists service_table (service_id int not null auto_increment, service_name text, amount double, page_requirement boolean, primary key (service_id))
create_pc_table = create table if not exists pc_table (pc_id char(45) not null, ip_address varchar(45), primary key (pc_id))
create_transaction_table = create table if not exists transaction_table (transaction_id int not null auto_increment, date_rendered date, amount_paid double unsigned not null,cost_payable double, username varchar(45) not null, service_id int not null, foreign key fk_username(username) references account_table(username) on update cascade on delete cascade, foreign key fk_service_id(service_id) references service_table(service_id) on update cascade on delete cascade, primary key (transaction_id))
create_pc_usage_table = create table if not exists pc_usage_table (transaction_id int not null, pc_id char(45) not null, login_time time, logout_time time, foreign key fk_pc_id(pc_id) references pc_table(pc_id) on update cascade on delete cascade, foreign key fk_transaction_id(transaction_id) references transaction_table(transaction_id) on update cascade on delete cascade, primary key(transaction_id))
create_pasa_hour_table = create table if not exists pasa_hour_table (transaction_id int not null auto_increment, date_rendered date, sender varchar(45) not null, amount_time time, current_free_sender time, deducted_free_sender time, receiver varchar(45) not null, current_free_receiver time, added_free_receiver time, primary key(transaction_id))
create_receipt_table = create table if not exists receipt_table (dates date, receipt_id varchar(45) not null, transaction_id int not null, username varchar(45) not null, amount_paid double, amount_change double, foreign key fk_transaction_id(transaction_id) references transaction_table(transaction_id) on update cascade on delete cascade, foreign key fk_username(username) references account_table(username) on update cascade on delete cascade)
create_cash_flow_table = create table if not exists cash_flow_table (dates date, cash_in double, cash_close double, cash_out double, primary key(dates))
create_free_pc_usage_table = create table if not exists free_pc_usage_table (transaction_id int not null, foreign key fk_transaction_id(transaction_id) references transaction_table(transaction_id) on update cascade on delete cascade, primary key(transaction_id))
create_diagnostic_table = create table if not exists diagnostic_table (sem_id int not null auto_increment , date_start date, date_end date, sem_num enum('first', 'second', 'mid year'), freetime time, time_penalty double, balance_penalty double, primary key(sem_id))
create_pasa_balance_table = create table if not exists pasa_balance_table (transaction_id int not null auto_increment, date_rendered date, sender varchar(45) not null, amount double, current_balance_sender double, deducted_balance_sender double, receiver varchar(45) not null, current_balance_receiver double, added_balance_receiver double, primary key(transaction_id))
insert_account_table = insert into account_table values (?, password(?), ?, ?, ?, ?)
insert_course_table = insert into course_table values (?, ?)
insert_student_table = insert into student_table values (?, now(), ?, ?, ?, ?, ?, ?)
insert_admin_table = insert into admin_table values (?, ?, ?, ?)
insert_reservation_table = insert into reservation_table values (?)
insert_service_table = insert into service_table (service_name, amount, page_requirement) values (?, ?, ?)
insert_pc_table = insert into pc_table values (?, ?)
insert_transaction_table = insert into transaction_table (date_rendered, amount_paid, cost_payable, username, service_id) values (now(), ?, ?, ?, ?)
insert_pc_usage_table = insert into pc_usage_table values (?, ?, ?, ?)
insert_pasa_hour_table = insert into pasa_hour_table (date_rendered, sender, amount_time, current_free_sender, deducted_free_sender, receiver, current_free_receiver, added_free_receiver) values (curdate(), ?, ?, ?, ?, ?, ?, ?)
insert_free_pc_usage_table = insert into free_pc_usage_table values (?)
insert_cash_flow_table = insert into cash_flow_table values (curdate(), ?, ?, ?)
insert_receipt_table = insert into receipt_table values (curdate(), ?, ?, ?, ?, ?)
insert_diagnostic_table = insert into diagnostic_table (date_start, date_end, sem_num, freetime, time_penalty, balance_penalty) values (?, ?, ?, ?, ?, ?)
insert_pasa_balance_table = insert into pasa_balance_table (date_rendered, sender, amount, current_balance_sender, deducted_balance_sender, receiver, current_balance_receiver, added_balance_receiver) values (curdate(), ?, ?, ?, ?, ?, ?, ?)
delete_reservation_table = delete from reservation_table where username = ?
delete_course_table = delete from course_table where course_abbr = ?
delete_user_assoc_to_course = delete account_table, student_table from student_table inner join account_table on account_table.username = student_table.username where student_table.course_abbr = ?
delete_service_table = delete from service_table where service_name = ?
delete_user_student = delete account_table, student_table from student_table inner join account_table on account_table.username = student_table.username where student_table.username = ?
delete_user_staff = delete account_table, admin_table from admin_table inner join account_table on account_table.username = admin_table.username where admin_table.username = ?
select_total_cost = select sum(cost_payable - amount_paid) from transaction_table where username = ? and cost_payable > amount_paid
select_time_penalty = select time_penalty from diagnostic_table where sem_id = ?
select_balance_penalty = select balance_penalty from diagnostic_table where sem_id = ?
select_balance = select balance from student_table where username = ?
select_accountabilities = select sum(cost_payable - amount_paid) from transaction_table where username = ? and cost_payable > amount_paid
select_count_service_table = select count(*) from service_table
select_count_course_table = select count(*) from course_table
select_course_count = select count(course_abbr) from student_table where course_abbr = ?
select_course_abbr = select course_abbr from course_table where course_name = ?
select_degree_name_abbr = select * from course_table
select_service_name = select * from service_table
select_service_name1 = select service_name from service_table where service_id = ?
select_services_amount = select * from service_table
select_username = select * from account_table where username = (?) and password = password(?)
select_user = select * from account_table where username = (?)
select_reserved_user = select * from reservation_table where username = (?)
select_existing_course = select * from course_table where course_abbr = (?)
select_existing_service = select * from service_table where service_name = (?)
select_existing_transaction_id = select transaction_id from transaction_table where transaction_id = ?
select_user_is_active = select status from student_table where username = ?
select_page_requirement = select page_requirement from service_table where service_name = ?
select_user_details = select account_table.username as 'Username', concat(account_table.last_name, ', ', account_table.first_name, ' ', account_table.suffix_name, ' ', account_table.mid_name) as 'Name', student_table.course_abbr as 'Degree Program', student_table.year_lvl as 'Year Level', student_table.freetime as 'Free Time' from account_table inner join student_table on account_table.username = student_table.username where student_table.username = ?
select_amount_service = select amount from service_table where service_name = ?
select_id_service = select * from service_table where service_name = ?
select_freetime = select student_table.freetime from student_table inner join transaction_table on student_table.username = transaction_table.username where transaction_table.transaction_id = ?
select_timediff = select timediff(time(?), timediff(time(logout_time), time(login_time))) as 'timedifference' from pc_usage_table where transaction_id = ?
select_trans_user = select username from transaction_table where transaction_id = ?
select_pc_id1 = select pc_id from pc_table where ip_address = ?
select_timedifference = select timediff(time(?), timediff(curtime(), time(?))) as 'timedifference' from pc_usage_table where transaction_id = ?
select_logout_time = select logout_time from pc_usage_table where transaction_id = ?
select_login_time = select login_time from pc_usage_table where transaction_id = ?
select_now = select curtime()
select_time_consumed = select timediff(time(logout_time), time(login_time)) as 'timedifference' from pc_usage_table where time_to_sec(timediff(time(logout_time), time(login_time))) < time_to_sec(time(?)) and transaction_id = ?
select_freetime_user = select freetime from student_table where username = ?
select_cost_transaction = select cost_payable from transaction_table where transaction_id = ?
select_amount_transaction = select amount_paid from transaction_table where transaction_id = ?
select_pc_id_from_trans = select pc_id from pc_usage_table where transaction_id = ?
select_pc_id2 = select pc_table.pc_id from pc_table
select_transactions_with_accountabilities = select transaction_id from transaction_table where username = ? and amount_paid < cost_payable
select_picture = select photolink from student_table where username = ?
select_diagnostic_table2 = select * from diagnostic_table where sem_id = ?
select_diagnostic_table = select * from diagnostic_table order by diagnostic_table.date_end desc limit 1
select_filtered_username = select account_table.username as 'Username', account_table.last_name as 'Last Name', concat(account_table.first_name, ', ', account_table.suffix_name) as 'First Name', account_table.mid_name as 'Middle Name', student_table.year_lvl as 'Year Level', student_table.course_abbr as 'Degree Program', student_table.status as 'Status', student_table.balance as 'Balance', ifnull((select sum(transaction_table.cost_payable - transaction_table.amount_paid) from transaction_table where transaction_table.cost_payable > transaction_table.amount_paid and transaction_table.username = account_table.username),0) as 'Accountabilities' from account_table inner join student_table on account_table.username = student_table.username where account_table.username like (?) and student_table.username like (?) group by username
select_filtered_lastname = select account_table.username as 'Username', account_table.last_name as 'Last Name', concat(account_table.first_name, ', ', account_table.suffix_name) as 'First Name', account_table.mid_name as 'Middle Name', student_table.year_lvl as 'Year Level', student_table.course_abbr as 'Degree Program', student_table.status as 'Status', student_table.balance as 'Balance', ifnull((select sum(transaction_table.cost_payable - transaction_table.amount_paid) from transaction_table where transaction_table.cost_payable > transaction_table.amount_paid and transaction_table.username = account_table.username),0) as 'Accountabilities' from account_table join student_table on account_table.username = student_table.username where account_table.last_name like ? group by username
select_filtered_firstname = select account_table.username as 'Username', account_table.last_name as 'Last Name', concat(account_table.first_name, ', ', account_table.suffix_name) as 'First Name', account_table.mid_name as 'Middle Name', student_table.year_lvl as 'Year Level', student_table.course_abbr as 'Degree Program', student_table.status as 'Status', student_table.balance as 'Balance', ifnull((select sum(transaction_table.cost_payable - transaction_table.amount_paid) from transaction_table where transaction_table.cost_payable > transaction_table.amount_paid and transaction_table.username = account_table.username),0) as 'Accountabilities' from account_table join student_table on account_table.username = student_table.username where account_table.first_name like ? group by username
select_filtered_yearlvl = select account_table.username as 'Username', account_table.last_name as 'Last Name', concat(account_table.first_name, ', ', account_table.suffix_name) as 'First Name', account_table.mid_name as 'Middle Name', student_table.year_lvl as 'Year Level', student_table.course_abbr as 'Degree Program', student_table.status as 'Status', student_table.balance as 'Balance', ifnull((select sum(transaction_table.cost_payable - transaction_table.amount_paid) from transaction_table where transaction_table.cost_payable > transaction_table.amount_paid and transaction_table.username = account_table.username),0) as 'Accountabilities' from account_table join student_table on account_table.username = student_table.username where student_table.year_lvl like ? group by username
select_filtered_degprog = select account_table.username as 'Username', account_table.last_name as 'Last Name', concat(account_table.first_name, ', ', account_table.suffix_name) as 'First Name', account_table.mid_name as 'Middle Name', student_table.year_lvl as 'Year Level', student_table.course_abbr as 'Degree Program', student_table.status as 'Status', student_table.balance as 'Balance', ifnull((select sum(transaction_table.cost_payable - transaction_table.amount_paid) from transaction_table where transaction_table.cost_payable > transaction_table.amount_paid and transaction_table.username = account_table.username),0) as 'Accountabilities' from account_table join student_table on account_table.username = student_table.username where student_table.course_abbr like ? group by username
select_filtered_username2 = select transaction_table.date_rendered as 'Date', transaction_table.transaction_id as 'Transaction ID', transaction_table.username 'Username', service_table.service_name 'Service Name', substring(transaction_table.cost_payable, 1, 5) as 'Cost', substring(transaction_table.amount_paid, 1, 5) as 'Amount Rendered' from transaction_table inner join service_table on transaction_table.service_id = service_table.service_id where transaction_table.username like ? group by transaction_id
select_filtered_servicename = select transaction_table.date_rendered as 'Date', transaction_table.transaction_id as 'Transaction ID', transaction_table.username 'Username', service_table.service_name 'Service Name', substring(transaction_table.cost_payable, 1, 5) as 'Cost', substring(transaction_table.amount_paid, 1, 5) as 'Amount Rendered' from transaction_table inner join service_table on transaction_table.service_id = service_table.service_id where service_table.service_name like ? group by transaction_id
select_filtered_date = select transaction_table.date_rendered as 'Date', transaction_table.transaction_id as 'Transaction ID', transaction_table.username 'Username', service_table.service_name 'Service Name', substring(transaction_table.cost_payable, 1, 5) as 'Cost', substring(transaction_table.amount_paid, 1, 5) as 'Amount Rendered' from transaction_table inner join service_table on transaction_table.service_id = service_table.service_id where transaction_table.date_rendered like ? group by Transaction_id
select_all = select account_table.username as 'Username', account_table.last_name as 'Last Name', concat(account_table.first_name, ', ', account_table.suffix_name) as 'First Name', account_table.mid_name as 'Middle Name', student_table.year_lvl as 'Year Level', student_table.course_abbr as 'Degree Program', student_table.status as 'Status', student_table.freetime as 'Free Time', student_table.balance as 'Balance', ifnull((select sum(transaction_table.cost_payable - transaction_table.amount_paid) from transaction_table where transaction_table.cost_payable > transaction_table.amount_paid and transaction_table.username = account_table.username),0) as 'Accountabilities' from account_table join student_table on account_table.username = student_table.username
select_filtered_active = select account_table.username as 'Username', account_table.last_name as 'Last Name', concat(account_table.first_name, ', ', account_table.suffix_name) as 'First Name', account_table.mid_name as 'Middle Name', student_table.year_lvl as 'Year Level', student_table.course_abbr as 'Degree Program', student_table.status as 'Status', student_table.freetime as 'Free Time', student_table.balance as 'Balance', ifnull((select sum(transaction_table.cost_payable - transaction_table.amount_paid) from transaction_table where transaction_table.cost_payable > transaction_table.amount_paid and transaction_table.username = account_table.username),0) as 'Accountabilities' from account_table join student_table on account_table.username = student_table.username where student_table.status = 'active'
select_filtered_inactive = select account_table.username as 'Username', account_table.last_name as 'Last Name', concat(account_table.first_name, ', ', account_table.suffix_name) as 'First Name', account_table.mid_name as 'Middle Name', student_table.year_lvl as 'Year Level', student_table.course_abbr as 'Degree Program', student_table.status as 'Status', student_table.freetime as 'Free Time', student_table.balance as 'Balance', ifnull((select sum(transaction_table.cost_payable - transaction_table.amount_paid) from transaction_table where transaction_table.cost_payable > transaction_table.amount_paid and transaction_table.username = account_table.username),0) as 'Accountabilities' from account_table join student_table on account_table.username = student_table.username where student_table.status = 'inactive'
select_online_pc =
select_reserved_pc = select reservation_table.username as 'Username' from reservation_table
select_staff_table = select account_table.username as 'Username', account_table.last_name as 'Last Name', concat(account_table.first_name, ', ', account_table.suffix_name) as 'First Name', account_table.mid_name as 'Middle Name', admin_table.delete_priv as 'Delete Privilege', admin_table.settle_priv as 'Settle Privilege', admin_table.db_access as 'Database Access' from account_table inner join admin_table on account_table.username = admin_table.username
select_degree_table = select course_table.course_name as 'Degree Program', course_table.course_abbr as 'Abbreviation' from course_table
select_service_table = select service_name as 'Service Name', amount as 'Amount' from service_table
select_pasa_hour = select pasa_hour_table.date_rendered as 'Date', pasa_hour_table.amount_time as 'Amount Time', concat(pasa_hour_table.sender, ' ( ', pasa_hour_table.current_free_sender, ' - ', pasa_hour_table.deducted_free_sender, ' )') as 'Sender (Current - Deducted)', concat(pasa_hour_table.receiver, ' ( ', pasa_hour_table.current_free_receiver, ' - ', pasa_hour_table.added_free_receiver, ' )') as 'Receiver (Current - Added)' from pasa_hour_table
select_pasa_bal = select date_rendered as 'Date', amount as 'Amount Time', concat(sender, ' ( ', current_balance_sender, ' - ', deducted_balance_sender, ' )') as 'Sender (Current - Deducted)', concat(receiver, ' ( ', current_balance_receiver, ' - ', added_balance_receiver, ' )') as 'Receiver (Current - Added)' from pasa_balance_table
select_transaction_table = select transaction_table.date_rendered as 'Date', transaction_table.transaction_id as 'Transaction ID', transaction_table.username 'Username', service_table.service_name 'Service Name', substring(transaction_table.cost_payable, 1, 5) as 'Cost', substring(transaction_table.amount_paid, 1, 5) as 'Amount Rendered' from transaction_table inner join service_table on transaction_table.service_id = service_table.service_id where transaction_table.date_rendered = curdate()
select_all_transaction_table = select transaction_table.date_rendered as 'Date', transaction_table.transaction_id as 'Transaction ID', transaction_table.username 'Username', service_table.service_name 'Service Name', substring(transaction_table.cost_payable, 1, 5) as 'Cost', substring(transaction_table.amount_paid, 1, 5) as 'Amount Rendered' from transaction_table inner join service_table on transaction_table.service_id = service_table.service_id
select_paid_transaction_table = select transaction_table.date_rendered as 'Date', transaction_table.transaction_id as 'Transaction ID', transaction_table.username 'Username', service_table.service_name 'Service Name', substring(transaction_table.cost_payable, 1, 5) as 'Cost', substring(transaction_table.amount_paid, 1, 5) as 'Amount Rendered' from transaction_table inner join service_table on transaction_table.service_id = service_table.service_id where transaction_table.cost_payable <= transaction_table.amount_paid
select_unpaid_transaction_table = select transaction_table.date_rendered as 'Date', transaction_table.transaction_id as 'Transaction ID', transaction_table.username 'Username', service_table.service_name 'Service Name', substring(transaction_table.cost_payable, 1, 5) as 'Cost', substring(transaction_table.amount_paid, 1, 5) as 'Amount Rendered' from transaction_table inner join service_table on transaction_table.service_id = service_table.service_id where transaction_table.cost_payable > transaction_table.amount_paid
select_usage_daily = select distinct a.pc_id as 'PC Number', (select count(b.pc_id) from pc_usage_table b where b.pc_id = a.pc_id && b.transaction_id in (select transaction_id from transaction_table where date_rendered = ?)) as 'Total # of Transactions', (select count(distinct c.username) from transaction_table c where c.transaction_id in (select d.transaction_id from pc_usage_table d where d.pc_id = a.pc_id) && c.transaction_id in (select transaction_id from transaction_table where date_rendered = ?)) as 'Total # of Users' from pc_usage_table a join transaction_table e on a.transaction_id = e.transaction_id where date_rendered = ?
select_usage_monthly = select distinct a.pc_id as 'PC Number', (select count(b.pc_id) from pc_usage_table b where b.pc_id = a.pc_id && b.transaction_id in (select transaction_id from transaction_table where year(date_rendered) = ? and monthname(date_rendered) = ?)) as 'Total # of Transactions', (select count(distinct c.username) from transaction_table c where c.transaction_id in (select d.transaction_id from pc_usage_table d where d.pc_id = a.pc_id) && c.transaction_id in (select transaction_id from transaction_table where year(date_rendered) = ? and monthname(date_rendered) = ?)) as 'Total # of Users' from pc_usage_table a join transaction_table e on a.transaction_id = e.transaction_id where year(e.date_rendered) = ? and monthname(date_rendered) = ?
select_usage_annual = select distinct a.pc_id as 'PC Number', (select count(b.pc_id) from pc_usage_table b where b.pc_id = a.pc_id && b.transaction_id in (select transaction_id from transaction_table where year(date_rendered) = ?)) as 'Total # of Transactions', (select count(distinct c.username) from transaction_table c where c.transaction_id in (select d.transaction_id from pc_usage_table d where d.pc_id = a.pc_id) && c.transaction_id in (select transaction_id from transaction_table where year(date_rendered) = ?)) as 'Total # of Users' from pc_usage_table a join transaction_table e on a.transaction_id = e.transaction_id where year(e.date_rendered) = ?
select_usage_semestral = select distinct a.pc_id as 'PC Number', (select count(b.pc_id) from pc_usage_table b where b.pc_id = a.pc_id && b.transaction_id in (select transaction_id from transaction_table where date_rendered between (select date_start from diagnostic_table where sem_num = ? and (year(date_start) = ? or year(date_end) = ?)) and (select date_end from diagnostic_table where sem_num = ? and (year(date_start) = ? or year(date_end) = ?)))) as 'Total # of Transactions', (select count(distinct c.username) from transaction_table c where c.transaction_id in (select d.transaction_id from pc_usage_table d where d.pc_id = a.pc_id) && c.transaction_id in (select transaction_id from transaction_table where transaction_table.date_rendered between (select date_start from diagnostic_table where sem_num = ? and (year(date_start) = ? or year(date_end) = ?)) and (select date_end from diagnostic_table where sem_num = ? and (year(date_start) = ? or year(date_end) = ?)))) as 'Total # of Users' from pc_usage_table a join transaction_table e on a.transaction_id = e.transaction_id where e.date_rendered between (select date_start from diagnostic_table where sem_num = ? and (year(date_start) = ? or year(date_end) = ?)) and (select date_end from diagnostic_table where sem_num = ? and (year(date_start) = ? or year(date_end) = ?))
select_student_daily = select account_table.username, concat(account_table.last_name, ', ', account_table.first_name, ', ', account_table.suffix_name, ', ', account_table.mid_name), student_table.course_abbr from account_table inner join student_table on student_table.username = account_table.username where account_table.username in (select transaction_table.username from transaction_table inner join pc_usage_table on transaction_table.transaction_id = pc_usage_table.transaction_id where year(transaction_table.date_rendered) = ? and transaction_table.date_rendered = ?)
select_student_monthly = select account_table.username as 'Student Number', concat(account_table.last_name, ', ', account_table.first_name, ', ', account_table.suffix_name, ', ', account_table.mid_name) as 'Name', student_table.course_abbr as 'Degree Program' from account_table inner join student_table on student_table.username = account_table.username where account_table.username in (select transaction_table.username from transaction_table inner join pc_usage_table on transaction_table.transaction_id = pc_usage_table.transaction_id where year(transaction_table.date_rendered) = ? and monthname(transaction_table.date_rendered) = ?)
select_student_annual = select account_table.username as 'Student Number', concat(account_table.last_name, ', ', account_table.first_name, ', ', account_table.suffix_name, ', ', account_table.mid_name) as 'Name', student_table.course_abbr as 'Degree Program' from account_table inner join student_table on student_table.username = account_table.username where account_table.username in (select transaction_table.username from transaction_table inner join pc_usage_table on transaction_table.transaction_id = pc_usage_table.transaction_id where year(transaction_table.date_rendered) = ?)
select_student_semestral = select account_table.username as 'Student Number', concat(account_table.last_name, ', ', account_table.first_name, ', ', account_table.suffix_name, ', ', account_table.mid_name) as 'Name', student_table.course_abbr as 'Degree Program' from account_table inner join student_table on student_table.username = account_table.username where account_table.username in (select transaction_table.username from transaction_table inner join pc_usage_table on transaction_table.transaction_id = pc_usage_table.transaction_id where transaction_table.date_rendered between (select date_start from diagnostic_table where sem_num = ? and (year(date_start) = ? or year(date_end) = ?)) and (select date_end from diagnostic_table where sem_num = ? and (year(date_start) = ? or year(date_end) = ?)))
select_transaction_daily = select service_table.service_name as 'Service Name', sum(transaction_table.cost_payable) as 'Cost Payable' from service_table join transaction_table on service_table.service_id = transaction_table.service_id where transaction_table.date_rendered = ? group by transaction_table.service_id
select_transaction_monthly = select service_table.service_name as 'Service Name', sum(transaction_table.cost_payable) as 'Cost Payable' from service_table join transaction_table on service_table.service_id = transaction_table.service_id where year(transaction_table.date_rendered) = ? and monthname(transaction_table.date_rendered) = ? group by transaction_table.service_id
select_transaction_annual = select service_table.service_name as 'Service Name', sum(transaction_table.cost_payable) as 'Cost Payable' from service_table join transaction_table on service_table.service_id = transaction_table.service_id where year(transaction_table.date_rendered) = ? group by transaction_table.service_id
select_transaction_semestral = select service_table.service_name as 'Service Name', sum(transaction_table.cost_payable) as 'Cost Payable' from service_table join transaction_table on service_table.service_id = transaction_table.service_id where transaction_table.date_rendered between (select date_start from diagnostic_table where sem_num = ? and (year(date_start) = ? or year(date_end) = ?)) and (select date_end from diagnostic_table where sem_num = ? and (year(date_start) = ? or year(date_end) = ?)) group by transaction_table.service_id
select_latest_trans = select transaction_table.date_rendered as 'Date', service_table.service_name as 'Service Name', substring(transaction_table.amount_paid,1,5) as 'Cash Rendered', substring(transaction_table.cost_payable,1,5) as "Cost Payable" from transaction_table inner join service_table on service_table.service_id = transaction_table.service_id where transaction_table.username = ? order by transaction_table.transaction_id desc limit 5
select_trans_by_user = select service_table.service_name as 'Service Name', sum(transaction_table.amount_paid) as 'Amount Paid', sum(transaction_table.cost_payable) as 'Cost Payable' from service_table join transaction_table on service_table.service_id = transaction_table.service_id where transaction_table.username = ? and transaction_table.amount_paid < transaction_table.cost_payable group by transaction_table.service_id
update_activate_student = update student_table set status = 'active' where username = ?
update_deactivate_student = update student_table set status = 'inactive' where username = ?
update_profile_pic = update student_table set photolink = ? where username = ?
update_amount = update transaction_table set amount_paid = ? where transaction_id = ?
update_cash_close = update cash_flow_table set cash_close = cash_close + ? where dates = curdate()
update_balance = update student_table set balance = ? where username = ?
update_logout_expand = update pc_usage_table set logout_time = ? where transaction_id = ?
update_cost_transaction = update transaction_table set cost_payable = (select cost_payable + ? where transaction_id = ?) where transaction_id = ?
update_cost_transaction_plain = update transaction_table set cost_payable = ? where transaction_id = ?
update_amount_transaction = update transaction_table set amount_paid = (select amount_paid + ? where transaction_id = ?) where transaction_id = ?
update_pasa_hour_table = update pasa_hour_table set deducted_free_sender = ?, added_free_receiver = ? where transaction_id = ?
update_pasa_balance_table = update pasa_balance_table set deducted_balance_sender = ?, added_balance_receiver = ? where transaction_id = ?
update_receiver_time = update student_table set freetime = (select addtime(freetime,time(?)) where username = ?) where username = ?
update_sender_time = update student_table set freetime = (select timediff(freetime,time(?)) where username = ?) where username = ?
update_logout_pending = update pc_usage_table set logout_time = (select addtime(time(login_time), time(?))) where transaction_id = ?
update_logout_time = update pc_usage_table set logout_time = curtime() where transaction_id = ?
update_logout_time_with_reference = update pc_usage_table set logout_time = ? where transaction_id = ?
update_user_time = update student_table set freetime = ? where username = ?
update_reset_pw = update account_table set password = password(?) where username = ?
update_all_status = update student_table set status = 'inactive'
update_course_table = update course_table set course_abbr = ?, course_name = ? where course_abbr = ?
update_user_password = update account_table set password = password(?) where username = ? and password = password(?)
update_account_table = update account_table set username = ?, last_name = ?, first_name = ?, mid_name = ?, suffix_name = ? where username = ?
update_admin_table = update admin_table set username = ?, delete_priv = ?, settle_priv = ?, db_access = ? where username = ?
update_student_table = update student_table set username = ?, year_lvl = ?, course_abbr = ?, status = ? where username = ?
update_service_table = update service_table set service_name = ?, amount = ?, page_requirement = ? where service_name = ?
|
Метки: CodeSOD |
Sponsor Post: Error Logging vs. Crash Reporting |
A lot of developers confuse error and crash reporting tools with traditional logging. And it’s easy to make the relation without understanding the two in more detail.
Dedicated logging tools give you a running history of events that have happened in your application. Dedicated error and crash reporting tools focus on the issues users face that occur when your app is in production, and record the diagnostic details surrounding the problem that happened to the user, so you can fix it with greater speed and accuracy.
Most error logging activities within software teams remain just that. A log of errors that are never actioned and fixed.
Traditionally speaking, when a user reports an issue, you might find yourself hunting around in log files searching for what happened so you can debug it successfully.
Having an error reporting tool running silently in production means not only do users not need to report issues, as they are identified automatically, but each one is displayed in a dashboard, ranked by severity. Teams are able to get down to the root cause of an issue in seconds, not hours.
Full diagnostic details about the issue are presented to the developer immediately. Information such as OS, browser, machine, a detailed stack trace, a history of events leading up to the issue and even which individual users have encountered the specific issue are all made available.
In short, when trying to solve issues in your applications, you immediately see the needle, without bothering with the haystack.
Error monitoring tools are designed to give you answers quickly. Once you experience how they fit into the software development workflow and work alongside your logging, you won’t want to manage your application errors in any other way.
So next time you’re struggling to resolve problems in your apps - Think Raygun.
Your life as a developer will be made so much easier.
[Advertisement]
Forget logs. Next time you're struggling to replicate error, crash and performance issues in your apps - Think Raygun! Installs in minutes. Learn more.
https://thedailywtf.com/articles/error-logging-vs-crash-reporting
|
Метки: Sponsor Post |
A Hard SQL Error |
Padma was the new guy on the team, and that sucked. When you're the new guy, but you're not new to the field, there's this maddening combination of factors that can make onboarding rough: a combination of not knowing the product well enough to be efficient, but knowing your craft well enough to expect efficiency. After all, if you're a new intern, you can throw back general-purpose tutorials and feel like you're learning new things at least. When you're a senior trying to make sense of your new company's dizzying array of under-documented products? The only way to get that knowledge is by dragging people who are already efficient away from what they're doing to ask.
By the start of week 2, however, Padma knew enough to get his hands dirty with some smaller bug-fixes. By the end of it, he'd begun browsing the company bug tracker looking for more work on his own. That's when he came across this bug report that seemed rather urgent:

It had been in the tracker for a month. That could mean a lot of things, all of them opaque when you're new enough not to know anyone. Was it impossible to reproduce? Was it one of those reports thrown in by someone who liked to tamper with their test environment and blame things breaking on the coders? Was their survey product just low priority enough that they hadn't gotten around to fixing it? Which client was this for?
It took Padma a few hours to dig into it enough to get to the root of the problem. The repository for their survey product was stored in their private github, one of dozens of repositories with opaque names. He found the codename of the product, "Santiago," by reading older tickets filed against the same product, before someone had renamed the tag to "Survey Deluxe." There was a branch for every client, an empty Master branch, and a Development branch as the default; he reached back out to the reporter for the name of the client so he could pull up their branch. Of course they had a "clientname" branch, a "clientname-new," and a "clientname3.0," but after comparing merge histories, he eventually discovered the production code: in a totally different branch, after they had merged two clients' environments together for a joint venture. Of course.
But finally, he had the problem reproduced in his local dev environment. After an hour of digging through folders, he found the responsible code:
Surveys
"But ... why?!" Padma growled at the screen.
"Oh, is that Santiago?" asked his neighbor, leaning over to see his screen. "Yeah, they requested a one-for-one conversion from their previous product. Warts and all. Seems they thought that was the name of the survey, and it was important that it be in red so they could find it easily enough."
Padma stared at the code in disbelief. After a long moment, he closed the editor and the browser, deleted the code from his hard drive, and closed the ticket "won't fix."
|
Метки: Feature Articles |
Error'd: Be Patient!...OK? |
"I used to feel nervous when making payments online, but now I feel ...um...'Close' about it," writes Jeff K.
"Looks like me and Microsoft have different ideas of what 75% means," Gary S. wrote.
George writes, "Try this one at home! Head to tdbank.com, search for 'documents for opening account' and enjoy 8 solid pages of ...this."
"I'm confused if the developers knew the difference between Javascript and Java. This has to be a troll...right?" wrote JM.
Tom S. writes, "Saw this in the Friendo app, but what I didn't spot was an Ok button. "
"I look at this and wonder if someone could deny a vacation requests because of a conflict of 0.000014 days with another member of staff," writes Rob.
[Advertisement]
Forget logs. Next time you're struggling to replicate error, crash and performance issues in your apps - Think Raygun! Installs in minutes. Learn more.
|
Метки: Error'd |
Wait Low Down |
As mentioned previously I’ve been doing a bit of coding for microcontrollers lately. Coming from the world of desktop and web programming, it’s downright revelatory. With no other code running, and no operating system, I can use every cycle on a 16MHz chip, which suddenly seems blazing fast. You might have to worry about hardware interrupts- in fact I had to swap serial connection libraries out because the one we were using misused interrupts and threw of the timing of my process.
And boy, timing is amazing when you’re the only thing running on the CPU. I was controlling some LEDs and if I just went in a smooth ramp from one brightness level to the other, the output would be ugly steps instead of a smooth fade. I had to use a technique called temporal dithering, which is a fancy way of saying “flicker really quickly” and in this case depended on accurate, sub-microsecond timing. This is all new to me.
Speaking of sub-microsecond timing, or "subus", let's check out Jindra S’s submission. This code also runs on a microcontroller, and for… “performance” or “clock accuracy” is assembly inlined into C.
/*********************** FUNCTION v_Angie_WaitSubus *******************************//**
@brief Busy waits for a defined number of cycles.
The number of needed sys clk cycles depends on the number of flash wait states,
but due to the caching, the flash wait states are not relevant for STM32F4.
4 cycles per u32_Cnt
*******************************************************************************/
__asm void v_Angie_WaitSubus( uint32_t u32_Cnt )
{
loop
subs r0, #1
cbz r0, loop_exit
b loop
loop_exit
bx lr
}
Now, this assembly isn’t the most readable thing, but the equivalent C code is pretty easy to follow: while(--u32_Cnt); In other words, this is your typical busy-loop. Since this code is the only code running on the chip, no problem right? Well, check out this one:
/*********************** FUNCTION v_Angie_IRQWaitSubus *******************************//**
@brief Busy waits for a defined number of cycles.
The number of needed sys clk cycles depends on the number of flash wait states,
but due to the caching, the flash wait states are not relevant for STM32F4.
4 cycles per u32_Cnt
*******************************************************************************/
__asm void v_Angie_IRQWaitSubus( uint32_t u32_Cnt )
{
IRQloop
subs r0, #1
cbz r0, IRQloop_exit
b IRQloop
IRQloop_exit
bx lr
}
What do you know, it’s the same exact code, but called IRQWaitSubus, implying it’s meant to be called inside of an interrupt handler. The details can get fiendishly complicated, but for those who aren’t looking at low-level code on the regular, interrupts are the low-level cousin of event handlers. It allows a piece of hardware (or software, in multiprocessing systems) to notify the CPU that something interesting has happened, and the CPU can then execute some of your code to react to it. Like any other event handler, interrupt handlers should be fast, so they can update the program state and then allow normal execution to continue.
What you emphatically do not do is wait inside of an interrupt handler. That’s bad. Not a full-on WTF, but… bad.
There’s at least three more variations of this function, with slightly different names, scattered across different modules, all of which represent a simple busy loop.
Ugly, sure, but where’s the WTF? Well, among other things, this board needed to output precisely timed signals, like say, a 500Hz square wave with a 20% duty cycle. The on-board CPU clock was a simple oscillator which would drift- over time, with changes in temperature, etc. Also, interrupts could claim CPU cycles, throwing off the waits. So Jindra’s company had placed this code onto some STM32F4 ARM microcontrollers, shipped it into the field, and discovered that outside of their climate controlled offices, stuff started to fail.
The code fix was simple- the STM32-series of processors had a hardware timer which could provide precise timing. Switching to that approach not only made the system more accurate- it also meant that Jindra could throw away hundreds of lines of code which was complicated, buggy, and littered with inline assembly for no particular reason. There was just one problem: the devices with the bad software were already in the field. Angry customers were already upset over how unreliable the system was. And short of going on site to reflash the microcontrollers or shipping fresh replacements, the company was left with only one recourse:
They announced Rev 2 of their product, which offered higher rates of reliability and better performance, and only cost 2% more!
[Advertisement]
Forget logs. Next time you're struggling to replicate error, crash and performance issues in your apps - Think Raygun! Installs in minutes. Learn more.
|
Метки: Feature Articles |
The Wizard Algorithm |
Password requirements can be complicated. Some minimum and maximum number of characters, alpha and numeric characters, special characters, upper and lower case, change frequency, uniqueness over the last n passwords and different rules for different systems. It's enough to make you revert to a PostIt in your desk drawer to keep track of it all. Some companies have brillant employees who feel that they can do better, and so they create a way to figure out the password for any given computer - so you need to neither remember nor even know it.
History does not show who created the wizard algorithm, or when, or what they were smoking at the time.
Barry W. has the misfortune of being a Windows administrator at a company that believes in coming up with their own unique way of doing things, because they can make it better than the way that everyone else is doing it. It's a small organization, in a sleepy part of a small country. And yet, the IT department prides itself on its highly secure practices.
Take the password of the local administrator account, for instance. It's the Windows equivalent of root, so you'd better use a long and complex password. The IT team won't use software to automate and keep track of passwords, so to make things extremely secure, there's a different password for every server.
Here's where the wizard algorithm comes in.
To determine the password, all you need is the server's hostname and its IP address.
For example, take the server PRD-APP2-SERV4 which has the IP address 178.8.1.44.
Convert the hostname to upper case and discard any hyphens, yielding PRDAPP2SERV4.
Take the middle two octets of the IP address. If either is a single digit, pad it out to double digits. So 178.8.1.44 becomes 178.80.10.44 which yields 8010. Now take the last character of the host name; if that's a digit, discard it and take the last letter, otherwise just take the last letter, which gives us V. Now take the second and third letters of the hostname and concatenate them to the 8010 and then stick that V on the end. This gives us 8010RDV. Now take the fourth and fifth letters, and add them to the end, which makes 8010RDVAP. And there's your password! Easy.
It had been that way for as long as anyone could remember, until the day someone decided to enable password complexity on the domain. From then on, you had to do all of the above, and then add @!#%&$?@! to the end of the password. How would you know whether a server has a password using the old method or the new one? Why by a spreadsheet available on the firm-wide-accessible file system, of course! Oh, by the way, there is no server management software.
Critics might say the wizard algorithm has certain disadvantages. The fact that two people, given the same hostname and IP address, often come up with different results for the algorithm. Apparently, writing a script to figure it out for you never dawned on anyone.
Or the fact that when a server has lost contact with the domain and you're trying to log on locally and the phone's ringing and everyone's pressuring you to get it resolved, the last thing you want to be doing is math puzzles.
But at least it's better than the standard way people normally do it!
|
Метки: Feature Articles |
CodeSOD: A Unique Specification |
One of the skills I think programmers should develop is not directly programming related: you should be comfortable reading RFCs. If, for example, you want to know what actually constitutes an email address, you may want to brush up on your BNF grammars. Reading and understanding an RFC is its own skill, and while I wouldn’t suggest getting in the habit of reading RFCs for fun, it’s something you should do from time to time.
To build the skill, I recommend picking a simple one, like UUIDs. There’s a lot of information encoded in a UUID, and five different ways to define UUIDs- though usually we use type 1 (timestamp-based) and type 4 (random). Even if you haven’t gone through and read the spec, you already know the most important fact about UUIDs: they’re unique. They’re universally unique in fact, and you can use them as identifiers. You shouldn’t have a collision happen within the lifetime of the universe, unless someone does something incredibly wrong.
Dexen encountered a database full of collisions on UUIDs. Duplicates were scattered all over the place. Since we’re not well past the heat-death of the universe, the obvious answer is that someone did something entirely wrong.
use Ramsey\Uuid\Uuid;
$model->uuid = Uuid::uuid5(Uuid::NAMESPACE_DNS, sprintf('%s.%s.%s.%s',
rand(0, time()), time(),
static::class, config('modelutils.namespace')))->toString();
This block of PHP code uses the type–5 UUID, which allows you to generate the UUID based on a name. Given a namespace, usually a domain name, it runs it through SHA–1 to generate the required bytes, allowing you to create specific UUIDs as needed. In this case, Dexen’s predecessor was generating a “domain name”-ish string by combining: a random number from 0 to seconds after the epoch, the number of seconds after the epoch, the name of the class, and a config key. So this developer wasn’t creating UUIDs with a specific, predictable input (the point of UUID–5), but was mixing a little from the UUID–1 time-based generation, and the UUID–4 random-based generation, but without the cryptographically secure source of randomness.
Thus, collisions. Since these UUIDs didn’t need to be sortable (no need for UUID–1), Dexen changed the generation to UUID–4.
|
Метки: CodeSOD |
CodeSOD: The Sanity Check |
I've been automating deployments at work, and for Reasons™, this is happening entirely in BASH. Those Reasons™ are that the client wants to use Salt, but doesn't want to give us access to their Salt environment. Some of our deployment targets are microcontrollers, so Salt isn't even an option.
While I know the shell well enough, I'm getting comfortable with more complicated scripts than I usually write, along with tools like xargs which may be the second best shell command ever invented. yes is the best, obviously.
The key point is that the shell, coupled with the so-called "Unix Philosophy" is an incredibly powerful tool. Even if you already know that it's powerful, it's even more powerful than you think it is.
How powerful? Well, how about ripping apart the fundamental rules of mathematics? An anonymous submitter found this prelude at the start of every shell script in their organization.
#/usr/bin/env bash
declare -r ZERO=$(true; echo ${?})
declare -r DIGITZERO=0
function sanity_check() {
function err_msg() {
echo -e "\033[31m[ERR]:\033[0m ${@}"
}
if [ ${ZERO} -ne ${DIGITZERO} ]; then
err_msg "The laws of physics doesn't apply to this server."
err_msg "Real value ${ZERO} is not equal to ${DIGITZERO}."
exit 1
fi
}
sanity_check
true, like yes, is one of those absurdly simple tools: it's a program that completes successfully (returning a 0 exit status back to the shell). The ${?} expression contains the last exit status. Thus, the variable $ZERO will contain… 0. Which should then be equal to 0.
Now, maybe BASH isn't BASH anymore. Maybe true has been patched to fail. Maybe, maybe, maybe, but honestly, I'm wondering whose sanity is actually being checked in the sanity_check?
|
Метки: CodeSOD |
Error'd: Just Handle It |
Clint writes, "On Facebook, I tried to report a post as spam. I think I might just have to accept it."
"Jira seems to have strange ideas about my keyboard layout... Or is there a key that I don't know about?" writes Rob H.
George wrote, "There was deep wisdom bestowed upon weary travelers by the New York subway system at the Jamaica Center station this morning."
"Every single number field on the checkout page, including phone and credit card, was an integer. Just in case, you know, you felt like clicking a lot," Jeremiah C. writes.
"I don't know which is more ridiculous: that a Linux recovery image is a Windows 10, or that there's a difference between Pro and Professional," wrote Dima R.
"I got my weekly workout summary and, well, it looks I might have been hitting the gym a little too hard," Colin writes.
|
Метки: Error'd |
The New Guy (Part II): Database Boogaloo |
When we last left our hero Jesse, he was wading through a quagmire of undocumented bad systems while trying to solve an FTP issue. Several months later, Jesse had things figured out a little better and was starting to feel comfortable in his "System Admin" role. He helped the company join the rest of the world by dumping Windows NT 4.0 and XP. The users whose DNS settings he bungled were now happily utilizing Windows 10 workstations. His web servers were running Windows Server 2016, and the SQL boxes were up to SQL 2016. Plus his nemesis Ralph had since retired. Or died. Nobody knew for sure. But things were good.
Despite all these efforts, there were still several systems that relied on Access 97 haunting him every day. Jesse spent tens of dollars of his own money on well-worn Access 97 programming books to help plug holes in the leaky dike. The A97 Finance system in particular was a complete mess to deal with. There were no clear naming guidelines and table locations were haphazard at best. Stored procedures and functions were scattered between the A97 VBS and the SQL DB. Many views/functions were nested with some going as far as eight layers while others would form temporary tables in A97 then continue to nest.
One of Jesse's small wins involved improving performance of some financial reporting queries that took minutes to run before but now took seconds. A few of these sped-up reports happened to be ones that Shane, the owner of the company, used frequently. The sudden time-savings got his attention to the point of calling Jesse in to his office to meet.
"Jesse! Good to see you!" Shane said in an overly cheerful manner. "I'm glad to talk to the guy who has saved me a few hours a week with his programmering fixes." Jesse downplayed the praise before Shane got to the point. "I'd like to find out from you how we can make further improvements to our Finance program. You seem to have a real knack for this."
Jesse, without thinking about it, blurted, "This here system is a pile of shit." Shane stared at him blankly, so he continued, "It should be rebuilt from the ground up by experienced software development professionals. That's how we make further improvements."
"Great idea! Out with the old, in with the new! You seem pretty well-versed in this stuff, when can you start on it?" Shane said with growing excitement. Jesse soon realized his response had backfired and he was now on the hook to the owner for a complete system rewrite. He took a couple classes on C# and ASP.NET during his time at Totally Legit Technical Institute so it was time to put that valuable knowledge to use.
Shane didn't just let Jesse loose on redoing the Finance program though. He insisted Jesse work closely with Linda, their CFO who used it the most. Linda proved to be very resistant to any kind of change Jesse proposed. She had mastered the painstaking nuances of A97 and didn't seem to mind fixing large amounts of bad data by hand. "It makes me feel in control, you know," Linda told him once after Jesse tried to explain the benefits of the rewrite.
While Jesse pecked away at his prototype, Linda would relentlessly nitpick any UI ideas he came up with. If she had it her way, the new system would only be usable by someone as braindead as her. "I don't need all these fancy menus and buttons! Just make it look and work like it does in the current system," she would say at least once a week. "And don't you dare take my manual controls away! I don't trust your automated robotics to get these numbers right!" In the times it wasn't possible to make something work like Access 97, she would run to Shane, who would have to talk her down off the ledge.
Even though Linda opposed Jesse at every turn, the new system was faster and very expandable. Using C# .NET 4.7.1 with WPF, it was much less of an eyesore. The database was also clearly defined with full documentation, both on the tables and in the stored procedures. The database size managed to go from 8 GB to .8 GB with no loss in data.
The time came at last for go-live of Finance 2.0. The thing Jesse was most excited about was shutting down the A97 system and feeling Linda die a little bit inside. He sent out an email to the Finance department with instructions for how to use it. The system was well-received by everyone except Linda. But that still led to more headaches for Jesse.
With Finance 2.0 in their hands, the rest of the users noticed the capabilities modern technology brought. The feature requests began pouring in with no way to funnel them. Linda refused to participate in feature reviews because she still hated the new system, so they all went to Shane, who greenlighted everything. Jesse soon found himself buried in the throes of the monster he created with no end in sight. To this day, he toils at his computer cranking out features while Linda sits and reminisces about the good old days of Access 97.
https://thedailywtf.com/articles/the-new-guy-part-ii-database-boogaloo
|
Метки: Feature Articles |
The Manager Who Knew Everything |
Have you ever worked for/with a manager that knows everything about everything? You know the sort; no matter what the issue, they stubbornly have an answer. It might be wrong, but they have an answer, and no amount of reason, intelligent thought, common sense or hand puppets will make them understand. For those occasions, you need to resort to a metaphorical clue-bat.
A few decades ago, I worked for a place that had a chief security officer who knew everything there was to know about securing their systems. Nothing could get past the policies she had put in place. Nobody could ever come up with any mechanism that could bypass her concrete walls, blockades and insurmountable defenses.
One day, she held an interdepartmental meeting to announce her brand spanking shiny new policies regarding this new-fangled email that everyone seemed to want to use. It would prevent unauthorized access, so only official emails sent by official individuals could be sent through her now-secured email servers.
I pointed out that email servers could only be secured to a point, because they had to have an open port to which email clients running on any internal computer could connect. As long as the port was open, anyone with internal access and nefarious intent could spoof a legitimate authorized email address and send a spoofed email.
She was incensed and informed me (and the group) that she knew more than all of us (together) about security, and that there was absolutely no way that could ever happen. I told her that I had some background in military security, and that I might know something that she didn't.
At this point, if she was smart, she would have asked me to explain. If she already handled the case, then I'd have to shut up. If she didn't handle the case, then she'd learn something, AND the system could be made more secure. She was not smart; she publicly called my bluff.
I announced that I accepted the challenge, and that I was going to use my work PC to send an email - from her - to the entire firm (using the restricted blast-to-all email address, which I would not normally be able to access as myself). In the email, I would explain that it was a spoof, and if they were seeing it, then the so-called impenetrable security might be somewhat less secure than she proselytized. In fact, I would do it in such a way that there would be absolutely no way to prove that I did it (other than my admission in the email).
She said that if I did that, that I'd be fired. I responded that 1) if the system was as secure as she thought, that there'd be nothing to fire me for, and 2) if they could prove that it was me, and tell me how I did it (aside from my admission that I had done it), that I would resign. But if not, then she had to stop the holier-than-thou act.
Fifteen minutes later, I went back to my desk, logged into my work PC using the guest account, wrote a 20 line Cold Fusion script to attach to the email server on port 25, and filled out the fields as though it was coming from her email client. Since she had legitimate access to the firm-wide email blast address, the email server allowed it. Then I sent it. Then I secure-erased the local system event and assorted other logs, as well as editor/browser/Cold Fusion/server caches, etc. that would show what I did. Finally, I did a cold boot to ensure that even the RAM was wiped out.
Not long after that, her minions the SA's showed up at my desk joking that they couldn't believe that I had actually done it. I told them that I had wiped out all the logs where they'd look, the actual script that did it, and the disk space that all of the above had occupied. Although they knew the IP address of the PC from which the request came, they agreed that without those files, there was no way they could prove that it was me. Then they checked everything and verified what I told them.
This info made its way back up the chain until the SAs, me and my boss got called into her office, along with a C-level manager. Everything was explained to the C-manager. She was expecting him to fire me.
He simply looked at me and raised an eyebrow. I responded that I spent all of ten minutes doing it in direct response to her assertion that it was un-doable, and that I had announced my intentions to expose the vulnerability - to her - in front of everyone - in advance.
He chose to tell her that maybe she needed to accept that she doesn't know quite as much about everything as she thinks, and that she might want to listen to people a little more. She then pointed out that I had proven that email was totally insecure and that it should be banned completely (this was at the point where the business had mostly moved to email). I pointed out that I had worked there for many years, had no destructive tendencies, that I was only exposing a potential gap in security, and would not do it again. The SAs also pointed out that the stunt, though it proved the point, was harmless. They also mentioned that nobody else at the firm had access to Cold Fusion. I didn't think it helpful to mention that not just Cold Fusion, but any programming language could be used to connect to port 25 and do the same thing, and so didn't. She huffed and puffed, but had no credibility at that point.
After that, my boss and I bought the SAs burgers and beer.
https://thedailywtf.com/articles/the-manager-who-knew-everything
|
Метки: Feature Articles |
CodeSOD: Maximum Performance |
There is some code, that at first glance, doesn’t seem great, but doesn’t leap out as a WTF. Stephe sends one such block.
double SomeClass::getMaxKeyValue(std::vector list)
{
double max = 0;
for (int i = 0; i < list.size(); i++) {
if (list[i] > max) {
max = list[i];
}
}
return max;
}
This isn’t great code. Naming a vector-type variable list is itself pretty confusing, the parameter should be marked as const to cut down on copy operations, and there’s an obvious potential bug: what happens if the input is nothing but negative values? You’ll incorrectly return 0, every time.
Still, this code, taken on its own, isn’t a WTF. We need more background.
First off, what this code doesn’t tell you is that we’re looking at a case of the parallel arrays anti-pattern. The list parameter might be something different depending on which key is being searched. As you can imagine, this creates spaghettified, difficult to maintain code. Code that performed terribly. Really terribly. Like “it must have crashed, no wait, no, the screen updated, no wait it crashed again, wait, it’s…” terrible.
Why was it so terrible? Well, for starters, the inputs to getMaxKeyValue were often arrays containing millions of elements. This method was called hundreds of times throughout the code, mostly inside of window redrawing code. All of that adds up to a craptacular application, but there’s one last, very important detail which brings this up to full WTF:
The inputs were already sorted in ascending order.
With a few minor changes, like taking advantage of the sorted vectors, Stephe to the 0.03333 frames-per-second performance up to something acceptable.
|
Метки: CodeSOD |
CodeSOD: The Enabler |
Shaneka works on software for an embedded device for a very demanding client. In previous iterations of the software, the client had made their own modifications to the device's code, and demanded they be incorporated. Over the years, more and more of the code came from the client, until the day when the client decided it was too much effort to maintain the ball of mud and just started demanding features.
One specific feature was a new requirement for turning the display on and off. Shaneka attempted to implement the feature, and it didn't work. No matter what she did, once they turned the display off, they simply couldn't turn it back on without restarting the whole system.
She dug into the code, and found the method to enable the display was implemented like this:
/***************************************************************************//**
* @brief Method, which enables display
*
* @param true = turn on / false = turn off
* @return None
*******************************************************************************/
void InformationDisplay::Enable(bool state)
{
displayEnabled = state;
if (!displayEnabled) {
enableDisplay(false);
}
}
The Enable method does a great job at turning off the display, but not so great a job turning it back on, no matter what the comments say. The simple fix would be to just pass the state parameter to enableDisplay directly, but huge swathes of the code depended on this method having the incorrect behavior. Shaneka instead updated the documentation for this method and wrote a new method which behaved correctly.
As you can guess, this is one of the pieces of code which came from the client.
[Advertisement]
Forget logs. Next time you're struggling to replicate error, crash and performance issues in your apps - Think Raygun! Installs in minutes. Learn more.
|
Метки: CodeSOD |
Error'd: Try Again (but with More Errors) |
"Sorry, Walgreens, in the future, I'll try to make an error next time," Greg L. writes.
"Hmm, I'm either going to shave with my new razors that I ordered... or I won't," wrote Paul.
Charlie L. writes,"IFNAME would be my name, IF it were my name that is."
"Yep, Dell, I like to brag about my kids File, Edit, View, Tools, and Help," wrote Carl C.
Renato L. writes, "Low-cost airlines have come a long way. Forget the Gregorian calendar, created their own one."
"So is this becuase, somehow, passwords longer than 9 characters are less secure?" wrote Keith H.
[Advertisement]
Forget logs. Next time you're struggling to replicate error, crash and performance issues in your apps - Think Raygun! Installs in minutes. Learn more.
https://thedailywtf.com/articles/try-again-but-with-more-errors
|
Метки: Error'd |
Improv for Programmers: The Internet of Really Bad Things |
Things might get a little dark in the season (series?) finale of Improv for Programmers, brought to you by Raygun. Remy, Erin, Ciar'an and Josh are back, and not only is everything you're about to hear entirely made up on the spot: everything you hear will be a plot point in the next season of Mr. Robot.
Raygun provides a window into how users are really experiencing your software applications.
Unlike traditional logging, Raygun silently monitors applications for issues affecting end users in production, then allows teams to pinpoint the root cause behind a problem with greater speed and accuracy by providing detailed diagnostic information for developers. Raygun makes fixing issues 1000x faster than traditional debugging methods using logs and incomplete information.
Now’s the time to sign up. In a few minutes, you can have a build of your app with Raygun integrated, and you’ll be surprised at how many issues it can identify. There’s nothing to lose with a 14-day free trial, and there are pricing options available that fit any team size.
https://thedailywtf.com/articles/improv-for-programmers-the-internet-of-really-bad-things
|
Метки: Feature Articles |
Sponsor Post: Six Months of Free Monitoring at Panopta for TDWTF Readers |
You may not have noticed, but in the footer of the site, there is a little banner that says:
Monitored by Panopta
Actually, The Daily WTF has been monitored with Panopta for nearly ten years. I've also been using it to monitor Inedo's important public and on-prem servers, and email and text us when there are issues.
I started using Panopta because it's easy to use and allows you to monitor using a number of different methods (public probes, private probes and server agents). I may install agents for more detailed monitoring going forward, but having Panopta probe HTTP, HTTPS, VPN, and SMTP is sufficient for our needs at the moment. We send custom HTTP payloads to mimic our actual use cases, especially with our registration APIs.
If you're not using a monitoring / alerting platform or want to try something new, now's the time to start!
Panopta is offering The Daily WTF readers six months of free monitoring!
Give it a shot. You may find yourself coming to dread those server outage emails and SMS messages. PROTIP: configure the alerting workflow to send outage notices to someone else to worry about.
Disclaimer: while Panopta is not paid sponsor, they been generously providing free monitoring for The Daily WTF (and Inedo) because they're fans of the site; I thought it was high time to tell you about them!
https://thedailywtf.com/articles/six-months-of-free-monitoring-at-panopta-for-tdwtf-readers
|
Метки: Sponsor Post |
CodeSOD: Many Happy Returns |
We've all encountered a situation where changing requirements caused some function that had a single native return type to need to return a second value. One possible solution is to put the two return values in some wrapper class as follows:
class ReturnValues { private int numDays; private String lastName; public ReturnValues(int i, String s) { numDays = i; lastName = s; } public int getNumDays() { return numDays; } public String getLastname() { return lastName; } }
It is trivial to add additional return values to this mechanism. If this is used as the return value to an interface function and you don't have access to change the ReturnValues object itself, you can simply subclass the ReturnValues wrapper to include additional fields as needed and return the base class reference.
Then you see something like this spread out over a codebase and wonder if maybe they should have been just a little less agile and that perhaps a tad more planning was required:
class AlsoReturnTransactionDate extends ReturnValues { private Date txnDate; public AlsoReturnTransactionDate(int i, String s, Date td) { super(i,s); txnDate = td; } public Date getTransactionDate() { return txnDate; } } class AddPriceToReturn extends AlsoReturnTransactionDate { private BigDecimal price; public AddPriceToReturn(int i, String s, Date td, BigDecimal px) { super(i,s,td); price = px; } public BigDecimal getPrice() { return price; } } class IncludeTransactionData extends AddPriceToReturn { private Transaction txn; public IncludeTransactionData(int i, String s, Date td, BigDecimal px, Transaction t) { super(i,s,td,px); txn = t; } public Transaction getTransaction() { return txn; } } class IncludeParentTransactionId extends IncludeTransactionData { private long id; public IncludeParentTransactionId(int i, String s, Date td, BigDecimal px, Transaction t, long id) { super(i,s,td,px,t); this.id = id; } public long getParentTransactionId() { return id; } } class ReturnWithRelatedData extends IncludeParentTransactionId { private RelatedData rd; public ReturnWithRelatedData(int i, String s, Date td, BigDecimal px, Transaction t, long id, RelatedData rd) { super(i,s,td,px,t,id); this.rd = rd; } public RelatedData getRelatedData() { return rd; } } class ReturnWithCalculatedFees extends ReturnWithRelatedData { private BigDecimal calcedFees; public ReturnWithCalculatedFees(int i, String s, Date td, BigDecimal px, Transaction t, long id, RelatedData rd, BigDecimal cf) { super(i,s,td,px,t,id,rd); calcedFees = cf; } public BigDecimal getCalculatedFees() { return calcedFees; } } class ReturnWithExpiresDate extends ReturnWithCalculatedFees { private Date expiresDate; public ReturnWithExpiresDate(int i, String s, Date td, BigDecimal px, Transaction t, long id, RelatedData rd, BigDecimal cf, Date ed) { super(i,s,td,px,t,id,rd,cf); expiresDate = ed; } public Date getExpiresDate() { return expiresDate; } } class ReturnWithRegulatoryQuantities extends ReturnWithExpiresDate { private RegulatoryQuantities regQty; public ReturnWithRegulatoryQuantities(int i, String s, Date td, BigDecimal px, Transaction t, long id, RelatedData rd, BigDecimal cf, Date ed, RegulatoryQuantities rq) { super(i,s,td,px,t,id,rd,cf,ed); regQty = rq; } public RegulatoryQuantities getRegulatoryQuantities() { return regQty; } } class ReturnWithPriorities extends ReturnWithRegulatoryQuantities { private Map<String,Double> priorities; public ReturnWithPriorities(int i, String s, Date td, BigDecimal px, Transaction t, long id, RelatedData rd, BigDecimal cf, Date ed, RegulatoryQuantities rq, Map<String,Double> p) { super(i,s,td,px,t,id,rd,cf,ed,rq); priorities = p; } public Map<String,Double> getPriorities() { return priorities; } } class ReturnWithRegulatoryValues extends ReturnWithPriorities { private Map<String,Double> regVals; public ReturnWithRegulatoryValues(int i, String s, Date td, BigDecimal px, Transaction t, long id, RelatedData rd, BigDecimal cf, Date ed, RegulatoryQuantities rq, Map<String,Double> p, Map<String,Double> rv) { super(i,s,td,px,t,id,rd,cf,ed,rq,p); regVals = rv; } public Map<String,Double> getRegulatoryValues() { return regVals; } }
The icing on the cake is that everywhere the added values are used, the base return type has to be cast to at least the level that contains the needed field.
|
Метки: CodeSOD |